threejs-textures について
Three.js 開発者向けのテクスチャ解説スキルです。WebGLRenderer と WebGPURenderer を対象に、読み込み、色空間、マッピング、レンダーターゲットを説明します。
主な機能
- 01画像、動画、生データのテクスチャ読み込み
- 02色空間、タイル、フィルタリングの設定
- 03KTX2 圧縮と環境マップの解説
- 04レンダーターゲットと動的反射の利用
- 05UV チャンネル、アトラス、破棄の解説
活用シーン
- 01マテリアルへのカラー・データマップ設定
- 02テクスチャのタイル、オフセット、フィルター調整
- 03シーンでの HDR 環境マップ利用
- 04動的反射やレンダーターゲットの作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cloudai-x/ threejs-skills --skill threejs-texturesインストール後、Claude Code で/threejs-texturesを入力して読み込まれたか確認します。
詳細
Three.js テクスチャ
three.js r186 を対象としています。例では WebGLRenderer を使用し、WebGPU と TSL のセクションでは WebGPURenderer を使用します。
クイックスタート
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 2);
// Color images must be tagged sRGB or they render too bright and washed out
const texture = new THREE.TextureLoader().load("diffuse.jpg");
texture.colorSpace = THREE.SRGBColorSpace;
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.MeshBasicMaterial({ map: texture }),
);
scene.add(mesh);
renderer.setAnimationLoop(animate);
function animate() {
mesh.rotation.z += 0.01;
renderer.render(scene, camera);
}
読み込み
// load() returns the texture at once; its image arrives asynchronously
const texture = new THREE.TextureLoader().load("diffuse.jpg");
texture.colorSpace = THREE.SRGBColorSpace;
mesh.material.map = texture; // before the first render; afterwards also set mesh.material.needsUpdate = true
// When the next step needs the loaded image, await it
const colorMap = await new THREE.TextureLoader().loadAsync("diffuse.jpg");
colorMap.colorSpace = THREE.SRGBColorSpace;
TextureLoader の onProgress パラメーターはサポートされておらず、呼び出されることはありません。各ファイルの読み込みが完了するたびに、LoadingManager が進行状況を報告します。
const manager = new THREE.LoadingManager();
manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}: ${url}`);
const loader = new THREE.TextureLoader(manager);
色空間
色管理はデフォルトで有効になっており、すべてのテクスチャは NoColorSpace で始まります。
- LDR のカラー画像には
SRGBColorSpaceを設定します。対象はmap、emissiveMap、specularMap、sheen カラーおよび specular カラーのマップ、LDR matcap、カラーとして使用する canvas または video です。 - スカラーのデータには
NoColorSpaceを維持します。対象は normal、bump、displacement、roughness、metalness、ambient-occlusion、alpha の各マップです。 - HDR 環境には
LinearSRGBColorSpaceを使用します。HDR ローダーが自動で設定します。GLTFLoader は読み込んだテクスチャの色空間を設定します。
const colorMap = new THREE.TextureLoader().load("color.jpg");
colorMap.colorSpace = THREE.SRGBColorSpace; // authored color image
const normalMap = new THREE.TextureLoader().load("normal.jpg");
// normalMap stays at NoColorSpace: it is data, not color
変換:ラップ、繰り返し、オフセット、回転
const texture = new THREE.TextureLoader().load("diffuse.jpg");
// Tiling requires repeat wrapping; the default clamps edge pixels
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4); // tile 4x4
texture.offset.set(0.5, 0.5); // shift in UV units
texture.center.set(0.5, 0.5); // pivot for rotation
texture.rotation = Math.PI / 4; // radians
MirroredRepeatWrapping は、繰り返すたびに反転しながらタイル状に並べます。
フィルタリング、ミップマップ、異方性
const texture = new THREE.TextureLoader().load("diffuse.jpg");
// Minification (many texture pixels per screen pixel)
texture.minFilter = THREE.LinearMipmapLinearFilter; // default: trilinear blend between mip levels
texture.minFilter = THREE.NearestFilter; // hard pixel look, samples only the base level
// Magnification (texture magnified on screen)
texture.magFilter = THREE.LinearFilter; // default
texture.magFilter = THREE.NearestFilter; // pixelated
// Mipmaps are generated whenever generateMipmaps is true, whatever the filters.
// Turn generation off to save memory (UI sprites, data textures):
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter; // and do not request a mipmap filter
// Sharper at grazing angles; clamp to what the hardware supports
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
テクスチャの種類
任意の画像要素から作成する基本クラスです。
const image = new Image();
image.src = "sprite.png";
const imageTexture = new THREE.Texture(image);
imageTexture.colorSpace = THREE.SRGBColorSpace;
image.onload = () => (imageTexture.needsUpdate = true); // upload once the pixels exist
生の型付き配列ピクセルから作成する DataTexture です。デフォルト値は Texture とは異なり、両方のフィルターに NearestFilter を使用し、flipY は false、ミップマップは無効です。
const size = 256;
const data = new Uint8Array(size * size * 4); // RGBA, one byte per channel
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const index = (i * size + j) * 4;
data[index] = i;
data[index + 1] = j;
data[index + 2] = 128;
data[index + 3] = 255;
}
}
const dataTexture = new THREE.DataTexture(data, size, size);
dataTexture.needsUpdate = true;
動的に描画するテクスチャには CanvasTexture を使用します(コンストラクターで needsUpdate を設定します)。
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#404040";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "#ffffff";
ctx.font = "48px sans-serif";
ctx.fillText("Hello", 40, 150);
const canvasTexture = new THREE.CanvasTexture(canvas);
canvasTexture.colorSpace = THREE.SRGBColorSpace; // canvas colors are sRGB
// after each redraw of the canvas:
canvasTexture.needsUpdate = true;
再生中の HTMLVideoElement には VideoTexture を使用します(毎フレーム再アップロードし、ミップマップは無効です)。
const video = document.createElement("video");
video.src = "video.mp4";
video.loop = true;
video.muted = true;
video.play();
const videoTexture = new THREE.VideoTexture(video);
videoTexture.colorSpace = THREE.SRGBColorSpace;
WebCodecs 用の VideoFrameTexture では、デコードした各フレームを手動で渡します。
const frameTexture = new THREE.VideoFrameTexture();
frameTexture.colorSpace = THREE.SRGBColorSpace;
const decoder = new VideoDecoder({
output(frame) {
if (frameTexture.image instanceof VideoFrame) frameTexture.image.close(); // free the replaced frame, or decoding stalls
frameTexture.setFrame(frame);
},
error: (e) => console.error(e),
});
HTMLTexture はブラウザーの HTML-in-Canvas API を通じて HTML 要素を描画します。レンダラーは要素を独自の canvas に移動し、描画イベントのたびに再アップロードします。この API に対応していないブラウザーでは、テクスチャは空のままです。
圧縮テクスチャ(KTX2)
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
const ktx2Loader = new KTX2Loader();
// No setTranscoderPath call: the transcoder loads from the three package.
// Set one only when self-hosting the decoders, pinned to this release.
ktx2Loader.detectSupport(renderer); // required before loading; works with both renderers
const compressedMap = await ktx2Loader.loadAsync("diffuse.ktx2");
// colorSpace comes from the file's transfer function; nothing to set by hand
Basis Universal KTX2 は、GPU がサポートする圧縮形式にトランスコードします。GPU が非圧縮でアップロードする PNG や JPEG と比べて、アップロードサイズとメモリー使用量を抑えられます(GPU が対象形式のどれにも対応していない場合、KTX2Loader は非圧縮 RGBA にフォールバックします)。圧縮テクスチャは、アップロード時に反転したりミップマップを生成したりできません。flipY は false のままなので、UV が想定する向きでファイルを作成してください。また、ミップマップはファイルに埋め込む必要があります。
環境マップ
キューブマップ(ローダーが結果を自動で sRGB に設定します):
const cubeTexture = new THREE.CubeTextureLoader().load([
"px.jpg", "nx.jpg", // +X, -X
"py.jpg", "ny.jpg", // +Y, -Y
"pz.jpg", "nz.jpg", // +Z, -Z
]);
scene.background = cubeTexture;
scene.environment = cubeTexture; // lights Standard and Physical materials (WebGLRenderer also Lambert and Phong)
HDR の正距円筒図法ファイル — mapping を設定してから割り当てます。どちらのレンダラーも環境を自動でプリフィルタリングします。
import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
const env = await new HDRLoader().loadAsync("environment.hdr");
env.mapping = THREE.EquirectangularReflectionMapping; // without it the background draws as a flat screen quad
scene.environment = env;
scene.background = env;
EXRLoader(three/addons/loaders/EXRLoader.js)も同じ方法で .exr ファイルを読み込み、UltraHDRLoader は Ultra HDR JPEG を読み込みます。どちらも独自に線形色空間を設定します。
シーンレベルの制御:
scene.environmentIntensity = 0.5; // dim the image-based lighting
scene.environmentRotation.y = Math.PI / 4;
scene.backgroundBlurriness = 0.5; // 0-1
scene.backgroundIntensity = 1.0;
scene.backgroundRotation.y = Math.PI;
どちらのレンダラーも、正距円筒図法の環境を内部でより大きなテクスチャを使ったキューブマップに変換します(サンプリング品質は向上しますが、メモリー使用量は増えます)。メモリーが重要な場合は、低解像度の正距円筒図法ファイルを用意してください。
PMREMGenerator は、fromScene でキャプチャする RoomEnvironment のようなシーンから環境を生成する場合に使用します。
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
const pmrem = new THREE.PMREMGenerator(renderer);
const room = new RoomEnvironment();
scene.environment = pmrem.fromScene(room, 0.04).texture;
room.dispose(); // frees the room's geometries and materials
pmrem.dispose();
読み込んだ正距円筒図法ファイルを「反射を改善するため」に PMREMGenerator に通さないでください。mapping を設定して直接割り当てれば、同じプリフィルタリングが適用されます。
レンダーターゲット
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
const rtScene = new THREE.Scene(); // must not contain the mesh that samples the target
rtScene.add(new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshNormalMaterial())); // unlit, visible without lights
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, camera);
renderer.setRenderTarget(null); // back to the canvas
// the output is a texture like any other
mesh.material.map = renderTarget.texture;
テクスチャとしての深度バッファ:
const depthTarget = new THREE.WebGLRenderTarget(512, 512);
depthTarget.depthTexture = new THREE.DepthTexture(512, 512); // default type UnsignedIntType
MSAA と複数のアタッチメント:
// MSAA: resolved into .texture automatically after rendering
const msaaTarget = new THREE.WebGLRenderTarget(512, 512, { samples: 4 });
// MRT: N color attachments, read back through .textures
const mrtTarget = new THREE.WebGLRenderTarget(512, 512, { count: 2 });
const firstAttachment = mrtTarget.textures[0];
const secondAttachment = mrtTarget.textures[1];
動的な反射
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter,
});
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);
mesh.material.envMap = cubeRenderTarget.texture;
// each frame: hide the reflective mesh so it does not see itself
mesh.visible = false;
cubeCamera.position.copy(mesh.position);
cubeCamera.update(renderer, scene);
mesh.visible = true;
UV チャンネル
UV マップを使うすべてのマテリアルテクスチャは、texture.channel で選択した UV 属性をサンプリングします。0 は uv(デフォルト)、1 は uv1、2 は uv2、3 は uv3 を読み取ります。そのため、aoMap と lightMap はデフォルトの uv セットで動作します。別途展開したセットを使う場合は uv1 に設定し、テクスチャをそのセットに割り当てます。
const aoTexture = new THREE.TextureLoader().load("ao.jpg");
const geometry = new THREE.BoxGeometry(1, 1, 1);
const uvAttribute = geometry.attributes.uv;
const aoUvs = new Float32Array(uvAttribute.count * 2); // fill with the second unwrap
geometry.setAttribute("uv1", new THREE.BufferAttribute(aoUvs, 2));
const material = new THREE.MeshStandardMaterial({ aoMap: aoTexture });
material.aoMap.channel = 1; // sample "uv1" instead of "uv"
const aoMesh = new THREE.Mesh(geometry, material);
scene.add(aoMesh);
テクスチャアトラス
1 枚の画像から複数のスプライトを使い、それぞれをオフセットと繰り返しで選択します。
const atlas = await new THREE.TextureLoader().loadAsync("atlas.png");
atlas.colorSpace = THREE.SRGBColorSpace;
// filtering and mipmaps sample across sprite borders: leave padding around each sprite
// one clone per sprite: clones share the image source, so WebGLRenderer uploads it once
function spriteTexture(row, col, gridSize = 2) {
const sprite = atlas.clone();
sprite.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
sprite.repeat.set(1 / gridSize, 1 / gridSize);
return sprite;
}
mesh.material.map = spriteTexture(0, 0); // top-left sprite of a 2x2 sheet
標準マテリアルスロット
知っておくと便利なチャンネルの規則:aoMap は赤、roughnessMap は緑、metalnessMap は青を読み取ります(そのため、オクルージョン・ラフネス・メタルネスを1枚にパックした画像を使えます)。alphaMap は緑を読み取ります。
const colorMap = new THREE.TextureLoader().load("color.jpg");
const emissiveMap = new THREE.TextureLoader().load("emissive.jpg");
const normalMap = new THREE.TextureLoader().load("normal.jpg");
colorMap.colorSpace = THREE.SRGBColorSpace;
emissiveMap.colorSpace = THREE.SRGBColorSpace;
// normalMap keeps NoColorSpace, as do roughness, metalness, ao, alpha and displacement maps
const material = new THREE.MeshStandardMaterial({
map: colorMap,
normalMap: normalMap,
normalScale: new THREE.Vector2(1, 1),
emissiveMap: emissiveMap,
emissive: 0xffffff, // non-black, so the emissive map contributes
roughness: 1, // scalar multipliers for the matching maps
metalness: 0,
});
material.aoMap = new THREE.TextureLoader().load("ao.jpg");
material.aoMapIntensity = 1;
alphaMap は transparent: true(ブレンド)または alphaTest(切り抜き)を設定すると有効になります。displacementMap は displacementScale と displacementBias に応じてスケーリングされ、細分化されたメッシュが必要です。normalMapType のデフォルトは TangentSpaceNormalMap です。オブジェクト空間法線マップには ObjectSpaceNormalMap を設定します。
破棄とメモリ
他に破棄されていないテクスチャが共有していなければ、texture.dispose() で GPU へのアップロードデータが解放されます(上記のアトラスのクローンは1つのテクスチャを共有しています)。画像オブジェクトは残ります。他のマテリアルで使用されていないテクスチャだけを破棄してください。
function disposeMaterial(material) {
for (const key of Object.keys(material)) {
const value = material[key];
if (value && value.isTexture) {
value.dispose();
}
}
material.dispose();
}
console.log(renderer.info.memory.textures); // texture count currently on the GPU
console.log(renderer.capabilities.maxTextureSize); // largest supported dimension
メッシュごとに読み込むのではなく、1つのテクスチャインスタンスを複数のマテリアルで共有してください。2のべき乗でないサイズも問題ありません。WebGL 2 は、それらのテクスチャのミップマップとリピートラッピングをサポートしています。
WebGPU と TSL
import * as THREE from "three/webgpu";
import { texture, uv } from "three/tsl";
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init(); // before any other use of the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111); // the WebGPU canvas starts transparent
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 2);
const map = new THREE.TextureLoader().load("diffuse.jpg");
map.colorSpace = THREE.SRGBColorSpace;
map.anisotropy = renderer.getMaxAnisotropy(); // no capabilities object under WebGPU
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), new THREE.MeshBasicNodeMaterial());
mesh.material.colorNode = texture(map, uv().mul(0.5).add(0.25)); // custom UV transform in TSL: the center half, zoomed 2x
scene.add(mesh);
renderer.setAnimationLoop(animate);
function animate() {
mesh.rotation.z += 0.01;
renderer.render(scene, camera);
}
TSL のサンプリング:texture(map) は uv() とテクスチャ独自のオフセット、リピート、回転を使用します。明示的なノードを指定すると、座標はすべてそのノードが引き継ぎます。従来のスロット(map、normalMap、roughnessMap、aoMap)はノードマテリアルでもそのまま機能します。カスタムサンプリングには colorNode を使用します。
import * as THREE from "three/webgpu";
import { texture, uv } from "three/tsl";
const map = new THREE.TextureLoader().load("diffuse.jpg");
map.colorSpace = THREE.SRGBColorSpace;
mesh.material.colorNode = texture(map); // uv() plus the texture's offset/repeat/rotation
mesh.material.colorNode = texture(map, uv(1)); // explicit node: samples the "uv1" attribute
KTX2Loader.detectSupport(renderer) はデバイスの機能を照会するため、await renderer.init() の後に呼び出してください。動的な反射には、同じ Hide-Update-Show の CubeCamera パターンで CubeRenderTarget(WebGLCubeRenderTarget ではありません)を使用します。
import * as THREE from "three/webgpu";
const cubeRenderTarget = new THREE.CubeRenderTarget(256); // replaces WebGLCubeRenderTarget
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);
mesh.material.envMap = cubeRenderTarget.texture;
mesh.visible = false; // hide so the mesh does not reflect itself
cubeCamera.position.copy(mesh.position);
cubeCamera.update(renderer, scene);
mesh.visible = true;
よくある間違い
geometry.setAttribute("uv2", geometry.attributes.uv)→material.aoMap = aoTexture;:aoMap はtexture.channelで選択した属性(デフォルトは 0 のuv)をサンプリングするため、追加の属性は不要です。uv2を読み取るのはchannelが 2 のテクスチャだけです。別途ベイクした UV 展開のみをuv1に設定し、aoTexture.channel = 1とします。new RGBELoader()→new HDRLoader():r180 で RGBELoader から HDRLoader に名前が変更されました。古いクラスも引き続き動作しますが、非推奨の警告がログに出ます。import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js"→import { EXRLoader } from "three/addons/loaders/EXRLoader.js":ブラウザーのインポートマップではthree/addons/が examples/jsm/ にマッピングされ、three/examples/のキーは定義されていないため、examples のパスでは解決できません。texture.encoding = THREE.sRGBEncoding→texture.colorSpace = THREE.SRGBColorSpace:encodingとsRGBEncoding定数は削除され、代わりにcolorSpaceが導入されました。const texture = loader.load("color.jpg")→const texture = loader.load("color.jpg"); texture.colorSpace = THREE.SRGBColorSpace;:LDR のカラー画像をNoColorSpaceのままにすると、明るすぎて色あせたように表示されます。texture.minFilter = THREE.LinearFilter; // no mipmaps→texture.generateMipmaps = false;:generateMipmapsが true であれば、フィルターにかかわらずミップマップが生成されるため、LinearFilter だけではミップマップのアップロードを防げません。texture.generateMipmaps = false; texture.wrapS = THREE.ClampToEdgeWrapping; // non-power-of-two size→texture.wrapS = THREE.RepeatWrapping;:WebGL 2 は 2 の累乗ではないテクスチャでもミップマップとリピートラッピングに対応しているため、これらを無効にする必要はありません。texture.anisotropy = renderer.capabilities.getMaxAnisotropy()→texture.anisotropy = renderer.getMaxAnisotropy():WebGPURenderer にはcapabilitiesオブジェクトがありません。WebGPU ではこちらを呼び出します。material.envMapIntensity = 0.5→scene.environmentIntensity = 0.5:envMapIntensityが調整するのは、マテリアル自身のenvMapのみであり、scene.environmentではありません。scene.environment = pmrem.fromEquirectangular(hdr).texture→hdr.mapping = THREE.EquirectangularReflectionMapping; scene.environment = hdr;:レンダラーが正距円筒図法の環境を自動で事前フィルタリングするため、読み込んだファイルに手動で PMREM を適用しても効果はありません。scene.background = hdr→hdr.mapping = THREE.EquirectangularReflectionMapping; scene.background = hdr;:mappingを設定しないと、正距円筒テクスチャは周囲を取り囲む背景ではなく、画面空間の平面として描画されます。ktx2Loader.detectSupportAsync(renderer)→await renderer.init(); ktx2Loader.detectSupport(renderer);:非同期版は r181 で非推奨になりました。また、サポートの検出には初期化済みのデバイスが必要です。ktx2Loader.setTranscoderPath("libs/basis/")→const ktx2Loader = new KTX2Loader(); ktx2Loader.detectSupport(renderer);:トランスコーダーはパッケージから読み込まれるため、パスが必要なのはセルフホストする場合のみです。[email protected] に固定してください。new THREE.WebGLMultipleRenderTargets(width, height, 2)→new THREE.WebGLRenderTarget(width, height, { count: 2 }):r162 で削除されました。アタッチメントはrenderTarget.textures[0..count-1]から取得します。new THREE.WebGLCubeRenderTarget(256)→new THREE.CubeRenderTarget(256):WebGPURenderer では、three/webgpuからインポートしたCubeRenderTargetを使用します。WebGLCubeRenderTargetは WebGL 専用です。loader.load(url, onLoad, onProgress, onError)→manager.onProgress = (url, loaded, total) => console.log(url, loaded, total):TextureLoader のonProgress引数はサポートされておらず、呼び出されることもありません。mesh.material.map = texture; // after the mesh has rendered→mesh.material.map = texture; mesh.material.needsUpdate = true;:WebGLRenderer は初回のレンダリング時に、その時点で設定されているマップを使ってマテリアルのシェーダーを構築します。その後で追加されたmapは、needsUpdateによってシェーダーを再構築しない限り表示されません。normalMap.colorSpace = THREE.LinearSRGBColorSpace→normalMap.colorSpace = THREE.NoColorSpace:ノーマル、roughness、metalness、ao、alpha、displacement の各マップは色ではなくデータを格納するため、デフォルトのNoColorSpaceのままにします。const texture = await new Promise((resolve, reject) => loader.load(url, resolve, undefined, reject))→const texture = await loader.loadAsync(url):loadAsyncはLoader基底クラスに組み込まれた、同じ処理を行う Promise です。cubeCamera.update(renderer, scene)→mesh.visible = false; cubeCamera.update(renderer, scene); mesh.visible = true;:反射するメッシュを表示したままにすると、そのメッシュ自身のenvMapが読み取るキューブマップに描画されてしまいます。
関連項目
threejs-materials— テクスチャスロットにとどまらない、マテリアルのプロパティ、ブレンド、物理ベースのマテリアル。threejs-loaders— ローディングマネージャー、glTF モデル、ローダー全般。threejs-shaders— WebGLRenderer でのカスタム GLSL テクスチャサンプリング。threejs-lighting— 環境光と光の単位。threejs-asset-pipeline— gltf-transform を使ってモデルのテクスチャを KTX2、WebP、AVIF にエンコードする方法。