Three.js 材質
目標版本為 three.js r186。範例使用 WebGLRenderer;WebGPU 與 TSL 章節則使用 WebGPURenderer。
本地覆寫(於 2026-11-10 由 Claude 為此電腦新增;非原始技能的一部分)。 我們的動畫短片管線 (
<ROOT>\studio,來自 mromidev/claude-animated-shorts) 鎖定為 three 0.175 (r175)
- Remotion 4.0.290 + @react-three/fiber 9.1.2 + pmndrs postprocessing 6.37.2。請保持 three 鎖定版本:shade.ts/cardshade.ts 會對 three 的著色器區塊進行字串修補。當在此管線中工作時:
- 忽略需要 r176+ 版本的「常見錯誤」/ API 建議(如 HDRLoader、Timer、PCFSoftShadowMap 棄用、setEffects、 RenderPipeline、outputBufferType、mesh.pivot、Capsule
height、TSL/WebGPU)。請先在 r175 中確認 API 是否存在。- 絕不使用 useFrame、Clock、Timer、delta time、setAnimationLoop 或 requestAnimationFrame 進行動畫。Remotion 會逐幀渲染: 每個值都應從
useCurrentFrame()/ fps 推導得出(對於片段使用mixer.setTime(frame / fps))。- 後製處理使用 pmndrs 的
postprocessing,而非 three/addons 的 EffectComposer:請勿添加 OutputPass(會導致雙重 sRGB 編碼)。- 卡通顏色:我們的合成器中色調映射為 NEUTRAL;請注意 ACES 預設值和
tonemapping_fragment的色調影響。- 對於其他 three.js 專案,下方原始的 r186 指導則照常適用。
快速開始
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(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 3);
// PBR 材質 - 用於有光照、逼真表面的預設選擇
const material = new THREE.MeshStandardMaterial({
color: 0x00aaff,
roughness: 0.35,
metalness: 0.1,
});
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 48, 32), material);
scene.add(mesh);
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1)); // 有光照的材質若無光源則渲染為黑色
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(3, 3, 3);
scene.add(light);
function animate() {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
材質類型概覽
| 材質 | 使用場景 | 光照 |
|---|---|---|
| MeshBasicMaterial | 無光照顏色、紋理、線框 | 無 |
| MeshLambertMaterial | 霧面漫射表面 | 漫射 |
| MeshPhongMaterial | 光澤塑膠、低成本的鏡面反射 | 漫射 + 鏡面反射 |
| MeshStandardMaterial | PBR 金屬/粗糙度工作流程 | PBR |
| MeshPhysicalMaterial | PBR 加上清漆、透射、絲絨光澤、彩虹色 | PBR |
| MeshToonMaterial | 卡通著色 | 卡通色階 |
| MeshMatcapMaterial | 將光照效果烘焙到 matcap 紋理中,無需光源 | 烘焙 |
| ShadowMaterial | 透明表面,僅顯示其接收的陰影 | 僅陰影 |
| PointsMaterial | 點雲 | 無 |
| LineBasicMaterial / LineDashedMaterial | 線條 | 無 |
| ShaderMaterial / RawShaderMaterial | 自訂 GLSL,僅適用於 WebGLRenderer | 自訂 |
顏色資料紋理 (map, emissiveMap, specularMap, sheenColorMap, specularColorMap, LDR matcap, 用作顏色的 canvas 或 video) 需要 texture.colorSpace = THREE.SRGBColorSpace,否則顏色會顯得黯淡。純量貼圖 (normal, bump, displacement, roughness, metalness, ao, alpha, toon gradientMap) 則保留預設值。HDR 載入器會自行設定其色彩空間。
MeshBasicMaterial
無光照計算。快速,始終可見。支援 envMap 搭配 reflectivity (0 到 1) 和 combine (THREE.MultiplyOperation, THREE.MixOperation, THREE.AddOperation);specularMap (sRGB) 會遮罩環境貼圖的貢獻。
const map = await new THREE.TextureLoader().loadAsync("color.jpg");
map.colorSpace = THREE.SRGBColorSpace;
const material = new THREE.MeshBasicMaterial({
color: 0xffffff, // 與 map 相乘
map,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide, // FrontSide (預設), BackSide, DoubleSide
fog: true, // 受到場景霧氣的影響
});
MeshLambertMaterial 與 MeshPhongMaterial
Lambert 僅有漫射。Phong 添加了鏡面高光;當 Standard 過於耗資源時,它是較便宜的替代方案。
const lambert = new THREE.MeshLambertMaterial({
color: 0x00ff00,
emissive: 0x111111, // 自發光顏色
emissiveIntensity: 1,
});
const phong = new THREE.MeshPhongMaterial({
color: 0x0000ff,
specular: 0x111111, // 高光顏色
shininess: 30, // 數值越高 = 高光越銳利
flatShading: false, // 平面 vs 平滑法線
});
兩者也都支援 map, emissiveMap, specularMap (sRGB;遮罩環境貼圖反射,以及在 Phong 上遮罩高光)、normalMap, bumpMap, displacementMap, aoMap, envMap, envMapIntensity 與 envMapRotation。
MeshStandardMaterial (PBR)
基於物理的渲染。建議用於逼真效果。
const map = await new THREE.TextureLoader().loadAsync("color.jpg");
map.colorSpace = THREE.SRGBColorSpace;
const ormMap = await new THREE.TextureLoader().loadAsync("orm.jpg"); // glTF ORM: ao 在紅色通道, roughness 在綠色, metalness 在藍色; 純量: 保持預設
const material = new THREE.MeshStandardMaterial({
color: 0xffffff, // 與 map 相乘
roughness: 1.0, // 0 = 鏡面, 1 = 完全漫射; 乘上 roughnessMap (綠色通道)
metalness: 1.0, // 0 = 介電質, 1 = 金屬; 乘上 metalnessMap (藍色通道), 因此使用貼圖時請保持為 1
map,
roughnessMap: ormMap,
metalnessMap: ormMap,
normalMap: null, // 表面細節; 使用 normalScale (一個 Vector2) 調整縮放
aoMap: ormMap, // 環境光遮蔽 (紅色通道); 強度使用 aoMapIntensity
displacementMap: null, // 頂點位移; 使用 displacementScale 調整縮放, displacementBias 調整偏移
emissive: 0x000000,
emissiveIntensity: 1,
emissiveMap: null, // sRGB
envMap: null, // 或依賴 scene.environment (見下文)
fog: true,
});
lightMap (搭配 lightMapIntensity) 以與 aoMap 烘焙遮蔽相同的方式烘焙間接光;HDR 或 EXR 的 lightMap 是 LinearSRGBColorSpace,載入器會自行設定。
UV 通道:aoMap 與 lightMap 讀取你選擇的通道
每個 UV 映射紋理都會取樣由 texture.channel 選擇的屬性:0 讀取 uv (預設),1 讀取 uv1,2 讀取 uv2,3 讀取 uv3。aoMap 和 lightMap 不需要第二組 UV 集 - 除非你更改 channel,否則它們會取樣 uv。GLTFLoader 會將 TEXCOORD_1 載入為 uv1 並自行設定 channel。
const aoMap = await new THREE.TextureLoader().loadAsync("ao.jpg"); // 純量: 保持預設
const material = new THREE.MeshStandardMaterial({ aoMap }); // 取樣 uv: 不需要額外的屬性
// 另一種用於單獨展開 (烘焙的 AO 或光貼圖) 的方法: 將烘焙師的 u,v 對複製到 uv1
const bakedUv = new THREE.Float32BufferAttribute(mesh.geometry.attributes.position.count * 2, 2); // 在你複製資料前為零填充
mesh.geometry.setAttribute("uv1", bakedUv); mesh.material = material;
aoMap.channel = 1; // 此材質現在取樣 uv1 而非 uv
MeshPhysicalMaterial (進階 PBR)
擴展 MeshStandardMaterial,添加圖層和體積功能。大多數屬性都存在對應的紋理變體:clearcoatMap, clearcoatRoughnessMap, clearcoatNormalMap, transmissionMap, thicknessMap, iridescenceMap, iridescenceThicknessMap, sheenColorMap, sheenRoughnessMap, anisotropyMap, specularIntensityMap, specularColorMap。
const material = new THREE.MeshPhysicalMaterial({
// 清漆 (汽車烤漆, 漆器)
clearcoat: 1.0, // 0 = 無, 1 = 完整塗層
clearcoatRoughness: 0.1,
// 透射 (玻璃, 水) - 折射其後方的不透明物體與 scene.background
transmission: 1.0, // 0 = 不透明, 1 = 完全透射
thickness: 0.5, // 用於折射的體積厚度
attenuationDistance: 1, // 世界單位; Infinity (預設值) = 無吸收
attenuationColor: new THREE.Color(0xffaa66), // 白光在經過 attenuationDistance 距離後變成此顏色
ior: 1.5, // 折射率, 1 到 2.333
// 絲絨光澤 (織物, 天鵝絨)
sheen: 1.0,
sheenColor: new THREE.Color(0xffffff),
sheenRoughness: 0.5,
// 彩虹色 (肥皂泡, 油膜)
iridescence: 1.0,
iridescenceIOR: 1.3,
iridescenceThicknessRange: [100, 400], // 奈米
// 各向異性 (拉絲金屬); 方向使用 anisotropyRotation
anisotropy: 1.0,
// 色散 (稜鏡玻璃); 回反射 (路標, 安全裝備)
dispersion: 0.0,
retroreflectivity: 0.0,
// 鏡面反射覆寫: specularIntensity, specularColor (加上它們的貼圖)
});
玻璃 - 具有平滑表面且其後方有物體可供折射的透射材質:
const glass = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0,
roughness: 0,
transmission: 1,
thickness: 0.5,
ior: 1.5,
});
// transmission 會折射其後方的不透明物體和背景;scene.environment 則會加入其反射
MeshToonMaterial
卡通著色。漸層貼圖是非顏色資料 - 請保留其預設色彩空間。
const steps = new Uint8Array([120, 200]); // 兩個著色色階
const gradientMap = new THREE.DataTexture(steps, 2, 1, THREE.RedFormat);
gradientMap.minFilter = THREE.NearestFilter; // 硬色階; LinearFilter 則會混合它們
gradientMap.magFilter = THREE.NearestFilter;
gradientMap.needsUpdate = true;
const material = new THREE.MeshToonMaterial({ color: 0x00ff00, gradientMap });
MeshNormalMaterial 與 MeshDepthMaterial
// 法線編碼為 RGB - 用於除錯和扁平化的插圖風格
const normals = new THREE.MeshNormalMaterial({ flatShading: false, wireframe: false });
// 深度為灰階 (BasicDepthPacking, 預設); RGBADepthPacking 則將其打包以便在著色器中讀回
const depth = new THREE.MeshDepthMaterial();
PointsMaterial
用於點雲。size 以像素為單位;當啟用 sizeAttenuation (預設,僅透視相機) 時,它會乘以畫布高度的一半再除以點在視圖空間的深度。
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0, 1, 0.5, 0, 0.5, 1, 0], 3));
const material = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.1,
sizeAttenuation: true, // 根據距離縮放
alphaTest: 0.5, // 搭配 map 或 alphaMap: 捨棄低於此 alpha 值的片段
});
const points = new THREE.Points(geometry, material);
scene.add(points);
LineBasicMaterial 與 LineDashedMaterial
WebGL 和 WebGPU 始終以 1 像素寬度繪製線條圖元;linewidth, linecap 和 linejoin 僅影響 SVGRenderer。若要在 WebGLRenderer 下繪製粗線,請使用 three/addons/lines/ 中的 Line2 搭配 LineGeometry 和 LineMaterial (linewidth 以像素為單位);LineMaterial 是一個 ShaderMaterial,因此 WebGPURenderer 需要使用 three/addons/lines/webgpu/Line2.js 中的 Line2,後者使用了 Line2NodeMaterial。
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.Float32BufferAttribute([-1, 0, 0, 1, 0, 0], 3));
const solid = new THREE.LineBasicMaterial({ color: 0xffffff }); // 始終為 1 px
const dashed = new THREE.LineDashedMaterial({ color: 0xffffff, dashSize: 0.5, gapSize: 0.25 });
const line = new THREE.Line(geometry, dashed);
line.computeLineDistances(); // 必須,否則線條會以實線繪製
scene.add(line);
ShaderMaterial (僅 WebGLRenderer)
自訂 GLSL,由 three.js 為你注入 uniform。它始終編譯為 GLSL ES 3.00:texture(), textureLod() 和 dFdx 可用,而 texture2D / gl_FragColor 仍作為別名存在 - 請保持 glslVersion 不設定。ShaderMaterial、RawShaderMaterial 和 onBeforeCompile 沒有 WebGPURenderer 路徑;在那裡,請使用 node materials 和 TSL。
const timer = new THREE.Timer();
timer.connect(document); // 當分頁隱藏時暫停
const material = new THREE.ShaderMaterial({
uniforms: { time: { value: 0 } },
vertexShader: `
varying vec2 vUv;
uniform float time;
void main() {
vUv = uv;
vec3 pos = position;
pos.z += sin(pos.x * 10.0 + time) * 0.1;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform float time;
void main() {
vec3 col = 0.5 + 0.5 * cos(time + vUv.xyx * 3.0);
gl_FragColor = vec4(col, 1.0);
}
`,
});
function animate() {
timer.update(); // 每幀第一個做的事,無參數
material.uniforms.time.value = timer.getElapsed();
renderer.render(scene, camera);
}
以下變數已在著色器中為你宣告:modelMatrix, modelViewMatrix, projectionMatrix, viewMatrix, normalMatrix, cameraPosition,以及屬性 position, normal, uv (對於 InstancedMesh 還有 instanceMatrix,你需要自己應用它:projectionMatrix * modelViewMatrix * instanceMatrix * vec4(pos, 1.0),以及搭配 vertexColors 時的 color)。額外的 uv 組和 tangent 不會被宣告 - 請自行添加 in vec2 uv1;。
RawShaderMaterial (僅 WebGLRenderer)
不會預先附加任何宣告 (僅從 glslVersion 附加 #version、SHADER_TYPE 和 SHADER_NAME 定義,以及你的 defines),但渲染器仍然會設定 projectionMatrix, modelViewMatrix, viewMatrix, normalMatrix, modelMatrix 和 cameraPosition - 請在著色器中宣告它們,並且永遠不要將它們傳入 uniforms:相同名稱的條目會在每次繪製時被覆寫 (modelMatrix, modelViewMatrix, normalMatrix) 或取代相機的值 (projectionMatrix, viewMatrix, cameraPosition)。
const material = new THREE.RawShaderMaterial({
glslVersion: THREE.GLSL3, // 沒有它,著色器將是 GLSL ES 1.00,它沒有 texture()
vertexShader: `
precision highp float;
in vec3 position;
in vec2 uv;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision highp float;
in vec2 vUv;
out vec4 fragColor;
void main() {
fragColor = vec4(vUv, 0.0, 1.0);
}
`,
});
透明材質:渲染順序
透明材質從後往前渲染,如果物件相互穿插或形成透明鏈,這會產生排序假影。解決方案:預先排序物件,中途切換材質,或啟用 depthWrite: false (但這會破壞不透明物體後的 CDA / 陰影接收)。您也可以使用材質的 renderOrder 和物件的 renderOrder 來強制排序。
const transparent = new THREE.MeshStandardMaterial({
transparent: true, // 啟用
opacity: 0.5,
depthWrite: true, // 預設為 false 當 transparent 為 true 時,手動設回 true 可修正排序,但會破壞半透明表面後的遮擋
});
alphaMap (純量) 添加逐像素透明度;alphaTest 則捨棄低於某個閾值的片段。
環境響應
材質透過其獨立屬性或場景環境來響應環境貼圖。
// 場景級環境:所有標準或物理材質自動響應
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const envTexture = pmremGenerator.fromScene(new THREE.RoomEnvironment()).texture;
scene.environment = envTexture;
// 材質級覆寫
material.envMap = envTexture;
material.envMapIntensity = 1.0; // 預設
material.envMapRotation = new THREE.Euler(0, Math.PI, 0); // 旋轉環境
對於 MeshBasicMaterial,最後兩個參數 combine 和 reflectivity 控制環境貼圖的外觀。
混合與模板
所有材質共享這些渲染狀態設定:
const material = new THREE.MeshStandardMaterial({
blending: THREE.NormalBlending, // NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending
blendSrc: THREE.SrcAlphaFactor,
blendDst: THREE.OneMinusSrcAlphaFactor,
blendEquation: THREE.AddEquation,
blendSrcAlpha: null, // 如果為 null 則回退到 blendSrc
blendDstAlpha: null, // 如果為 null 則回退到 blendDst
blendEquationAlpha: null, // 如果為 null 則回退到 blendEquation
stencilWrite: false,
stencilRef: 0,
stencilFunc: THREE.AlwaysStencilFunc, // AlwaysStencilFunc, EqualStencilFunc, NotEqualStencilFunc, LessStencilFunc, GreaterStencilFunc, etc.
stencilFail: THREE.KeepStencilOp,
stencilZFail: THREE.KeepStencilOp,
stencilZPass: THREE.KeepStencilOp,
stencilWriteMask: 0xff,
});
當使用 CustomBlending 時,您必須設定 blendSrc, blendDst 和 blendEquation;alpha 部分會回退。
WebGPU 與 TSL
WebGPURenderer 需要節點材質 (MeshBasicNodeMaterial, MeshStandardNodeMaterial, MeshPhysicalNodeMaterial, MeshToonNodeMaterial, PointsNodeMaterial, LineBasicNodeMaterial, LineDashedNodeMaterial, MeshNormalNodeMaterial, ShadowNodeMaterial, SpriteNodeMaterial, VolumeNodeMaterial) 或使用 TSL ( Three.js Shading Language ) 的 NodeMaterial。程式碼範例:
import { materialToon } from './path/to/your/material.js'; // 假設的匯入
import { color, float, uv, mix, positionLocal, instanceIndex, uniform, timerLocal, oscSine, vec3, PI } from 'three/tsl';
// 節點材質 - 就像一般的材質,但屬性接受節點
const material = new THREE.MeshPhysicalNodeMaterial();
material.colorNode = color(0x00aaff);
material.metalnessNode = float(0.1);
material.roughnessNode = float(0.35);
// TSL:動畫
const time = timerLocal();
const offset = time.mul(0.2).sin().mul(0.1);
material.positionNode = positionLocal.add(offset);
// 更簡單的寫法
material.colorNode = new THREE.Color(0x00aaff);
material.metalnessNode = float(0.1);
material.roughnessNode = float(0.35);
完整的 TSL 範例,產生一個脈衝的、使用者自訂顏色的球體:
import * as THREE from 'three';
import { color, float, timerLocal, positionLocal, instanceIndex, uniform, oscSine, vec3, PI } from 'three/tsl';
const renderer = new THREE.WebGPURenderer({ 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(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
// 動畫的 TSL 節點
const time = timerLocal();
const hue = time.add(PI).sin().mul(0.5).add(0.5);
const baseColor = color(hue, 1, 1).convertColorSpace(THREE.SRGBColorSpace);
const material = new THREE.MeshPhysicalNodeMaterial();
material.colorNode = baseColor;
material.metalnessNode = float(0.1);
material.roughnessNode = float(0.35);
const scale = oscSine(time.mul(0.5)).mul(0.2).add(1);
material.positionNode = positionLocal.mul(scale);
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 48, 32), material);
scene.add(mesh);
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1));
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(3, 3, 3);
scene.add(light);
renderer.setAnimationLoop(() => renderer.render(scene, camera));
將 TSL 與標準材質屬性結合
您可以在節點材質上混合 TSL 節點和標準值。非節點屬性會自動包裝。colorNode 會覆寫 color。
const material = new THREE.MeshStandardNodeMaterial({
color: 0x00aaff, // 會被 colorNode 覆寫
roughness: 0.5,
metalness: 0.1,
colorNode: color(0x00aaff),
});
// 自訂的 positionNode 和 normalNode 不受物件變換的影響
material.positionNode = positionLocal;
material.normalNode = vec3(0, 1, 0);
非標準節點 (如 clearcoat, transmission, sheen, iridescence, anisotropy, dispersion, retroreflectivity 及其對應的紋理節點) 在 MeshPhysicalNodeMaterial 上可用:clearcoatNode, transmissionNode, sheenNode, iridescenceNode, anisotropyNode, dispersionNode, retroreflectivityNode 以及地圖節點如 clearcoatMapNode, transmissionMapNode, iorNode, thicknessNode 等。
將 WebGL ShaderMaterial 移植到 WebGPU
ShaderMaterial / RawShaderMaterial 在 WebGPURenderer 上無法使用。遷移:
- 將 vertex 和 fragment 的副作用編碼為節點,並將它們分配給對應的
xxxNode屬性。 - 將
uniforms替換為uniform()或timerLocal()。 - 將 GLSL 表達式重寫為 TSL 等效表達式。
- 使用
NodeMaterial或特定的xxxNodeMaterial類別作為基底。
節點可以使用新的 TSL 語法 (例如 uv().mul(3.0)) 或舊的 Fn() 語法來建構。
一般錯誤
material.color 紋理無效
紋理需要正確的色彩空間。將所有顏色貼圖設定為 SRGBColorSpace,將純量貼圖保留為預設值。HDR 載入器會自行設定。
遺失光源
標準、物理、Lambert、Phong 和卡通材質需要 scene 中的光源才能顯示;Euler/離線烘焙的 Matcap 則不需要。MeshBasicMaterial 也永遠不需要光源。
線條始終以 1px 寬度渲染
使用 Line2 / LineMaterial 來繪製粗線。
透明排序假影
啟用 depthWrite 或調整 renderOrder。
混合模式未生效
確認 transparent: true 已設定。
不正確的貼圖通道
aoMap、lightMap 和 displacementMap 會取樣 texture.channel 選擇的 UV 組。
實用捷徑
scene.environment自動對所有標準/物理材質啟用 envMap,強度預設為 1。scene.background可以設定為顏色、紋理或立方體貼圖。renderer.toneMapping控制高動態範圍到低動態範圍的轉換:THREE.ACESFilmicToneMapping,THREE.NeutralToneMapping,THREE.ReinhardToneMapping等。