Commit plan and iteration instructions

This commit is contained in:
Samuel Bouchet 2026-03-31 20:04:00 +02:00
parent 8ab908054c
commit c2d1a1e0b6
7 changed files with 637 additions and 118 deletions

View file

@ -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
View 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

View 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)

View 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
View 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 |

View file

@ -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;

View file

@ -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.