bvle-voxels/docs/plan-lod-skirts.md

147 lines
5.6 KiB
Markdown
Raw Normal View History

2026-03-31 20:04:00 +02:00
# 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