テクスチャ
2D 画像を 3D オブジェクトに貼って質感を作る。カラースペースとUVと フィルタ・ラップの3点だけ押さえればたいていのケースは扱える。
読み込み
基本は TextureLoader。Promise も使えるが、コールバック版の方が型と安全性が分かりやすい。
const loader = new THREE.TextureLoader()
const map = loader.load(
"/textures/wall_color.jpg",
(tex) => { tex.colorSpace = THREE.SRGBColorSpace },
undefined,
(err) => console.error(err),
)
const mat = new THREE.MeshStandardMaterial({ map })
load() は同期的に Texture を返すが、その時点では画像が読まれていない。
実際の読み込み完了を待ちたい時は onLoad コールバックか、
LoadingManager を使う(ローダー参照)。
カラースペース(最重要)
Three.js r152+ は sRGB がデフォルト。テクスチャの種類で colorSpace を分ける必要がある。
| 用途 | colorSpace |
|---|---|
| カラー画像(map / emissiveMap / matcap など) | THREE.SRGBColorSpace |
| データ画像(normalMap / roughnessMap / metalnessMap / aoMap / displacementMap) | THREE.NoColorSpace(既定) |
| HDR(環境マップ) | THREE.LinearSRGBColorSpace 系(自動) |
colorMap.colorSpace = THREE.SRGBColorSpace // ベースカラーは sRGB
normalMap.colorSpace = THREE.NoColorSpace // データなのでそのまま
カラー画像に colorSpace を設定し忘れている可能性大。map と emissiveMap には必ず sRGB を当てる。
UV ラップ・繰り返し
UV が 0..1 を超えたときの挙動を wrapS / wrapT で制御。
繰り返したい時は RepeatWrapping。
tex.wrapS = THREE.RepeatWrapping
tex.wrapT = THREE.RepeatWrapping
tex.repeat.set(4, 4)
tex.offset.set(0, 0)
tex.rotation = 0
ClampToEdgeWrapping(既定)— 端のピクセルが外まで延びるRepeatWrapping— タイル繰り返しMirroredRepeatWrapping— 鏡映繰り返し(継ぎ目が目立たない)
フィルタリング
拡大時と縮小時で別々に指定できる。
tex.magFilter = THREE.LinearFilter // 拡大: 線形(既定)
tex.minFilter = THREE.LinearMipmapLinearFilter // 縮小: トリリニア(既定)
- ピクセルアートを綺麗に出したい:
magFilter = NearestFilter+minFilter = NearestFilter(mipmap も無効化) - 遠くまで綺麗: 既定の
LinearMipmapLinearFilter。mipmap は自動生成(POT 画像が前提だったが、最新の Three.js は NPOT も対応) - 縦方向に潰れる時は
tex.anisotropyを上げる(最大値はrenderer.capabilities.getMaxAnisotropy())
ピクセルアート用の典型設定
tex.magFilter = THREE.NearestFilter
tex.minFilter = THREE.NearestFilter
tex.generateMipmaps = false
tex.colorSpace = THREE.SRGBColorSpace
flipY
Three.js は画像の Y を反転してから GPU に送る(既定 flipY = true)。
glTF / KTX2 のような反転済みの形式を扱う時は flipY = false。
glTF はローダー側で正しく処理されるので普段は意識しなくていい。
マテリアルにマップを当てる
MeshStandardMaterial / MeshPhysicalMaterial で使えるマップ:
| プロパティ | 役割 | colorSpace |
|---|---|---|
map | ベースカラー | sRGB |
normalMap | 法線(凹凸) | NoColorSpace |
roughnessMap | ラフネス | NoColorSpace |
metalnessMap | 金属度 | NoColorSpace |
aoMap | 環境遮蔽 | NoColorSpace |
emissiveMap + emissive | 自発光 | sRGB |
displacementMap | 頂点シフト(要セグメント数) | NoColorSpace |
alphaMap | α | NoColorSpace |
envMap | 環境(普通は scene.environment 経由) | — |
aoMap は uv2 が必要
Ambient Occlusion マップは2つめの UV セットを使う設計。
geometry.setAttribute("uv2", geometry.attributes.uv) // 同じ UV を流用
環境マップ(envMap)
反射やリッチな光環境のために、シーン全体を取り囲む画像を渡す。
最近は scene.environment に入れるのが定番(全マテリアルに自動適用)。
HDR(equirectangular)
import { RGBELoader } from "three/addons/loaders/RGBELoader.js"
new RGBELoader().load("/hdr/studio.hdr", (hdr) => {
hdr.mapping = THREE.EquirectangularReflectionMapping
scene.environment = hdr
// 背景にも使う場合:
scene.background = hdr
})
CubeMap(6面)
const cube = new THREE.CubeTextureLoader()
.setPath("/cubemap/")
.load(["px.jpg", "nx.jpg", "py.jpg", "ny.jpg", "pz.jpg", "nz.jpg"])
scene.background = cube
scene.environment = cube
PMREMGenerator で前処理
本格的な PBR 用の環境マップは事前に「roughness ごとのフィルタ済み版」を作っておく必要がある。
PMREMGenerator がやってくれる。最近の Three.js は scene.environment に渡せば自動でやるので普段は不要。
const pmrem = new THREE.PMREMGenerator(renderer)
pmrem.compileEquirectangularShader()
new RGBELoader().load("/hdr/studio.hdr", (hdr) => {
const env = pmrem.fromEquirectangular(hdr).texture
scene.environment = env
hdr.dispose()
pmrem.dispose()
})
動的なテクスチャ
VideoTexture — 動画
const video = document.createElement("video")
video.src = "/video.mp4"
video.muted = true
video.loop = true
video.play()
const tex = new THREE.VideoTexture(video)
tex.colorSpace = THREE.SRGBColorSpace
CanvasTexture — 動的描画
Canvas に2Dで描いた内容をテクスチャ化。ラベル・スコア表示等に。
const canvas = document.createElement("canvas")
canvas.width = 512; canvas.height = 256
const ctx = canvas.getContext("2d")
ctx.fillStyle = "#7dd3fc"
ctx.fillRect(0, 0, 512, 256)
ctx.font = "60px sans-serif"
ctx.fillStyle = "white"
ctx.fillText("Hello", 100, 130)
const tex = new THREE.CanvasTexture(canvas)
tex.colorSpace = THREE.SRGBColorSpace
// 中身を更新したら tex.needsUpdate = true
DataTexture — 自前のピクセル配列
const w = 256, h = 256
const data = new Uint8Array(w * h * 4)
for (let i = 0; i < w * h; i++) {
data[i*4+0] = (i / w) % 256
data[i*4+1] = 0
data[i*4+2] = (i % w)
data[i*4+3] = 255
}
const tex = new THREE.DataTexture(data, w, h, THREE.RGBAFormat)
tex.needsUpdate = true
テクスチャの繰り返し・スクロール
repeat / offset をフレームごとに変えるだけで「流れる帯」「コンベア」「水面」のような演出が手軽に作れる。
tex.wrapS = THREE.RepeatWrapping
tex.wrapT = THREE.RepeatWrapping
tex.repeat.set(4, 1)
renderer.setAnimationLoop((time) => {
tex.offset.x = (time * 0.0001) % 1
renderer.render(scene, camera)
})
圧縮テクスチャ(KTX2)
本番でテクスチャが多い時、KTX2(Basis Universal)に変換するとロード時間と GPU メモリが
大幅に減る。KTX2Loader + Basis transcoder が必要。
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js"
const ktx2 = new KTX2Loader()
.setTranscoderPath("/basis/") // Basis transcoder を配置
.detectSupport(renderer)
const tex = await ktx2.loadAsync("/tex.ktx2")
UV を確認する
UV が変だと貼り方がおかしくなる。UV を可視化する用のチェッカー画像を1枚持っておくとデバッグが早い。
- UV チェッカー画像を
mapに当てて、伸びや破綻を目視。 geometry.attributes.uvを console に出して値域チェック。- 独自シェーダならUV を色として可視化するパスを書くのが定石。
dispose()
シーンから外したマテリアルが持つテクスチャは自動では解放されない。
texture.dispose() を明示。詳しくは 注意点。