bvle-voxels/docs/plan-vertex-deformation.md

5.4 KiB

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)

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 :

// 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 :

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

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