# 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, 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