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

5.6 KiB

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

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)

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 :

    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