7.9 KiB
Textures stylisees reelles + quilting
Passer des couleurs procedurales a de vraies textures hand-painted dans un style Wonderbox / Enshrouded. Inclut la technique de quilting Roblox comme optimisation.
Etat actuel
textureArray_: 5 layers 256x256 generees proceduralement (bruit + couleur unie)MaterialDesc: champsalbedoTextureIndex,normalTextureIndex,heightmapTextureIndexdeja presents mais pointent vers des textures generees- Triplanar mapping : fonctionnel dans
voxelPS.hlsl(blocky) etvoxelSmoothPS.hlsl - Height-based blending : fonctionnel (Phase 3), winner-takes-all + corner attenuation
sampler_: deja cree, lineaire avec wrap
L'infrastructure est prete, il manque les textures et l'integration.
Plan d'implementation
Phase A : Preparer les textures (art)
Format cible par materiau :
| Texture | Format | Contenu | Taille |
|---|---|---|---|
| Albedo | RGBA8 | RGB = couleur, A = heightmap | 512x512 |
| Normal | RG8 | Normal map tangent-space (BC5) | 512x512 |
La heightmap dans le canal alpha de l'albedo est la convention Roblox et evite une texture separee. Le height-based blending lit deja un canal height.
Materiaux a creer (6) :
- Grass : herbe hand-painted, brins visibles, height map avec pointes hautes
- Dirt : terre seche, crevasses, height map irreguliere
- Stone : pierre grise, fissures, height map avec aretes saillantes
- Sand : sable fin, ondulations, height map douce
- Snow : neige poudreuse, surface quasi-plate, height map tres lisse
- Smoothstone : pierre polie, veines subtiles
Sources de textures stylisees (libres ou a creer) :
- Polyhaven (CC0, PBR) : redessiner par-dessus pour le style hand-painted
- Ambientcg (CC0) : bases realistes a simplifier
- Creer from scratch dans Krita/Aseprite en 512x512
Phase B : Charger les textures dans le texture array
Fichier : VoxelRenderer.cpp, remplacer generateTextures().
void VoxelRenderer::loadTextures() {
// Charger chaque materiau depuis des fichiers PNG/DDS
const char* albedoPaths[] = {
"Content/voxel/grass_albedo.png",
"Content/voxel/dirt_albedo.png",
"Content/voxel/stone_albedo.png",
"Content/voxel/sand_albedo.png",
"Content/voxel/snow_albedo.png",
"Content/voxel/smoothstone_albedo.png",
};
// Creer un texture array 512x512 x N layers
TextureDesc desc;
desc.width = 512;
desc.height = 512;
desc.arraySize = NUM_MATERIALS;
desc.format = Format::R8G8B8A8_UNORM;
desc.bindFlags = BindFlag::SHADER_RESOURCE;
desc.mipLevels = 0; // auto mip generation
// Charger chaque layer via wi::helper::loadTextureFromFile
// puis copier dans le texture array via CopyTexture
}
Wicked Engine helper : wi::resourcemanager::Load() charge PNG/DDS et genere
les mips automatiquement. On peut aussi utiliser wi::helper::CreateTexture() avec
des donnees brutes.
Post-build : ajouter une copie des textures dans CMakeLists.txt :
add_custom_command(TARGET BVLEVoxels POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/voxel
$<TARGET_FILE_DIR:BVLEVoxels>/Content/voxel
)
Phase C : Adapter les shaders
Changements dans voxelPS.hlsl (blocky) :
Le shader utilise deja materialTextures (texture array) et triplanarSample().
Modifications :
// Actuel : couleur procedurale + texture subtile
float3 color = materialColor * texSample.rgb;
// Nouveau : texture directe, la couleur vient de la texture
float3 albedo = triplanarSample(materialTextures, worldPos, normal, matIndex).rgb;
float height = triplanarSample(materialTextures, worldPos, normal, matIndex).a;
Le height est utilise pour le blending inter-materiaux (deja en place).
Changements dans voxelSmoothPS.hlsl :
Identique. Le triplanar est deja en place, juste remplacer la source de couleur.
Phase D : Detiling (anti-repetition)
Probleme : le triplanar avec des textures 512x512 montre de la repetition visible tous les 16 voxels (si tiling = 1 texel/voxel).
Technique Roblox (p.25) : rotation + shift pseudo-random par vertex.
// Dans le VS, calculer un seed de detiling a partir de la position
uint detileSeed = hash(uint3(floor(worldPos)));
// Dans le PS, appliquer une rotation/shift aux UVs
float angle = (detileSeed & 0x3) * (3.14159 / 2.0); // 0, 90, 180, 270 degres
float2 rotatedUV = rotate2D(uv, angle);
float2 shiftedUV = rotatedUV + float2(
((detileSeed >> 2) & 0xF) / 16.0,
((detileSeed >> 6) & 0xF) / 16.0
);
Cela casse la repetition sans ajouter de samples supplementaires. Le seed est passe du VS au PS via un interpolant (1 uint).
Alternative plus simple : varier le tiling scale par axe triplanar (1.0, 0.97, 1.03). Casse deja pas mal la repetition pour un cout quasi nul.
Phase E : Quilting (optimisation optionnelle)
Si le triplanar (3 fetches par texture * N textures) devient un bottleneck :
Technique Roblox (p.22) : choisir UN plan de projection par vertex parmi 18 plans (6 axes * 3 rotations de 30 degres). Encode le plan ID dans le vertex data (5 bits).
18 plans = 6 faces * 3 rotations :
+X face : 0deg, 30deg, 60deg
-X face : 0deg, 30deg, 60deg
+Y face : ...
...
Le PS n'echantillonne qu'une seule fois par materiau au lieu de 3 (triplanar). Reduction : de 9 fetches (3 materiaux * 3 axes) a 3 fetches (3 materiaux * 1 plan).
Pour le blocky : pas necessaire. Les quads ont une face unique, le triplanar est deja reduit a 1 axe dominant. Le quilting n'apporte rien.
Pour le smooth : potentiellement utile si on blend 3+ materiaux. A mesurer d'abord si le triplanar est reellement un bottleneck (peu probable avec 6 materiaux).
Verdict : reporter le quilting apres avoir mesure. Le triplanar standard devrait suffire avec notre nombre de materiaux.
Phase F : Normal mapping
Ajouter une deuxieme texture array pour les normal maps (ou un 2eme set de layers).
// Triplanar normal map sampling
float3 normalMap = triplanarSampleNormal(normalTextures, worldPos, geometricNormal, matIndex);
// Perturber la normale geometrique
float3 finalNormal = normalize(geometricNormal + normalMap * normalStrength);
Le triplanar normal mapping necessite de reconstruire le TBN par axe de projection. C'est un calcul supplementaire mais classique.
Approche simplifiee : pour un style hand-painted, les normal maps ne sont pas obligatoires. L'albedo porte la majeure partie du detail visuel. A evaluer visuellement avant d'investir du temps.
Structure des assets
assets/
voxel/
grass_albedo.png # RGBA : RGB=couleur, A=heightmap
dirt_albedo.png
stone_albedo.png
sand_albedo.png
snow_albedo.png
smoothstone_albedo.png
# (optionnel) grass_normal.png, etc.
Estimation d'effort
| Phase | Effort | Dependance |
|---|---|---|
| A. Creer textures (art) | 4-8h | Aucune (parallelisable) |
| B. Loader texture array | 3h | A |
| C. Adapter shaders | 2h | B |
| D. Detiling | 2h | C |
| E. Quilting | 4h | C (optionnel) |
| F. Normal maps | 3h | C (optionnel) |
| Total minimum | ~11h | A+B+C+D |
Risques
- Style incoherent : les textures doivent toutes avoir le meme style hand-painted. Mieux vaut commencer par 2 materiaux (grass+stone) et valider le look avant de faire les 6.
- Mip bleeding : dans un texture array, les mips peuvent bleed entre layers. Solution : padding 4px autour de chaque texture, ou utiliser des formats compresses (BC7) avec mips explicites.
- Tiling visible : le detiling resout ca, mais necessitee un tuning par materiau. Les textures doivent etre tileable de base.
References
- Roblox SIGGRAPH 2020, p.21-29 (quilting, detiling, height-based blend)
- DreamCat Games, Smooth Voxel Mapping : https://bonsairobo.medium.com/smooth-voxel-mapping-a-technical-deep-dive-on-real-time-surface-nets-and-texturing-ef06d0f8ca14
- Real-time Image Quilting (Hugh Malan, SIGGRAPH 2011)