Commit plan and iteration instructions
This commit is contained in:
parent
8ab908054c
commit
c2d1a1e0b6
7 changed files with 637 additions and 118 deletions
30
CLAUDE.md
30
CLAUDE.md
|
|
@ -64,6 +64,36 @@ cmake --build build --config Release --target BVLEVoxels --parallel
|
|||
|
||||
Le SDK 10.0.26100 est requis car les headers DX12 (`d3dx12_check_feature_support.h`) fournis par Wicked Engine ne sont pas compatibles avec le SDK 22621.
|
||||
|
||||
### Exécution
|
||||
|
||||
**IMPORTANT** : Le CWD doit être la **racine du projet**, pas `build/Release/`.
|
||||
L'exe utilise des chemins relatifs pour les assets (`Content/`) et la compilation shader (`engine/WickedEngine/shaders/`).
|
||||
|
||||
```bash
|
||||
# Lancer normalement (fenêtre 1920x1080 centrée)
|
||||
build/Release/BVLEVoxels.exe
|
||||
|
||||
# Mode screenshot (640x480, capture 3 vues, quitte automatiquement)
|
||||
build/Release/BVLEVoxels.exe screenshot
|
||||
|
||||
# Autres arguments
|
||||
build/Release/BVLEVoxels.exe debug # Faces colorées par direction
|
||||
build/Release/BVLEVoxels.exe debugsmooth # Scène smooth debug
|
||||
build/Release/BVLEVoxels.exe vulkan # Forcer backend Vulkan
|
||||
```
|
||||
|
||||
**Fichiers de sortie** (écrits dans le CWD, donc la racine du projet) :
|
||||
- `bvle_backlog.txt` — log Wicked Engine
|
||||
- `bvle_crash.log` + `bvle_crash.dmp` — crash report SEH (si crash)
|
||||
- `bvle_screenshot_*.png` — captures mode screenshot ou F6
|
||||
|
||||
**Raccourcis clavier** :
|
||||
- `F2` — toggle backlog Wicked
|
||||
- `F3` — toggle animation terrain (30 Hz)
|
||||
- `F4` — toggle debug blend
|
||||
- `F5` — cycle RT shadows/AO (ON → debug shadows → debug AO → OFF)
|
||||
- `F6` — screenshot in-app (sauvegarde `voxelRT_` en PNG)
|
||||
|
||||
### Post-build automatique (CMakeLists.txt)
|
||||
|
||||
Le build copie automatiquement :
|
||||
|
|
|
|||
146
docs/plan-lod-skirts.md
Normal file
146
docs/plan-lod-skirts.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# LOD multi-resolution avec skirts
|
||||
|
||||
Inspire du talk Roblox SIGGRAPH 2020 (p.34-38) et de l'approche Transvoxel.
|
||||
Objectif : augmenter la distance de vue sans exploser le cout de meshing/rendu.
|
||||
|
||||
## Probleme actuel
|
||||
|
||||
- Le monde entier (512x512x256 = 8192 chunks potentiels, ~648 actifs) est meshe et
|
||||
rendu a pleine resolution 32^3
|
||||
- Le meshing smooth CPU coute 17ms pour 648 chunks (parallelise)
|
||||
- Le rendu est cheap (0.1ms GPU mesh), mais le meshing smooth bloque le scale-up
|
||||
- Pas de distance de vue variable : tout ou rien
|
||||
|
||||
## Approche Roblox : mip pyramid + skirts
|
||||
|
||||
### Principe
|
||||
|
||||
1. Chaque chunk stocke un **mip pyramid** de voxels : 32^3, 16^3, 8^3, 4^3, 2^3, 1^3
|
||||
2. Un **octree** de rendu decide quel niveau de mip utiliser par chunk (distance camera)
|
||||
3. Les coutures entre chunks de LOD different sont masquees par des **skirts**
|
||||
4. Les skirts sont des triangles supplementaires avec **depth bias** dans le VS
|
||||
|
||||
### Pourquoi des skirts plutot que Transvoxel ?
|
||||
|
||||
| | Transvoxel | Skirts (Roblox) |
|
||||
|--|-----------|-----------------|
|
||||
| Complexite | Elevee (tables de cas, 73 transition cells) | Faible (1 couche extra + depth bias) |
|
||||
| Qualite | Parfaite (mesh continu) | Bonne (gaps invisibles grace au depth bias) |
|
||||
| Cout meshing | +50% (transition cells) | +15% (1 couche de voxels en plus) |
|
||||
| Integration | Invasive (change le mesher) | Additive (post-process sur le mesh) |
|
||||
|
||||
## Plan d'implementation
|
||||
|
||||
### Phase A : Mip pyramid storage
|
||||
|
||||
**Fichier :** `VoxelWorld.h/.cpp`
|
||||
|
||||
```cpp
|
||||
struct Chunk {
|
||||
VoxelData voxels[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE]; // LOD 0 (32^3)
|
||||
// Mip levels stockes a la demande
|
||||
std::array<std::vector<VoxelData>, 4> mips; // LOD 1-4 (16^3, 8^3, 4^3, 2^3)
|
||||
uint8_t maxAvailableLOD = 0;
|
||||
};
|
||||
```
|
||||
|
||||
**Downsampling :** pour chaque groupe 2x2x2, le voxel dominant (material le plus frequent,
|
||||
occupancy > 4/8) est conserve. Voxels smooth : l'occupancy est moyennee.
|
||||
|
||||
**Memoire :** un mip pyramid complet = 32^3 + 16^3 + 8^3 + 4^3 + 2^3 = 37448 voxels
|
||||
= ~73 Ko par chunk (vs 64 Ko actuellement). Surcout de 14%.
|
||||
|
||||
### Phase B : Selection de LOD
|
||||
|
||||
**Fichier :** `VoxelRenderer.cpp` (dans le frustum cull ou en CPU)
|
||||
|
||||
```cpp
|
||||
uint8_t selectLOD(const ChunkPos& pos, const XMFLOAT3& cameraPos) {
|
||||
float dist = distance(chunkCenter(pos), cameraPos);
|
||||
if (dist < 64.0f) return 0; // pleine resolution
|
||||
if (dist < 128.0f) return 1; // 16^3
|
||||
if (dist < 256.0f) return 2; // 8^3
|
||||
return 3; // 4^3
|
||||
}
|
||||
```
|
||||
|
||||
Le LOD est passe au mesher. Le mesher binaire greedy et le Surface Nets travaillent
|
||||
sur le mip correspondant (identiques, juste un tableau plus petit).
|
||||
|
||||
Le compute shader `voxelMeshCS` recoit le LOD level et ajuste le chunk size en
|
||||
consequence. Les positions des quads sont multipliees par `2^LOD` pour rester en
|
||||
coordonnees monde.
|
||||
|
||||
### Phase C : Generation des skirts
|
||||
|
||||
**Principe :** quand un chunk a un LOD inferieur (moins detaille) qu'un voisin, des gaps
|
||||
apparaissent a la frontiere. On genere une "jupe" de geometrie supplementaire pour
|
||||
les masquer.
|
||||
|
||||
**Implementation :**
|
||||
|
||||
1. Pour chaque face de chunk adjacente a un chunk de LOD superieur :
|
||||
- Ajouter une couche supplementaire de voxels dupliques depuis le voisin haute-res
|
||||
- Mesher normalement cette couche (elle s'etend legerement au-dela du chunk)
|
||||
|
||||
2. Taguer les vertices de skirt dans le vertex data (1 bit dans les flags)
|
||||
|
||||
3. Dans le VS, appliquer un **depth bias** aux vertices de skirt :
|
||||
```hlsl
|
||||
if (isSkirt) {
|
||||
// Pousse le skirt legerement derriere la surface
|
||||
output.position.z += 0.0001; // en clip space (reverse-Z: vers le far)
|
||||
}
|
||||
```
|
||||
Le skirt n'est visible que la ou il y a un gap, car la geometrie normale le
|
||||
masque partout ailleurs grace au depth test.
|
||||
|
||||
### Phase D : Integration rendu
|
||||
|
||||
**Buffers :** les skirts sont inclus dans le meme mega-buffer de quads, tagges par un bit.
|
||||
Pas de draw call supplementaire.
|
||||
|
||||
**Compute cull :** le compute shader de culling (`voxelCullCS`) recoit le LOD par chunk
|
||||
dans le GPUChunkInfo. Les chunks LOD > 0 ont moins de quads, donc moins de vertices
|
||||
a traiter.
|
||||
|
||||
**RT :** les BLAS sont construits par chunk. Les chunks LOD > 0 ont des BLAS plus petits.
|
||||
Le TLAS reste identique.
|
||||
|
||||
### Phase E : Smooth LOD specifique
|
||||
|
||||
Pour les chunks smooth (Surface Nets), le LOD est plus delicat :
|
||||
- Le mesh smooth LOD 1 (16^3) a des triangles 2x plus grands
|
||||
- Les normales sont moins precises
|
||||
- La deformation par materiau (plan-vertex-deformation.md) doit rester coherente
|
||||
|
||||
**Approche :** mesher smooth sur le mip correspondant. Les skirts smooth sont generes
|
||||
de la meme facon (couche supplementaire). La coherence visuelle est acceptable car
|
||||
le smooth est deja "flou" par nature.
|
||||
|
||||
## Estimation d'effort
|
||||
|
||||
| Phase | Effort | Dependance |
|
||||
|-------|--------|------------|
|
||||
| A. Mip pyramid | 4h | Aucune |
|
||||
| B. Selection LOD | 2h | A |
|
||||
| C. Skirts blocky | 4h | B |
|
||||
| D. Integration rendu | 3h | C |
|
||||
| E. Smooth LOD | 4h | B + Phase 5.1 |
|
||||
| **Total** | **~17h** | |
|
||||
|
||||
## Risques
|
||||
|
||||
- **Popping** : le changement de LOD est visible si les distances sont trop proches.
|
||||
Solution : cross-fade ou hysteresis (changer de LOD a dist+10% pour eviter l'oscillation).
|
||||
- **Skirt artifacts** : si le depth bias est trop grand, les skirts sont visibles comme
|
||||
des ombres. Tuner le bias par LOD level.
|
||||
- **Meshing cache** : les mips LOD > 0 changent moins souvent. Cacher le mesh par LOD
|
||||
level et ne re-mesher que quand le mip change.
|
||||
|
||||
## References
|
||||
|
||||
- Roblox SIGGRAPH 2020, p.34-38 (skirts + depth bias)
|
||||
- Transvoxel (Eric Lengyel) : https://transvoxel.org/
|
||||
- 0fps LOD for blocky voxels : https://0fps.net/2018/03/03/a-level-of-detail-method-for-blocky-voxels/
|
||||
- Nick Gildea, Dual Contouring seams : http://ngildea.blogspot.com/2014/09/dual-contouring-chunked-terrain.html
|
||||
222
docs/plan-stylized-textures.md
Normal file
222
docs/plan-stylized-textures.md
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
# Textures stylisees reelles + quilting
|
||||
|
||||
Passer des couleurs procedurales a de vraies textures hand-painted dans un style
|
||||
Wonderbox / Enshrouded. Inclut la technique de quilting Roblox comme optimisation.
|
||||
|
||||
## Etat actuel
|
||||
|
||||
- `textureArray_` : 5 layers 256x256 generees proceduralement (bruit + couleur unie)
|
||||
- `MaterialDesc` : champs `albedoTextureIndex`, `normalTextureIndex`, `heightmapTextureIndex`
|
||||
deja presents mais pointent vers des textures generees
|
||||
- Triplanar mapping : fonctionnel dans `voxelPS.hlsl` (blocky) et `voxelSmoothPS.hlsl`
|
||||
- Height-based blending : fonctionnel (Phase 3), winner-takes-all + corner attenuation
|
||||
- `sampler_` : deja cree, lineaire avec wrap
|
||||
|
||||
L'infrastructure est prete, il manque les textures et l'integration.
|
||||
|
||||
## Plan d'implementation
|
||||
|
||||
### Phase A : Preparer les textures (art)
|
||||
|
||||
**Format cible par materiau :**
|
||||
|
||||
| Texture | Format | Contenu | Taille |
|
||||
|---------|--------|---------|--------|
|
||||
| Albedo | RGBA8 | RGB = couleur, A = heightmap | 512x512 |
|
||||
| Normal | RG8 | Normal map tangent-space (BC5) | 512x512 |
|
||||
|
||||
La heightmap dans le canal alpha de l'albedo est la convention Roblox et evite une
|
||||
texture separee. Le height-based blending lit deja un canal height.
|
||||
|
||||
**Materiaux a creer (6) :**
|
||||
|
||||
1. **Grass** : herbe hand-painted, brins visibles, height map avec pointes hautes
|
||||
2. **Dirt** : terre seche, crevasses, height map irreguliere
|
||||
3. **Stone** : pierre grise, fissures, height map avec aretes saillantes
|
||||
4. **Sand** : sable fin, ondulations, height map douce
|
||||
5. **Snow** : neige poudreuse, surface quasi-plate, height map tres lisse
|
||||
6. **Smoothstone** : pierre polie, veines subtiles
|
||||
|
||||
**Sources de textures stylisees (libres ou a creer) :**
|
||||
- Polyhaven (CC0, PBR) : redessiner par-dessus pour le style hand-painted
|
||||
- Ambientcg (CC0) : bases realistes a simplifier
|
||||
- Creer from scratch dans Krita/Aseprite en 512x512
|
||||
|
||||
### Phase B : Charger les textures dans le texture array
|
||||
|
||||
**Fichier :** `VoxelRenderer.cpp`, remplacer `generateTextures()`.
|
||||
|
||||
```cpp
|
||||
void VoxelRenderer::loadTextures() {
|
||||
// Charger chaque materiau depuis des fichiers PNG/DDS
|
||||
const char* albedoPaths[] = {
|
||||
"Content/voxel/grass_albedo.png",
|
||||
"Content/voxel/dirt_albedo.png",
|
||||
"Content/voxel/stone_albedo.png",
|
||||
"Content/voxel/sand_albedo.png",
|
||||
"Content/voxel/snow_albedo.png",
|
||||
"Content/voxel/smoothstone_albedo.png",
|
||||
};
|
||||
|
||||
// Creer un texture array 512x512 x N layers
|
||||
TextureDesc desc;
|
||||
desc.width = 512;
|
||||
desc.height = 512;
|
||||
desc.arraySize = NUM_MATERIALS;
|
||||
desc.format = Format::R8G8B8A8_UNORM;
|
||||
desc.bindFlags = BindFlag::SHADER_RESOURCE;
|
||||
desc.mipLevels = 0; // auto mip generation
|
||||
|
||||
// Charger chaque layer via wi::helper::loadTextureFromFile
|
||||
// puis copier dans le texture array via CopyTexture
|
||||
}
|
||||
```
|
||||
|
||||
**Wicked Engine helper :** `wi::resourcemanager::Load()` charge PNG/DDS et genere
|
||||
les mips automatiquement. On peut aussi utiliser `wi::helper::CreateTexture()` avec
|
||||
des donnees brutes.
|
||||
|
||||
**Post-build :** ajouter une copie des textures dans CMakeLists.txt :
|
||||
```cmake
|
||||
add_custom_command(TARGET BVLEVoxels POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/assets/voxel
|
||||
$<TARGET_FILE_DIR:BVLEVoxels>/Content/voxel
|
||||
)
|
||||
```
|
||||
|
||||
### Phase C : Adapter les shaders
|
||||
|
||||
**Changements dans `voxelPS.hlsl` (blocky) :**
|
||||
|
||||
Le shader utilise deja `materialTextures` (texture array) et `triplanarSample()`.
|
||||
Modifications :
|
||||
|
||||
```hlsl
|
||||
// Actuel : couleur procedurale + texture subtile
|
||||
float3 color = materialColor * texSample.rgb;
|
||||
|
||||
// Nouveau : texture directe, la couleur vient de la texture
|
||||
float3 albedo = triplanarSample(materialTextures, worldPos, normal, matIndex).rgb;
|
||||
float height = triplanarSample(materialTextures, worldPos, normal, matIndex).a;
|
||||
```
|
||||
|
||||
Le height est utilise pour le blending inter-materiaux (deja en place).
|
||||
|
||||
**Changements dans `voxelSmoothPS.hlsl` :**
|
||||
|
||||
Identique. Le triplanar est deja en place, juste remplacer la source de couleur.
|
||||
|
||||
### Phase D : Detiling (anti-repetition)
|
||||
|
||||
Probleme : le triplanar avec des textures 512x512 montre de la repetition visible
|
||||
tous les 16 voxels (si tiling = 1 texel/voxel).
|
||||
|
||||
**Technique Roblox (p.25) :** rotation + shift pseudo-random par vertex.
|
||||
|
||||
```hlsl
|
||||
// Dans le VS, calculer un seed de detiling a partir de la position
|
||||
uint detileSeed = hash(uint3(floor(worldPos)));
|
||||
|
||||
// Dans le PS, appliquer une rotation/shift aux UVs
|
||||
float angle = (detileSeed & 0x3) * (3.14159 / 2.0); // 0, 90, 180, 270 degres
|
||||
float2 rotatedUV = rotate2D(uv, angle);
|
||||
float2 shiftedUV = rotatedUV + float2(
|
||||
((detileSeed >> 2) & 0xF) / 16.0,
|
||||
((detileSeed >> 6) & 0xF) / 16.0
|
||||
);
|
||||
```
|
||||
|
||||
Cela casse la repetition sans ajouter de samples supplementaires.
|
||||
Le seed est passe du VS au PS via un interpolant (1 uint).
|
||||
|
||||
**Alternative plus simple :** varier le tiling scale par axe triplanar (1.0, 0.97, 1.03).
|
||||
Casse deja pas mal la repetition pour un cout quasi nul.
|
||||
|
||||
### Phase E : Quilting (optimisation optionnelle)
|
||||
|
||||
Si le triplanar (3 fetches par texture * N textures) devient un bottleneck :
|
||||
|
||||
**Technique Roblox (p.22) :** choisir UN plan de projection par vertex parmi 18 plans
|
||||
(6 axes * 3 rotations de 30 degres). Encode le plan ID dans le vertex data (5 bits).
|
||||
|
||||
```
|
||||
18 plans = 6 faces * 3 rotations :
|
||||
+X face : 0deg, 30deg, 60deg
|
||||
-X face : 0deg, 30deg, 60deg
|
||||
+Y face : ...
|
||||
...
|
||||
```
|
||||
|
||||
Le PS n'echantillonne qu'une seule fois par materiau au lieu de 3 (triplanar).
|
||||
Reduction : de 9 fetches (3 materiaux * 3 axes) a 3 fetches (3 materiaux * 1 plan).
|
||||
|
||||
**Pour le blocky :** pas necessaire. Les quads ont une face unique, le triplanar est
|
||||
deja reduit a 1 axe dominant. Le quilting n'apporte rien.
|
||||
|
||||
**Pour le smooth :** potentiellement utile si on blend 3+ materiaux. A mesurer d'abord
|
||||
si le triplanar est reellement un bottleneck (peu probable avec 6 materiaux).
|
||||
|
||||
**Verdict :** reporter le quilting apres avoir mesure. Le triplanar standard devrait
|
||||
suffire avec notre nombre de materiaux.
|
||||
|
||||
### Phase F : Normal mapping
|
||||
|
||||
Ajouter une deuxieme texture array pour les normal maps (ou un 2eme set de layers).
|
||||
|
||||
```hlsl
|
||||
// Triplanar normal map sampling
|
||||
float3 normalMap = triplanarSampleNormal(normalTextures, worldPos, geometricNormal, matIndex);
|
||||
// Perturber la normale geometrique
|
||||
float3 finalNormal = normalize(geometricNormal + normalMap * normalStrength);
|
||||
```
|
||||
|
||||
Le triplanar normal mapping necessite de reconstruire le TBN par axe de projection.
|
||||
C'est un calcul supplementaire mais classique.
|
||||
|
||||
**Approche simplifiee :** pour un style hand-painted, les normal maps ne sont pas
|
||||
obligatoires. L'albedo porte la majeure partie du detail visuel. A evaluer
|
||||
visuellement avant d'investir du temps.
|
||||
|
||||
## Structure des assets
|
||||
|
||||
```
|
||||
assets/
|
||||
voxel/
|
||||
grass_albedo.png # RGBA : RGB=couleur, A=heightmap
|
||||
dirt_albedo.png
|
||||
stone_albedo.png
|
||||
sand_albedo.png
|
||||
snow_albedo.png
|
||||
smoothstone_albedo.png
|
||||
# (optionnel) grass_normal.png, etc.
|
||||
```
|
||||
|
||||
## Estimation d'effort
|
||||
|
||||
| Phase | Effort | Dependance |
|
||||
|-------|--------|------------|
|
||||
| A. Creer textures (art) | 4-8h | Aucune (parallelisable) |
|
||||
| B. Loader texture array | 3h | A |
|
||||
| C. Adapter shaders | 2h | B |
|
||||
| D. Detiling | 2h | C |
|
||||
| E. Quilting | 4h | C (optionnel) |
|
||||
| F. Normal maps | 3h | C (optionnel) |
|
||||
| **Total minimum** | **~11h** | A+B+C+D |
|
||||
|
||||
## Risques
|
||||
|
||||
- **Style incoherent** : les textures doivent toutes avoir le meme style hand-painted.
|
||||
Mieux vaut commencer par 2 materiaux (grass+stone) et valider le look avant de
|
||||
faire les 6.
|
||||
- **Mip bleeding** : dans un texture array, les mips peuvent bleed entre layers.
|
||||
Solution : padding 4px autour de chaque texture, ou utiliser des formats compresses
|
||||
(BC7) avec mips explicites.
|
||||
- **Tiling visible** : le detiling resout ca, mais necessitee un tuning par materiau.
|
||||
Les textures doivent etre tileable de base.
|
||||
|
||||
## References
|
||||
|
||||
- Roblox SIGGRAPH 2020, p.21-29 (quilting, detiling, height-based blend)
|
||||
- DreamCat Games, Smooth Voxel Mapping : https://bonsairobo.medium.com/smooth-voxel-mapping-a-technical-deep-dive-on-real-time-surface-nets-and-texturing-ef06d0f8ca14
|
||||
- Real-time Image Quilting (Hugh Malan, SIGGRAPH 2011)
|
||||
140
docs/plan-vertex-deformation.md
Normal file
140
docs/plan-vertex-deformation.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# Deformation de vertices par materiau
|
||||
|
||||
Inspiree du talk Roblox SIGGRAPH 2020 (p.19). Chaque materiau definit une deformation
|
||||
procedurale appliquee aux vertices Surface Nets apres le calcul du centroide.
|
||||
Donne un caractere visuel distinct a chaque materiau sans cout GPU supplementaire.
|
||||
|
||||
## Objectif
|
||||
|
||||
Actuellement, tous les materiaux smooth produisent les memes blobs lisses uniformes.
|
||||
Avec la deformation par materiau :
|
||||
- La **pierre** aurait des aretes plus marquees (cubify)
|
||||
- Le **sable** aurait des surfaces ondulees (shift)
|
||||
- La **neige** resterait lisse (aucune deformation)
|
||||
- La **glace** aurait des facettes cristallines (quantize)
|
||||
|
||||
## Modes de deformation (Roblox)
|
||||
|
||||
| Mode | Effet | Formule | Materiaux cibles |
|
||||
|------|-------|---------|-----------------|
|
||||
| `None` | Aucune deformation | identity | snow, water |
|
||||
| `Shift` | Offset pseudo-random | `pos += hash(pos) * amplitude` | sand, dirt |
|
||||
| `Cubify` | Lerp vers centre du cube | `pos = lerp(pos, round(pos) + 0.5, factor)` | stone, rock |
|
||||
| `Quantize` | Arrondi a pas fixe | `pos = round(pos * K) / K` | ice, crystal |
|
||||
| `Barrel` | Cubify uniquement en Y | `pos.y = lerp(pos.y, round(pos.y) + 0.5, f)` | pillars, trunks |
|
||||
|
||||
## Integration dans le code existant
|
||||
|
||||
### 1. Etendre MaterialDesc (VoxelTypes.h)
|
||||
|
||||
```cpp
|
||||
struct MaterialDesc {
|
||||
// ... champs existants ...
|
||||
uint8_t deformMode = 0; // 0=None, 1=Shift, 2=Cubify, 3=Quantize, 4=Barrel
|
||||
uint8_t deformStrength = 0; // 0-255 -> 0.0-1.0
|
||||
// remplace _pad ou ajoute 2 bytes (struct reste 16-aligned)
|
||||
};
|
||||
```
|
||||
|
||||
Pas de changement GPU : la deformation est CPU-only dans le mesher.
|
||||
|
||||
### 2. Modifier SmoothMesher (VoxelMesher.cpp)
|
||||
|
||||
Le point d'insertion est apres le calcul du centroide, avant l'ecriture dans le buffer
|
||||
de sortie. Actuellement dans `meshSurfaceNets()` :
|
||||
|
||||
```
|
||||
centroid = average(edge_crossings)
|
||||
normal = average(triangle_normals)
|
||||
-> INSERER DEFORMATION ICI
|
||||
write SmoothVertex(centroid, normal, material)
|
||||
```
|
||||
|
||||
Implementation :
|
||||
|
||||
```cpp
|
||||
// Apres le calcul du centroid et avant l'ecriture du vertex
|
||||
XMFLOAT3 deformVertex(XMFLOAT3 pos, const MaterialDesc& mat) {
|
||||
switch (mat.deformMode) {
|
||||
case 1: { // Shift
|
||||
float strength = mat.deformStrength / 255.0f;
|
||||
// Hash stable base sur position entiere (pas de flicker en animation)
|
||||
uint32_t h = hash3(int(pos.x), int(pos.y), int(pos.z));
|
||||
float rx = ((h & 0xFF) / 255.0f - 0.5f) * strength;
|
||||
float ry = (((h >> 8) & 0xFF) / 255.0f - 0.5f) * strength;
|
||||
float rz = (((h >> 16) & 0xFF) / 255.0f - 0.5f) * strength;
|
||||
return { pos.x + rx, pos.y + ry, pos.z + rz };
|
||||
}
|
||||
case 2: { // Cubify
|
||||
float f = mat.deformStrength / 255.0f;
|
||||
float cx = floorf(pos.x) + 0.5f;
|
||||
float cy = floorf(pos.y) + 0.5f;
|
||||
float cz = floorf(pos.z) + 0.5f;
|
||||
return { lerp(pos.x, cx, f), lerp(pos.y, cy, f), lerp(pos.z, cz, f) };
|
||||
}
|
||||
case 3: { // Quantize
|
||||
float K = 2.0f + (mat.deformStrength / 255.0f) * 6.0f; // 2-8 steps
|
||||
return { roundf(pos.x * K) / K, roundf(pos.y * K) / K, roundf(pos.z * K) / K };
|
||||
}
|
||||
case 4: { // Barrel (cubify Y only)
|
||||
float f = mat.deformStrength / 255.0f;
|
||||
float cy = floorf(pos.y) + 0.5f;
|
||||
return { pos.x, lerp(pos.y, cy, f), pos.z };
|
||||
}
|
||||
default: return pos;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Recalculer les normales apres deformation
|
||||
|
||||
Les normales moyennees doivent etre recalculees APRES la deformation, sinon elles ne
|
||||
correspondent plus a la geometrie deformee. Deux options :
|
||||
|
||||
**Option A (simple) :** Recalculer les face normals des triangles adjacents apres deformation.
|
||||
C'est ce que fait deja le pass de normales dans `meshSurfaceNets()`, il suffit de le
|
||||
deplacer apres la deformation.
|
||||
|
||||
**Option B (rapide) :** Garder les normales originales. La deformation est subtile,
|
||||
l'erreur de normale est visuellement acceptable. Recommande pour le prototype.
|
||||
|
||||
### 4. Soft/hard edges par materiau
|
||||
|
||||
Roblox controle aussi les aretes douces/dures par materiau. On peut ajouter :
|
||||
|
||||
```cpp
|
||||
uint8_t edgeHardness = 0; // 0=smooth normals, 255=flat/geometric normals
|
||||
```
|
||||
|
||||
Dans le PS, interpoler entre les smooth normals et les geometric normals (deja
|
||||
disponibles via le triplanar). Cout zero cote mesher, petit calcul PS.
|
||||
|
||||
### 5. Configurer les materiaux existants
|
||||
|
||||
```cpp
|
||||
// Dans VoxelWorld::initMaterials() ou equivalent
|
||||
materials[5].deformMode = 0; materials[5].deformStrength = 0; // snow: lisse
|
||||
materials[3].deformMode = 2; materials[3].deformStrength = 180; // stone: cubify fort
|
||||
materials[6].deformMode = 2; materials[6].deformStrength = 100; // smoothstone: cubify leger
|
||||
materials[4].deformMode = 1; materials[4].deformStrength = 60; // sand: shift subtil
|
||||
materials[2].deformMode = 1; materials[2].deformStrength = 30; // dirt: shift tres leger
|
||||
```
|
||||
|
||||
## Risques et precautions
|
||||
|
||||
- **Self-intersection** : une deformation trop forte peut creer des triangles inverses.
|
||||
Limiter `deformStrength` a ~200 max et verifier visuellement.
|
||||
- **Coutures chunk** : la deformation doit etre identique des deux cotes d'une frontiere
|
||||
de chunk. Le hash base sur la position monde (pas locale) garantit la coherence.
|
||||
- **Animation** : en mode animation (terrain regenere a 30Hz), la deformation doit etre
|
||||
stable. Utiliser la position entiere (pas le centroide) comme seed du hash.
|
||||
|
||||
## Estimation d'effort
|
||||
|
||||
- Etendre MaterialDesc : 15 min
|
||||
- Fonction deformVertex : 30 min
|
||||
- Integration dans meshSurfaceNets : 30 min
|
||||
- Tuning des parametres par materiau : 1h
|
||||
- **Total : ~2h**
|
||||
|
||||
Aucun changement shader, aucun changement GPU buffer, aucun impact performance.
|
||||
94
docs/plan.md
Normal file
94
docs/plan.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# BVLE Voxels - Plan de travail
|
||||
|
||||
Fonctionnalites restantes et idees d'evolution, organisees par sujet.
|
||||
Chaque sujet a un document d'implementation detaille dans `docs/`.
|
||||
|
||||
L'etat actuel du prototype est documente dans `CLAUDE.md` a la racine.
|
||||
|
||||
---
|
||||
|
||||
## Sujets restants de la specification originale
|
||||
|
||||
### 1. GPU Compute Surface Nets (Phase 5.3) ✅
|
||||
|
||||
Le mesher smooth fonctionne en GPU compute (2-pass: centroid CS + mesh CS).
|
||||
Auto-bascule GPU/CPU. Shaders: `voxelSmoothCentroidCS.hlsl`, `voxelSmoothCS.hlsl`.
|
||||
|
||||
**Statut :** Termine.
|
||||
|
||||
### 2. LOD multi-resolution (Phase 5.4)
|
||||
|
||||
LOD 1 implementé : chunks 32³ couvrant 64³ world space, lodScale dans GPUChunkInfo,
|
||||
VS multiplie localPos par lodScale. LOD 0 radius=6, LOD 1 ring radius=12 (480 chunks).
|
||||
Pas de smooth/topings sur LOD 1.
|
||||
|
||||
Reste a faire : LOD 2+, skirts pour cacher les coutures, fog aux bords, LOD dynamique.
|
||||
|
||||
**Statut :** En cours. Voir `docs/plan-lod-skirts.md`.
|
||||
|
||||
### 3. Fallback Shadow Maps + SSAO (Phase 6.4)
|
||||
|
||||
Le ray tracing est obligatoire pour les ombres/AO. Les GPU sans RT (ou les configs
|
||||
faibles) n'ont aucun eclairage directionnel. Le fallback devrait utiliser le pipeline
|
||||
existant de Wicked Engine.
|
||||
|
||||
**Statut :** Non commence. Priorite basse (tous les GPU cibles supportent RT).
|
||||
|
||||
### 4. Connected Blocks / Tuyaux (idee spec)
|
||||
|
||||
Blocs contenant des modeles 3D customs avec jointure dynamique selon les voisins
|
||||
identiques. Exemple : tuyaux qui se connectent automatiquement. Extension du systeme
|
||||
de topings avec bitmask 6-faces au lieu de 4-adjacence.
|
||||
|
||||
**Statut :** Concept uniquement.
|
||||
|
||||
---
|
||||
|
||||
## Nouveaux sujets (inspires du talk Roblox SIGGRAPH 2020)
|
||||
|
||||
### 5. Deformation de vertices par materiau
|
||||
|
||||
Roblox definit des deformations procedurales par materiau sur les vertices Surface Nets :
|
||||
shift (offset random), cubify (lerp vers centre cube), quantize (arrondi a 1/K),
|
||||
barrel (cubify en Y), soft/hard edges. Donne du caractere visuel a chaque materiau
|
||||
sans cout GPU.
|
||||
|
||||
**Statut :** Non commence. Voir `docs/plan-vertex-deformation.md`.
|
||||
|
||||
### 6. LOD avec skirts
|
||||
|
||||
Roblox utilise un mip pyramid par chunk + octree LOD. Les coutures entre niveaux LOD
|
||||
sont resolues par des "skirts" (triangles de debordement + depth bias) au lieu du
|
||||
stitching Transvoxel, qui est complexe. Solution elegante et simple.
|
||||
|
||||
**Statut :** Non commence. Voir `docs/plan-lod-skirts.md`.
|
||||
|
||||
### 7. Textures stylisees reelles
|
||||
|
||||
Passer des couleurs procedurales actuelles a de vraies textures (albedo + heightmap +
|
||||
normal) dans un texture array. Triplanar mapping ameliore avec detiling (rotation/shift
|
||||
par vertex a la Roblox). Height-based blending deja en place cote shader.
|
||||
|
||||
**Statut :** Infrastructure presente (texture array 5 layers, triplanar, height blend),
|
||||
mais textures generees proceduralement. Voir `docs/plan-stylized-textures.md`.
|
||||
|
||||
### 8. Texture quilting (Roblox)
|
||||
|
||||
Alternative au triplanar : 1 plan de projection parmi 18 par vertex, encode dans le
|
||||
vertex data. Reduit les fetches de 9-27 a 3. Technique a integrer dans le sujet
|
||||
textures si le triplanar devient un bottleneck.
|
||||
|
||||
**Statut :** Non commence. Integre dans `docs/plan-stylized-textures.md`.
|
||||
|
||||
---
|
||||
|
||||
## Priorites suggerees
|
||||
|
||||
| Priorite | Sujet | Impact | Effort |
|
||||
|----------|-------|--------|--------|
|
||||
| 1 | Textures stylisees reelles | Visuel majeur | Moyen |
|
||||
| 2 | Deformation vertices/materiau | Visuel fort, cout nul | Faible |
|
||||
| 3 | LOD avec skirts | Scalabilite | Moyen-eleve |
|
||||
| 4 | GPU Surface Nets | Performance smooth | Moyen |
|
||||
| 5 | Fallback SM+SSAO | Compatibilite | Faible |
|
||||
| 6 | Connected blocks | Gameplay | Eleve |
|
||||
|
|
@ -208,9 +208,10 @@ int APIENTRY wWinMain(
|
|||
if (renderPath.screenshotMode) {
|
||||
struct CamView { float x, y, z, pitch, yaw; const char* name; };
|
||||
static const CamView views[] = {
|
||||
{ 223.f, 36.5f, 261.f, -0.20f, 0.7f, "closeup" }, // close-up: slightly above grass, looking across
|
||||
{ 222.5f, 36.2f, 261.f, -0.10f, 0.5f,"blade" }, // eye-level with grass blades
|
||||
{ 220.f, 39.f, 258.f, -0.35f, 0.7f, "medium" }, // medium shot of grass patch
|
||||
{ 220.f, 42.f, 258.f, -0.40f, 0.7f, "landscape" }, // higher overview
|
||||
{ 220.f, 39.f, 258.f, -0.35f, 0.7f, "medium" }, // medium shot, terrain detail
|
||||
{ 222.f, 37.f, 260.f, -0.20f, 0.5f, "closeup" }, // close-up ground level
|
||||
{ 220.f, 120.f, 258.f, 1.0f, 0.7f, "birdseye" }, // bird's eye (LOD overview)
|
||||
};
|
||||
static const int numViews = sizeof(views) / sizeof(views[0]);
|
||||
static int currentView = 0;
|
||||
|
|
|
|||
|
|
@ -324,121 +324,7 @@ Le VoxelRenderer s'insère dans le render path de Wicked via des hooks dans le R
|
|||
|
||||
J'aimerais tester quelque chose, c'est un nouveau type de block qui ne contient que des modèles 3D customs et qui aurait des comportements de jointure dynamique selon les blocs voisins identiques. Spécifiquement, j'aimerais créer des tuyaux qui se connectent les uns aux autres ou créent des nouvelles connexions pour toujours toucher les blocks tuyaux voisin.
|
||||
|
||||
Le ciel te plaît — parfait ! On continue vers Wonderbox. Qu'est-ce que tu voudrais améliorer ensuite ? En comparant avec la ref, je vois plusieurs pistes :
|
||||
|
||||
Couleurs plus saturées/profondes — le vert de l'herbe Wonderbox est plus riche et profond
|
||||
Target wonderbox
|
||||
Fog atmosphérique — la brume chaude au loin qui fond le terrain dans le ciel
|
||||
Ombres plus marquées — le contraste ombre/lumière est plus prononcé dans Wonderbox
|
||||
Faces latérales des blocs — plus texturées/détaillées dans Wonderbox
|
||||
|
||||
Remaining (deferred):
|
||||
|
||||
DeferredGPUBuffer utility extraction — moderate refactor, 3 instances, lower priority
|
||||
VoxelRTManager extraction — significant refactor, deferred
|
||||
Toping instance sort+pack parallelization — P3
|
||||
Voici mon analyse complète et mes propositions.
|
||||
|
||||
## 1. Gestion mémoire : état des lieux et clarification
|
||||
|
||||
**Pattern actuel (répété 5× dans le code) :**
|
||||
```
|
||||
uploadXxxData() {
|
||||
staging.resize(count); // CPU staging vector
|
||||
/* fill staging */
|
||||
if (capacity < count) {
|
||||
capacity = count * 1.25;
|
||||
CreateBuffer(nullptr); // GPU buffer, no data
|
||||
}
|
||||
dirty = true; // flag for Render()
|
||||
}
|
||||
|
||||
Render() {
|
||||
if (dirty) {
|
||||
UpdateBuffer(staging); // actual GPU upload
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problèmes :**
|
||||
- Pattern dupliqué pour : `topingInstanceBuffer_`, `topingBLASPositionBuffer_`, `smoothVertexBuffer_`, et les 3 BLAS + TLAS
|
||||
- Chaque duplication a engendré des bugs (le crash `memmove`, la fuite VRAM BLAS, les ombres figées)
|
||||
- Les dirty flags sont dispersés (`topingInstanceDirty_`, `smoothVertexDirty_`, `topingBLASDirty_`, `rtDirty_`) avec des dépendances d'ordre non-évidentes (le BLAS upload doit précéder le BLAS build)
|
||||
- 15 membres `mutable` juste pour les flags + capacités
|
||||
|
||||
**Proposition :** Extraire un `DeferredGPUBuffer` encapsulant ce pattern :
|
||||
|
||||
```cpp
|
||||
struct DeferredGPUBuffer {
|
||||
GPUBuffer gpu;
|
||||
std::vector<uint8_t> staging;
|
||||
uint32_t count = 0;
|
||||
uint32_t capacity = 0;
|
||||
uint32_t stride = 0;
|
||||
bool dirty = false;
|
||||
|
||||
void prepare(uint32_t newCount, const void* data); // resize + fill + dirty=true
|
||||
void upload(GraphicsDevice* dev, CommandList cmd); // UpdateBuffer + dirty=false
|
||||
void ensureCapacity(GraphicsDevice* dev, uint32_t newCount, BindFlag flags);
|
||||
};
|
||||
```
|
||||
|
||||
Ça élimine ~50 lignes de boilerplate par buffer et centralise les invariants (capacity > count, create avec nullptr, upload avec taille réelle).
|
||||
|
||||
---
|
||||
|
||||
## 2. Performance : propositions sans régression fonctionnelle
|
||||
|
||||
### 2.2 — Paralléliser le tri + packing d'instances toping (~5ms → ~1ms)
|
||||
|
||||
Le `std::sort` sur 30K éléments et la copie dans `topingGpuInsts_` sont single-thread. Utiliser `wi::jobsystem` pour partitionner par type (2 types = 2 jobs), ou un counting sort (16 buckets par variant × 2 types = 32 buckets) qui est O(N) au lieu de O(N log N).
|
||||
|
||||
### 2.3 — Skip le BLAS rebuild quand seul le blocky change
|
||||
|
||||
Actuellement buildAccelerationStructures() rebuild les 3 BLAS + TLAS à chaque frame d'animation. Si seul le terrain blocky change (pas de vent/toping), le toping BLAS rebuild est inutile. Ajouter des dirty flags granulaires :
|
||||
|
||||
mutable bool blockyBLASDirty_ = false;
|
||||
mutable bool smoothBLASDirty_ = false;
|
||||
// topingBLASDirty_ existe déjà
|
||||
|
||||
## 3. Refactoring : axes de simplification
|
||||
|
||||
### 3.2 — Extraire le RT dans une classe dédiée
|
||||
|
||||
`VoxelRenderer` fait 2900+ lignes et mélange rendering, meshing, et ray tracing. Extraire un `VoxelRTManager` :
|
||||
|
||||
```cpp
|
||||
class VoxelRTManager {
|
||||
// BLAS/TLAS management, capacity tracking
|
||||
// dispatchBLASExtract(), buildAccelerationStructures()
|
||||
// dispatchShadows()
|
||||
// All RT-related state (rtAvailable_, rtDirty_, aoTextures_, etc.)
|
||||
};
|
||||
```
|
||||
|
||||
Ça isole les ~500 lignes de RT et ses 20+ membres, rendant le debugging plus ciblé.
|
||||
|
||||
### 3.3 — Unifier le pattern deferred upload
|
||||
|
||||
Comme décrit en §1, le `DeferredGPUBuffer` centralisé évite la duplication error-prone. Chaque bug rencontré (crash memmove, VRAM leak, ombres figées) vient d'une variation mal implémentée de ce même pattern.
|
||||
|
||||
### 3.4 — Simplifier `VoxelRenderPath`
|
||||
|
||||
`VoxelRenderPath` fait office de "god object" : caméra, input, animation, profiling, render targets, wind. Extraire :
|
||||
- Input/caméra → struct `CameraController`
|
||||
- Profiling → struct `VoxelProfiler` (déjà un bon candidat, les `ProfileAccum` sont isolables)
|
||||
- Animation → struct `AnimationState`
|
||||
|
||||
## Priorisation recommandée
|
||||
|
||||
| Priorité | Action | Impact perf | Effort |
|
||||
|----------|--------|-------------|--------|
|
||||
| **P1** | `DeferredGPUBuffer` (§3.3) | Prévention bugs | Moyen |
|
||||
| **P2** | Extraire RT dans classe (§3.2) | Maintenabilité | Moyen |
|
||||
| **P2** | Dirty flags granulaires BLAS (§2.3) | ~2-5ms/frame | Faible |
|
||||
| **P3** | Paralléliser tri toping (§2.2) | ~4ms | Faible |
|
||||
| **P3** | LOD topings en animation (§4.1) | Raster + BLAS | Moyen |
|
||||
|
||||
**Le P0 seul ramènerait le frame time de 232ms à ~35ms (~28 FPS), soit 6.5× mieux.** Combiné avec P2 dirty flags, on approche les 60 FPS cibles.
|
||||
|
||||
Dis-moi quelles priorités tu veux attaquer et dans quel ordre.
|
||||
Loading…
Add table
Reference in a new issue