# 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
