Prozedurale Texturen in three.js
Quelle: room-at-dawn (MIT), entnommen aus src/core/tex.js, rng.js, src/scene/materials.js und den Materialfunktionen in parts/.
Das ursprüngliche Schlafzimmer enthält keine einzige Bilddatei – alle Texturen werden beim Laden prozedural generiert.
Kernideen:
- Physikalische Einheiten verwenden. Höhen in Millimetern, Texturgitter sind in Metern angegeben, UVs in Metern. Dadurch entspricht
repeat = 1 / tileMeterund eine Änderung der Auflösung ändert das Aussehen nicht. - Der Großteil der „Realitätsnähe“ liegt in der Rauheit, nicht in der Farbe. Fingerabdrücke, Wischspuren, Staub sind farblich kaum sichtbar – sie treten erst unter Glanzlicht auf.
- Immer wiederholbar und reproduzierbar. Rauschen hat Perioden, Zufallszahlen laufen über
RNG(seed). Derselbe Seed liefert jedes Mal dasselbe Ergebnis (wichtig für Light Baking und Screenshot-Regressionstests).
Dateien
| Pfad | Inhalt |
|---|---|
scripts/tex.js | Grundlagen: RNG, pnoise/pfbm (mit unterschiedlichen Perioden pro Achse), worley, vn (schnelles, nachgeschlagenes Rauschen), field, normalBytes (Millimeter-Einheiten), dataTex/canvasTex/packTex/colorTex, avgColor, once (Cache), rep (Textur wiederverwenden, nur Repeat ändern), seamScore, texData |
scripts/surfaces.js | wallNormal + wallPatch (Holzspan-Tapete mit 3,2 m Putz- und Farbvariation), lacquerNormal, smudgeRough, dustRough, floorMaps, woodMaps, oakVeneer |
scripts/textiles.js | weaveNormal, waffleNormal, terryNormal, ribNormal, knitNormal, feltNormal, ropeNormal, fabricMaterial (Sheen + Rückseitendurchscheinen) |
scripts/wear.js | wornMaps (eine ganze Oberfläche mit Gebrauchsspuren: Farb- und Rauheitskarte), speckleTexture, scuffTexture, sowie Basis-Pinsel softBlob, strand, fingerprint, noiseTile, overlay |
scripts/print.js | word/phrase (Platzhaltertext aus Silben), PAL, packer, fitText, paperGrain, makeBook + bookAtlas + bookGeometry, pagesTexture, topoPoster, photoPrints |
example/ | Eine Raumecke, jede Oberfläche verwendet eine Textur aus der Bibliothek. ?cam= (room, desk, glint, fabric, floor, wall, books); ?flat=1 wechselt zu Volltonfarbe |
example/preview.jpg | Beispiel-Screenshot |
references/recipes.md | Vorgehen, Abmessungen, Parameter jeder Textur und wie man neue hinzufügt |
references/verify-engines.md | Validierungsmethoden, aufgetretene Probleme, Leistung und Portierung nach Godot/Unreal |
Beispiel ausführen
cd <skill-ordner> && python3 -m http.server 5422 --bind 127.0.0.1
# Öffne http://127.0.0.1:5422/example/ (window.__tex enthält die Generierungszeit, den Naht-Score und die Durchschnittsfarbe jeder Textur)
(<skill-ordner> ist das Basisverzeichnis, das beim Laden dieses Skills angezeigt wird; als Plugin liegt es unter ~/.claude/plugins/cache/)
Verwendung
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(); // UVs in Metern; repeat ist bereits eingestellt
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 });
- Bei Materialien mit
roughnessMapmussroughnessauf 1 gesetzt werden. three.js multipliziert beide. - Die Rauheit von
packTexundwornMapsliegt im G-Kanal; Graustufen-Canvas können direkt verwendet werden. - Jede Textur gibt
avg(lineare Durchschnittsfarbe) zurück, zur Verwendung mitthreejs-baked-gials Albedo für indirektes Licht.
Wichtige Regeln
- Kein
Math.random()verwenden. Jede Textur benötigt einen festen Seed. - Die Periode des Rauschens muss ganzzahlig und mit der Texturgröße ausgerichtet sein (z. B.
pfbm(u*6, v*6, oct, seed, 6)). Ohne Ausrichtung entstehen Nähte. MitseamScoreprüfen. - Höhen in Millimetern, mit
normalBytes(H, w, h, mmProGitter). Bei Auflösungsänderung ändert sich die Stärke der Reliefdarstellung nicht; einige Materialien haben nicht-quadratische Gitter (Boden 1.1 × 1.9 mm), daher beide Achsen getrennt angeben. - Rauheit zusammen mit der Farbe rendern.
wornMapszeichnet jede Aktion gleichzeitig in zwei Canvas und verwendet denselbennew RNG(i), sodass die Formen übereinstimmen. - Bei dunklen Oberflächen Partikel und Glanzlichter reduzieren (im Beispiel dunkle Tischplatte von 9000 auf 2500), sonst wirkt es wie Granit.
- Auf Canvas gezeichnete Texte und Muster immer selbst erstellen (
word(), geometrische Formen); keine echten Buchtitel, Marken oder Logos. - Die größte Textur (2048²) benötigt 0,1–0,35 s; mit
oncecachen und mitrep()wiederverwenden. Nicht für jedes Objekt neu generieren.
Performance (RTX 4090, Chrome headless, gesamtes Beispiel 1,1 s)
| Textur | Größe | Zeit | Naht-Score (x/y) |
|---|---|---|---|
floorMaps | 2048² × 3 | 0,32 s | Farbe 0,96/1,10; Normal 1,00/1,02; Rauheit 0,89/1,05 |
woodMaps | 1024² × 3 | 0,20–0,23 s | Farbe 0,93/1,12; Normal 1,30/1,06 |
wornMaps (1,2 × 0,6 m, 3400 px/m) | 4080×2040 + halbe Rauheit | 0,13 s | Nicht wiederholt |
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 Stoffarten | 128²–512² | 1–19 ms | 0,94–1,10 (Kordel y = 2,03, absichtliche Rille, siehe verify-engines.md) |
bookAtlas (12 Bücher) | 2048×1536 | 0,05–0,08 s | — |
Ein Naht-Score von etwa 1 bedeutet keine sichtbare Naht. Werte über 2 erfordern eine Überprüfung. Bei der Berechnung wird auf Zähler und Nenner jeweils 1 Farbstufe addiert, um zu verhindern, dass nahezu rauschfreie Richtungen (z. B. bei Langholz) durch Rauschen unter 1 Farbstufe einen unnötig hohen Wert erhalten.