プログラム生成テクスチャ
出典:room-at-dawn(MIT)、src/core/tex.js・rng.js・src/scene/materials.js、および parts/ 以下の各パーツのテクスチャ関数。
元のプロジェクトの寝室には画像ファイルが一つもなく、すべてのテクスチャを読み込み時にプログラムで生成しています。
核となる考え方:
- 物理単位で設計する。 高さはミリメートル単位、テクスチャの1グリッドが何メートルに相当するかを明記し、UV もメートルで指定します。これにより
repeat = 1 / tileメートルとなり、解像度を変えても見た目が変わりません。 - 「リアルさ」の大部分は色ではなくラフネスに現れる。 指紋、拭き跡、ホコリは色ではほとんど見えず、ハイライトが当たって初めて現れます。
- すべて繰り返し可能かつ再現可能。 ノイズはすべて周期を持ち、乱数は一律
RNG(seed)を使うため、同じシードからは常に同じ結果が得られます(ライトベイクやスクリーンショットの回帰テストに重要)。
ファイル
| パス | 内容 |
|---|---|
scripts/tex.js | 基本:RNG、pnoise/pfbm(軸ごとに異なる周期可能)、worley、vn(ルックアップテーブルによる高速ノイズ)、field、normalBytes(ミリメートル単位)、dataTex/canvasTex/packTex/colorTex、avgColor、once(キャッシュ)、rep(画像共有・repeatのみ変更)、seamScore、texData |
scripts/surfaces.js | wallNormal+wallPatch(木くず壁紙に3.2mスケールの漆喰と塗装の変化を加えたもの)、lacquerNormal、smudgeRough、dustRough、floorMaps、woodMaps、oakVeneer |
scripts/textiles.js | weaveNormal、waffleNormal、terryNormal、ribNormal、knitNormal、feltNormal、ropeNormal、fabricMaterial(sheen+逆光透過) |
scripts/wear.js | wornMaps(使い込まれた表面全体:色とラフネスの2枚)、speckleTexture、scuffTexture、および基本ブラシ softBlob、strand、fingerprint、noiseTile、overlay |
scripts/print.js | word/phrase(音節から生成されるダミーテキスト)、PAL、packer、fitText、paperGrain、makeBook+bookAtlas+bookGeometry、pagesTexture、topoPoster、photoPrints |
example/ | 部屋の一隅。すべての面がライブラリのいずれかのマテリアルを使用。?cam=(room、desk、glint、fabric、floor、wall、books);?flat=1 で単色比較モード |
example/preview.jpg | サンプルスクリーンショット |
references/recipes.md | 各マテリアルの作り方・サイズ・パラメータ、および新しいマテリアルを追加する方法 |
references/verify-engines.md | 検証方法、遭遇した問題、パフォーマンス、Godot/Unrealへの移植について |
サンプルの実行
cd <skill ディレクトリ> && python3 -m http.server 5422 --bind 127.0.0.1
# ブラウザで http://127.0.0.1:5422/example/ を開く (window.__tex に各テクスチャの生成時間・シームスコア・平均アルベドが格納されます)
(<skill ディレクトリ> はこのスキルを読み込んだときに表示される Base directory です。プラグインとしてインストールした場合、~/.claude/plugins/cache/ 以下に配置されます)
使い方
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はメートル単位。repeatは自動設定済み
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 });
roughnessMapを使用するマテリアルでは、roughnessを 1 に設定してください。Three.js は両者を乗算します。packTexとwornMapsのラフネスは G チャンネルに格納されます。グレースケールの canvas もそのまま使用できます。- 各マテリアルは
avg(線形平均色)を返します。これはthreejs-baked-giで反射光のアルベドとして使用します。
必ず守ること
Math.random()を使ってはいけません。 すべてのテクスチャに固定のシードが必要です。- ノイズの周期は整数で、テクスチャのタイル数と揃っていなければなりません(例:
pfbm(u*6, v*6, oct, seed, 6))。揃っていないとシームが発生します。seamScoreで確認してください。 - 高さはミリメートル単位で、
normalBytes(H, w, h, 1グリッドあたりのミリメートル数)を使用します。 解像度を変えてもバンプの強さは変わりません。また、床(1.1×1.9 mm)のようにグリッドが正方形でない場合は、軸ごとに別々の値を指定してください。 - ラフネスは色と一緒に描画します。
wornMapsでは各ストロークを2枚の canvas に同時に描画し、同じnew RNG(i)を使用して2枚の形状を合わせます。 - 暗い表面では、ゴミや明るい点の数を減らします(サンプルの濃い色の机では 9000 から 2500 に削減)。減らさないと花崗岩のように見えます。
- Canvas に描画するテキストや図形はすべて自作します(
word()、幾何図形)。実際の書名、ブランド名、商標を使ってはいけません。 - 最大サイズのテクスチャ(2048²)の生成には 0.1–0.35 秒かかります。 同じテクスチャは
onceでキャッシュし、rep()で共有します。オブジェクトごとに再生成しないでください。
実測値(RTX 4090、Chrome headless、サンプル全体で 1.1 秒)
| テクスチャ | サイズ | 時間 | シームスコア (x/y) |
|---|---|---|---|
floorMaps | 2048² × 3枚 | 0.32 s | 色 0.96/1.10、法線 1.00/1.02、ラフネス 0.89/1.05 |
woodMaps | 1024² × 3 | 0.20–0.23 s | 色 0.93/1.12、法線 1.30/1.06 |
wornMaps(1.2×0.6 m、3400 px/m) | 4080×2040+半分サイズのラフネス | 0.13 s | 非繰り返し |
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種 | 128²–512² | 1–19 ms | 0.94–1.10(綿ロープのyは2.03、意図的な溝。verify-engines.md参照) |
bookAtlas(12冊) | 2048×1536 | 0.05–0.08 s | — |
シームスコアが約1であればシームが見えず、2を超える場合は確認が必要です。計算時、分子と分母にそれぞれ1階調を加えることで、木目などほぼ変化のない方向で1階調未満のノイズにより大きな比が算出されるのを防ぎます。