テクスチャ

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

フィルタリング

拡大時縮小時で別々に指定できる。

tex.magFilter = THREE.LinearFilter   // 拡大: 線形(既定)
tex.minFilter = THREE.LinearMipmapLinearFilter  // 縮小: トリリニア(既定)

ピクセルアート用の典型設定

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枚持っておくとデバッグが早い。

テクスチャも dispose()

シーンから外したマテリアルが持つテクスチャは自動では解放されない
texture.dispose() を明示。詳しくは 注意点