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
deformStrengtha ~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.