Génération de textures procédurales
Source : room-at-dawn (MIT), issu de src/core/tex.js, rng.js, src/scene/materials.js et des fonctions de matériaux des pièces dans parts/.
Le projet original ne contient aucun fichier image dans la chambre ; toutes les textures sont générées par programme au chargement.
Idées clés :
- Conception en unités physiques. Les hauteurs sont en millimètres, la taille de la texture en mètres est précisée, les UV sont en mètres. Ainsi
repeat = 1 / tileMètres, et changer la résolution ne modifie pas l'aspect. - Le réalisme vient surtout de la rugosité, pas de la couleur. Les empreintes digitales, les traces d'essuyage, la poussière sont quasi invisibles en couleur ; ils ne se révèlent que sous les reflets spéculaires.
- Tout est répétable et reproductible. Les bruits ont une période, l'aléatoire utilise
RNG(seed). Même seed donne toujours le même résultat (indispensable pour le baking GI, les tests de régression).
Fichiers
| Chemin | Contenu |
|---|---|
scripts/tex.js | Fonctions de base : RNG, pnoise/pfbm (période par axe), worley, vn (bruit rapide par table), field, normalBytes (en mm), dataTex/canvasTex/packTex/colorTex, avgColor, once (cache), rep (image partagée, seul le repeat change), seamScore, texData |
scripts/surfaces.js | wallNormal + wallPatch (papier peint bois avec variations de plâtre et peinture à l'échelle 3,2 m), lacquerNormal, smudgeRough, dustRough, floorMaps, woodMaps, oakVeneer |
scripts/textiles.js | weaveNormal, waffleNormal, terryNormal, ribNormal, knitNormal, feltNormal, ropeNormal, fabricMaterial (sheen + translucidité à contre-jour) |
scripts/wear.js | wornMaps (surface usée complète : deux canvas couleur et rugosité), speckleTexture, scuffTexture, et pinceaux de base softBlob, strand, fingerprint, noiseTile, overlay |
scripts/print.js | word/phrase (mots factices générés par syllabes), PAL, packer, fitText, paperGrain, makeBook + bookAtlas + bookGeometry, pagesTexture, topoPoster, photoPrints |
example/ | Coin de pièce, chaque surface utilise un matériau de la bibliothèque. ?cam= (room, desk, glint, fabric, floor, wall, books) ; ?flat=1 pour un rendu en couleur unie |
example/preview.jpg | Capture d'écran de l'exemple |
references/recipes.md | Recettes pour chaque matériau : dimensions, paramètres, et comment en ajouter de nouveaux |
references/verify-engines.md | Méthodes de vérification, problèmes rencontrés, performances, et portage vers Godot/Unreal |
Lancer l'exemple
cd <dossier_skill> && python3 -m http.server 5422 --bind 127.0.0.1
# Ouvrir http://127.0.0.1:5422/example/ (window.__tex donne le temps de génération, le score de couture, l'albedo moyen de chaque texture)
(<dossier_skill> est le répertoire de base affiché lors du chargement du skill ; en tant que plugin, il se trouve sous ~/.claude/plugins/cache/)
Utilisation
import { rep } from './scripts/tex.js';
import { floorMaps, wallNormal, wallPatch, lacquerNormal } from './scripts/surfaces.js';
import { knitNormal, fabricMaterial } from './scripts/textiles.js';
import { wornMaps } from './scripts/wear.js';
const fl = floorMaps(); // UV en mètres ; repeat déjà défini
floor.material = new THREE.MeshStandardMaterial({ map: fl.map, normalMap: fl.normalMap, roughnessMap: fl.roughnessMap, roughness: 1 });
wall.material = wallPatch(new THREE.MeshStandardMaterial({ color: 0xebebe6, roughness: 0.93, normalMap: wallNormal() }));
door.material = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.6, normalMap: rep(lacquerNormal(), 9), normalScale: new THREE.Vector2(0.18, 0.18) });
sweater.material = fabricMaterial(0x6f7f6a, { normal: knitNormal(), tileM: 0.1 });
const w = wornMaps({ sizeM: [1.2, 0.6], hands: [[0.55, 0.5]], rings: [[0.9, 0.4, 0.032, 0.6]], prints: 6 });
deskTop.material = new THREE.MeshStandardMaterial({ map: w.map, roughnessMap: w.roughnessMap, roughness: 1 });
- Pour les matériaux avec
roughnessMap, définirroughnessà 1. three.js multiplie les deux. - La rugosité de
packTexet dewornMapsest dans le canal G ; un canvas en niveaux de gris peut aussi être utilisé directement. - Chaque matériau renvoie
avg(couleur moyenne linéaire) pourthreejs-baked-gicomme albédo de rebond lumineux.
Règles impératives
- Ne pas utiliser
Math.random(). Chaque texture doit avoir une seed fixe. - La période des bruits doit être un entier et alignée avec le nombre de tuiles. (ex.
pfbm(u*6, v*6, oct, seed, 6)). Sinon, il y a des coutures. UtiliserseamScorepour vérifier. - Les hauteurs sont en millimètres, utiliser
normalBytes(H, w, h, mmParTuile). Changer la résolution ne modifie pas l'intensité des reliefs ; certains matériaux ont des tuiles non carrées (sol 1,1 × 1,9 mm), il faut donner les deux axes séparément. - Dessiner la rugosité en même temps que la couleur. Chaque coup de pinceau de
wornMapsdessine simultanément sur les deux canvas, en utilisant le mêmenew RNG(i)pour aligner les formes. - Sur les surfaces sombres, réduire les débris et les points brillants (dans l'exemple, le plateau sombre passe de 9000 à 2500), sinon l'aspect devient granitique.
- Le texte et les motifs dessinés sur canvas doivent être générés par le code (
word(), formes géométriques). Ne pas utiliser de vrais noms de livres, marques ou logos. - La plus grande texture (2048²) prend 0,1 à 0,35 s à créer. La même texture est mise en cache avec
onceet partagée avecrep(). Ne pas générer une copie par objet.
Performances (RTX 4090, Chrome headless, exemple complet 1,1 s)
| Texture | Taille | Temps | Score de couture (x/y) |
|---|---|---|---|
floorMaps | 2048² × 3 | 0,32 s | couleur 0,96/1,10 ; normal 1,00/1,02 ; rugosité 0,89/1,05 |
woodMaps | 1024² × 3 | 0,20–0,23 s | couleur 0,93/1,12 ; normal 1,30/1,06 |
wornMaps (1,2 × 0,6 m, 3400 px/m) | 4080×2040 + rugosité moitié | 0,13 s | non répétable |
oakVeneer | 1024² × 3 | 0,10 s | 0,98–1,06 |
wallNormal | 1024² | 0,04 s | 1,00/1,07 |
lacquerNormal, smudgeRough | 512² | 0,02–0,04 s | 0,98–1,04 |
| 7 types de tissus | 128²–512² | 1–19 ms | 0,94–1,10 (corde coton y = 2,03, rainure intentionnelle, voir verify-engines.md) |
bookAtlas (12 livres) | 2048×1536 | 0,05–0,08 s | — |
Un score de couture proche de 1 signifie couture invisible ; au-dessus de 2, il faut vérifier. Le calcul ajoute 1 niveau de couleur au numérateur et au dénominateur pour éviter des ratios aberrants dans les directions où la texture a peu de variations (ex. bois filé droit).