首页 / Three.js 入门教程 / PBR材质

Three.js 入门教程

PBR材质

本教程共 40 篇 · 第 14 篇 · 更新于 2026-08-14 · 约 8 分钟阅读

Three.jsPBRMeshStandardMaterialMeshPhysicalMaterial物理渲染

本节目标:理解 PBR 的概念,掌握 MeshStandardMaterial 的 roughness 与 metalness 两大核心参数,学会 MeshPhysicalMaterial 的 clearcoat、transmission、sheen 等高级属性。

PBR 是什么

PBR(Physically Based Rendering,基于物理的渲染)是用真实物理规律计算”光打到表面后怎么反射”的渲染技术。它由 Disney 为动画电影提出,后来成为游戏引擎(Unreal、Frostbite)的行业标准。最大的好处是材质参数的含义贴近真实世界,调起来有直觉——“这是金属还是塑料?粗糙还是光滑?“——而不是盲目调一堆魔法数字。

three.js 的 PBR 材质有两个:MeshStandardMaterial(标准)和 MeshPhysicalMaterial(物理增强)。使用它们必须配灯光或环境贴图,否则表面是全黑的。 PBR 还有一个好处:材质在不同光照条件下表现一致。传统材质在日光场景里调好的参数,换到夜景就全废;PBR 材质只要参数符合真实世界,白天黑夜都成立。调材质时只需要回答两个问题:表面是金属吗?表面有多粗糙?剩下交给物理公式。

MeshStandardMaterial:标准 PBR 材质

它是 three.js 的主力材质,能模拟绝大多数日常表面:木材、石材、金属、塑料、橡胶……核心参数只有两个:roughnessmetalness

const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  roughness: 0.4, // 粗糙度 0~1,默认 1
  metalness: 0.2, // 金属度 0~1,默认 0
});

roughness 粗糙度

表面”磨砂程度”:0 是镜面般光滑,1 是完全粗糙(以漫反射为主)。

  1. roughness = 0:像抛光的镜子,高光又亮又清晰;
  2. roughness = 0.3:像亚克力、湿石头,高光柔和;
  3. roughness = 1:像哑光涂料、纸,几乎没有高光。

默认值是 1。很多新手忘了调,物体看起来”灰蒙蒙的”,把 roughness 降到 0.3 左右立刻精神起来。 真实材质的 roughness 有大致参考值:抛光金属 0.10.3,塑料 0.4 左右,木材 0.60.9,粗糙石头接近 1。做材质时先按参考值起步,再微调。

metalness 金属度

表面”像金属的程度”:0 是非金属(塑料、木头、皮肤),1 是纯金属(金、银、铁)。

  1. metalness = 0:非金属,反射弱,颜色以自身颜色为主;
  2. metalness = 1:金属,几乎只反射环境,自身颜色变淡;
  3. 中间值:生锈的金属、脏金属。
Note

金属度高的物体必须配合环境贴图(envMap)或足够丰富的灯光才好看。纯金属几乎不产生漫反射,场景里只有一两盏灯时,会黑得发闷。这是正常的物理现象,不是 bug。环境贴图在第 18 章光照模型与渲染原理会展开。

纹理槽位

MeshStandardMaterial 除了 color,还有一整套纹理槽位(第 15、16 章展开):

  1. map:颜色贴图,给表面铺上图案;
  2. normalMap:法线贴图,给平面加凹凸细节(不改变形状,只改变光照);
  3. roughnessMap / metalnessMap:逐像素控制粗糙度和金属度;
  4. aoMap:环境光遮蔽贴图,加深缝隙和角落的阴影;
  5. emissiveemissiveMap:自发光,注意自发光不会照亮别人;
  6. envMap:环境贴图,提供反射来源。

其他常用属性

flatShading(平面着色)、wireframetransparent / opacityside 都是第 12 章讲过的通用属性,Standard 全部支持。另有 envMapIntensity 控制环境贴图强度,默认 1。

MeshPhysicalMaterial:Standard 的增强版

MeshPhysicalMaterial 继承自 MeshStandardMaterial,拥有它的全部能力,再叠加一摞高级属性。默认行为和 Standard 完全一致,可以无缝替换。它比 Standard 贵(渲染开销更大),只在需要特殊效果时用。

clearcoat 清漆层

在表面叠一层”透明漆”,模拟车漆、钢琴漆、抛光木器:底层有颜色和粗糙度,上层是光亮清漆。

const material = new THREE.MeshPhysicalMaterial({
  color: 0xcc3333,
  clearcoat: 1,            // 清漆强度 0~1,默认 0
  clearcoatRoughness: 0.1, // 清漆层的粗糙度,默认 0(最光滑)
});

clearcoatRoughness 控制清漆层本身的光滑度,0 是镜面漆,越大越像磨砂漆。

transmission 透射:玻璃效果

transmission: 1 让物体像玻璃一样透光,能看到后面的物体。常搭配三个参数:

const material = new THREE.MeshPhysicalMaterial({
  transmission: 1,   // 开启透射
  thickness: 0.5,    // 厚度,0 表示薄壁玻璃
  ior: 1.5,          // 折射率,默认 1.5;水约 1.33,玻璃 1.5~1.9
  roughness: 0.05,   // 表面越光滑,透射越清晰
  transparent: true, // 通常搭配透明使用
});
Tip

transmission 开销很大,而且场景里要有足够的内容在物体后面,才看得出”透”的效果。测试时放几个彩色物体在玻璃后面。

sheen 天鹅绒与布料

sheen 模拟天鹅绒、丝绸这类”边缘发亮”的布料质感,有自己独立的颜色和粗糙度:

const material = new THREE.MeshPhysicalMaterial({
  color: 0x8822aa,
  sheen: 1,             // 强度 0~1,默认 0
  sheenColor: 0xff88cc, // 绒毛的颜色
  sheenRoughness: 0.6,  // 绒毛粗糙度,默认 1
});

更多高级属性(认识即可)

  1. iridescence:虹彩效果,肥皂泡、油膜,配合 iridescenceIOR 使用;
  2. specularIntensity / specularColor:调整非金属的镜面反射强度和颜色;
  3. anisotropy:各向异性高光,拉丝金属、光盘背面,配合 anisotropyRotation
  4. reflectivity:非金属反射率,默认 0.5(对应折射率 1.5);
  5. dispersion:色散,透明物体的彩虹色边缘;
  6. attenuationColor / attenuationDistance:光在透射介质中被吸收的颜色和距离,做彩色玻璃用。

环境贴图与 PBR

前面反复提到”金属需要环境贴图”。环境贴图(envMap)是一张记录周围环境景象的图,材质从里面采样作为反射来源。有了它,金属球才能映出环境,roughness 低的光滑表面才会有真实的反光。

three.js 里最省事的做法,是用官方的 RoomEnvironment 扩展生成一个模拟室内环境的贴图:

import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;

scene.environment 设置后,场景里所有 PBR 材质自动获得环境反射,金属球立刻”亮”起来。这行代码是调试 PBR 材质时的第一选择——先确认材质本身没问题,再去调灯光。

Standard 还是 Physical

  1. 90% 的场景用 MeshStandardMaterial 就够了;
  2. 需要车漆(clearcoat)、玻璃(transmission)、布料(sheen)、虹彩(iridescence)时,再升级到 MeshPhysicalMaterial;
  3. 移动端性能敏感时优先 Standard,transmission 和 clearcoat 都很吃性能。

完整示例:四个球对比

一屏四个球,分别演示 roughness、metalness、clearcoat、transmission 的效果。保存成 HTML 直接运行:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>PBR 材质</title>
  <style>
    html, body { margin: 0; height: 100%; }
  </style>
</head>
<body>
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
    }
  }
  </script>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
    camera.position.set(3, 3, 7);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(innerWidth, innerHeight);
    document.body.appendChild(renderer.domElement);

    // PBR 材质必须有光
    scene.add(new THREE.AmbientLight(0xffffff, 3));
    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
    dirLight.position.set(5, 8, 6);
    scene.add(dirLight);

    const controls = new OrbitControls(camera, renderer.domElement);

    // 四个测试球
    const setups = [
      { name: 'roughness=0.2', material: new THREE.MeshStandardMaterial({ color: 0x44aaff, roughness: 0.2, metalness: 0 }) },
      { name: 'metalness=1',   material: new THREE.MeshStandardMaterial({ color: 0x44aaff, roughness: 0.3, metalness: 1 }) },
      { name: 'clearcoat',     material: new THREE.MeshPhysicalMaterial({ color: 0xcc3333, clearcoat: 1, clearcoatRoughness: 0.1 }) },
      { name: 'transmission',  material: new THREE.MeshPhysicalMaterial({ color: 0xffffff, transmission: 1, thickness: 0.5, roughness: 0.05, transparent: true }) },
    ];

    setups.forEach(({ material }, i) => {
      const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.9, 32, 16), material);
      sphere.position.x = (i - 1.5) * 2;
      scene.add(sphere);
    });

    function animate() {
      requestAnimationFrame(animate);
      scene.children.forEach((obj) => {
        if (obj.isMesh) obj.rotation.y += 0.01;
      });
      controls.update();
      renderer.render(scene, camera);
    }
    animate();

    addEventListener('resize', () => {
      camera.aspect = innerWidth / innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(innerWidth, innerHeight);
    });
  </script>
</body>
</html>
Note

第二个球(metalness=1)在只有两盏灯的场景里会明显偏暗,这是金属反射环境光的特性。给场景加一张环境贴图(envMap)后,它才会呈现金属光泽,第 18 章会教。

常见问题

  1. 金属球黑得发闷:纯金属几乎不产生漫反射,只有环境反射。用 RoomEnvironment 给 scene.environment 赋值,或加更多灯光。
  2. 物体全黑:PBR 材质必须开灯,检查场景里有没有光源。
  3. transmission 玻璃看不出效果:玻璃后面要有物体;另外 transparent 要配合开启。
  4. clearcoat 没反应clearcoat 默认为 0,先设成 1,再配合低 clearcoatRoughness 观察高光变化。
  5. 页面掉帧:Physical 的 transmission、clearcoat 开销大,移动端慎用;能用 Standard 就别升级到 Physical。

小结

PBR 让”调材质”从玄学变成直觉:roughness 管粗糙光滑,metalness 管金属非金属。MeshStandardMaterial 是默认选择,MeshPhysicalMaterial 提供清漆、透射、天鹅绒等进阶质感。下一章开始,给材质贴上真实世界的纹理。