bvle-voxels/docs/plan-stylized-textures.md

222 lines
7.9 KiB
Markdown

# 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
$<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 :
```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)