# 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` : champs `albedoTextureIndex`, `normalTextureIndex`, `heightmapTextureIndex` deja presents mais pointent vers des textures generees - Triplanar mapping : fonctionnel dans `voxelPS.hlsl` (blocky) et `voxelSmoothPS.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) :** 1. **Grass** : herbe hand-painted, brins visibles, height map avec pointes hautes 2. **Dirt** : terre seche, crevasses, height map irreguliere 3. **Stone** : pierre grise, fissures, height map avec aretes saillantes 4. **Sand** : sable fin, ondulations, height map douce 5. **Snow** : neige poudreuse, surface quasi-plate, height map tres lisse 6. **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()`. ```cpp 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 : ```cmake add_custom_command(TARGET BVLEVoxels POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/voxel $/Content/voxel ) ``` ### Phase C : Adapter les shaders **Changements dans `voxelPS.hlsl` (blocky) :** Le shader utilise deja `materialTextures` (texture array) et `triplanarSample()`. Modifications : ```hlsl // 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. ```hlsl // 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). ```hlsl // 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)