跳转到主要内容

03|材质系统

掌握 6 种材质类型(Basic / Lambert / Phong / Standard / Physical / Toon),理解 PBR 材质的 roughness 和 metalness,学会环境贴图和法线贴图的使用。

材质决定了物体表面如何与光线交互。掌握 6 种材质类型和 PBR 参数,就能让 3D 物体从"塑料感"变成"照片级真实"。

运行效果:6 种材质对比 + roughness×metalness 网格

从左到右:Basic、Lambert、Phong、Standard、Physical、Toon 六种材质对比,下方为 roughness×metalness 参数网格

01 学习目标

本节课聚焦 Three.js 材质系统,目标是:

  • 区分 6 种材质类型及其适用场景
  • 理解 PBR 材质的 roughness(粗糙度)和 metalness(金属感)
  • 掌握环境贴图(CubeTexture)实现反射效果
  • 理解法线贴图(Normal Map)的原理和使用
  • 学会使用 TextureLoader 加载外部纹理

02 材质类型对比

Three.js 提供了多种材质类型,每种适用于不同场景:

材质光照性能适用场景
MeshBasicMaterial不受光照影响最快纯色、线框、UI 元素
MeshLambertMaterialLambert 光照模型大量物体、低性能设备
MeshPhongMaterialPhong 光照模型(有高光)需要高光但不需要物理正确
MeshStandardMaterialPBR 物理渲染大多数场景(推荐默认)
MeshPhysicalMaterialPBR + 清漆/透射/薄膜玻璃、水面、汽车漆
MeshToonMaterial卡通着色卡通/像素风格

材质继承关系

Material (基类)
├── MeshBasicMaterial      — 不受光照
├── MeshLambertMaterial    — Lambert 漫反射
├── MeshPhongMaterial      — Phong 高光
├── MeshStandardMaterial   — PBR 物理渲染
│   └── MeshPhysicalMaterial — PBR 扩展(清漆、透射)
├── MeshToonMaterial       — 卡通着色
├── MeshMatcapMaterial     — MatCap 球面贴图
├── MeshDepthMaterial      — 深度可视化
├── MeshNormalMaterial     — 法线可视化
└── ShaderMaterial         — 自定义着色器

03 PBR 材质参数(Standard / Physical)

PBR(Physically Based Rendering)是现代 3D 渲染的标准,通过 roughness 和 metalness 两个参数控制材质外观:

new THREE.MeshStandardMaterial({
  color,      // 基础颜色
  roughness,  // 粗糙度(0=镜面光滑, 1=完全粗糙)
  metalness,  // 金属感(0=非金属/塑料, 1=纯金属)
  map,        // 颜色纹理贴图
  normalMap,  // 法线贴图(模拟凹凸细节)
  envMap,     // 环境贴图(反射周围环境)
  envMapIntensity, // 环境贴图强度
})

roughness(粗糙度)

效果示例
0完美镜面反射镜子、铬
0.2光滑抛光金属、塑料
0.5中等磨砂金属
0.8粗糙木头、石头
1完全粗糙(漫反射)粉笔、布料

metalness(金属感)

效果示例
0非金属(电介质)塑料、木头、玻璃
0.5半金属(不常见)氧化金属
1纯金属金、银、铜、铁

重要:metalness 只有 0 和 1 有意义,中间值只在特殊情况下使用。

关键组合理解

  • roughness=0, metalness=1 → 镜面金属(铬、银)
  • roughness=0.5, metalness=1 → 磨砂金属(铝)
  • roughness=0, metalness=0 → 玻璃/塑料(光滑非金属)
  • roughness=1, metalness=0 → 木头、石头、布料

04 环境贴图(Environment Map)

环境贴图是一个立方体贴图(CubeTexture),包含 6 张图片,让物体反射周围环境,金属感更强。

        +---+
        | +Y |  (顶部)
    +---+---+---+
    | -X | +Z | +X |  (侧面)
    +---+---+---+
        | -Y |  (底部)
        +---+
        | -Z |  (背面)
        +---+

物体根据表面法线方向,从环境贴图中采样颜色,实现反射效果。

const cubeTextureLoader = new THREE.CubeTextureLoader()
const envMap = cubeTextureLoader.load([
  'px.jpg', 'nx.jpg',  // 正X、负X
  'py.jpg', 'ny.jpg',  // 正Y、负Y
  'pz.jpg', 'nz.jpg',  // 正Z、负Z
])
material.envMap = envMap
material.envMapIntensity = 1.0

05 法线贴图(Normal Map)

什么是法线?

法线是垂直于表面的方向向量。想象一根针从物体表面垂直插出去,针尖指向的方向就是法线方向。平坦表面的法线都朝同一个方向;弯曲表面的法线会随着曲面变化。

为什么要用法线贴图?

3D 模型的凹凸细节可以通过两种方式实现:

  • 增加顶点:把模型做得更精细,但会大幅增加计算量
  • 法线贴图:用一张图片"骗"眼睛,让平坦表面看起来有凹凸感,但几何体本身不变

法线贴图的本质:告诉 GPU「这个像素的光线应该往哪个方向反射」,从而模拟凹凸效果。

为什么平坦表面是蓝紫色 RGB(128, 128, 255)?

法线贴图用 RGB 值编码法线方向:

通道方向值范围映射
RX(左右倾斜)0~255-1~+1
GY(前后倾斜)0~255-1~+1
BZ(朝上/朝外)0~255-1~+1

平坦表面法线朝上(+Z 方向),所以:

  • R=128:中间值,X 方向无倾斜(0)
  • G=128:中间值,Y 方向无倾斜(0)
  • B=255:最大值,Z 方向完全朝上(+1)

蓝紫色 = 法线完全朝上 = 平坦表面。偏离这个颜色 = 法线倾斜 = 视觉上产生凹凸感。

const textureLoader = new THREE.TextureLoader()
const normalMap = textureLoader.load('normal-map.png')
material.normalMap = normalMap
material.normalScale.set(1, 1) // 凹凸强度

06 外部纹理加载(TextureLoader)

const textureLoader = new THREE.TextureLoader()

textureLoader.load(
  '/textures/rust-metal.jpg',  // 纹理路径
  (texture) => {
    // 加载成功
    texture.wrapS = THREE.RepeatWrapping
    texture.wrapT = THREE.RepeatWrapping
    texture.repeat.set(2, 2)

    const material = new THREE.MeshStandardMaterial({
      map: texture,
      roughness: 0.8,
      metalness: 0.2,
    })
  },
  undefined,
  (error) => {
    console.warn('加载纹理失败:', error)
  }
)

TextureLoader vs CanvasTexture

  • TextureLoader:加载外部图片文件,适合真实纹理
  • CanvasTexture:程序生成,适合动态纹理或简单图案

07 纹理包裹模式

  • RepeatWrapping:纹理平铺重复(代码中使用)
  • ClampToEdgeWrapping:拉伸边缘像素(默认)
  • MirroredRepeatWrapping:镜像重复

08 API 速查

API用途
new THREE.MeshBasicMaterial({ color })不受光照的材质
new THREE.MeshLambertMaterial({ color })Lambert 漫反射材质
new THREE.MeshPhongMaterial({ color, shininess })Phong 高光材质
new THREE.MeshStandardMaterial({ color, roughness, metalness })PBR 物理材质
new THREE.MeshPhysicalMaterial({ clearcoat, transmission })PBR 扩展材质
new THREE.MeshToonMaterial({ color })卡通着色材质
new THREE.CubeTextureLoader().load([...])加载立方体环境贴图
new THREE.TextureLoader().load(url)加载普通纹理
material.envMap = texture设置环境贴图
material.normalMap = texture设置法线贴图
material.normalScale.set(x, y)设置法线强度

09 复盘自测

  1. Three.js 中哪些材质类型不受光照影响?为什么它们不需要灯光就能看到效果?

    MeshBasicMaterial 不受光照影响。它直接输出颜色,不经过光照计算,所以有没有灯光都一样。

  2. roughness=0 和 roughness=1 分别像什么材质?请各举 2 个例子。

    roughness=0 → 镜子、铬金属(完美镜面反射)。roughness=1 → 粉笔、布料(完全粗糙漫反射)。roughness 控制表面微观粗糙程度,影响反射的清晰度。

  3. metalness 参数的含义是什么?为什么说 metalness 只有 0 和 1 有意义,中间值只在特殊情况下使用?

    metalness 控制材质是金属还是非金属。现实中材质只有金属和非金属两种,不存在"半金属"。中间值可用于:物体表面有混合材质(如金属生锈)、特殊视觉风格(卡通/艺术化渲染)、磨损粗糙的金属效果。

  4. 法线贴图的 RGB 通道分别代表什么方向?平坦表面的法线贴图颜色是什么?

    R→X(左右倾斜),G→Y(前后倾斜),B→Z(朝上/朝外)。平坦表面法线朝 +Z,所以颜色是 RGB(128, 128, 255) 蓝紫色:R=128(中间值,X 无倾斜),G=128(中间值,Y 无倾斜),B=255(最大值,Z 完全朝上)。

  5. TextureLoader 和 CanvasTexture 分别适合什么场景?代码中哪些地方用了程序生成,哪些用了外部加载?

    TextureLoader 加载外部图片文件(jpg/png),适合真实纹理(如金属锈蚀、木地板)。CanvasTexture 用 Canvas 2D 程序生成,适合动态纹理、简单图案(如棋盘格、凹凸砖块)。项目中:金属锈蚀纹理用 TextureLoader,棋盘格纹理用 CanvasTexture。

10 大白话解释

材质类型

材质大白话
Basic群众演员:不管灯光怎么喊,表情永远不变
Lambert新手演员:能根据灯光反应,但不会"抢戏"(无高光)
Phong资深演员:会在关键位置"亮一下"(高光)
Standard实力派:按物理规律表演,大部分场景都能胜任
Physical影帝级:连皮肤质感、反光细节都能演出来
Toon卡通配音员:故意用夸张的"二值化"演技

roughness

  • roughness=0:完美镜子 → 清晰看到对面的人
  • roughness=0.5:磨砂玻璃 → 只能看到模糊的影子
  • roughness=1:白纸 → 完全不反射,只有颜色

记住:roughness 越高,反射越模糊。

metalness

  • metalness=0(塑料):高光是白色,不会反射环境
  • metalness=1(金属):高光是金属色,会反射周围环境

例子:塑料玩具(0) → 金戒指(1) → 生锈的铁(0.7)

法线贴图

问题:想让平面有凹凸感?

聪明办法:法线贴图 → 就像化妆!

  • 告诉 GPU “光线该往哪反射”
  • 平面看起来有凹凸,但其实还是平的
  • 蓝紫色 = 平坦表面,偏离蓝紫色 = 凹凸感

环境贴图

原理:金属球像镜子,反射周围环境(6面墙的照片)

例子:不锈钢水壶能看到厨房倒影,塑料杯子看不到

三种贴图

贴图类型作用生活比喻
Color Map决定颜色穿衣服
Normal Map模拟凹凸化妆
Environment Map反射环境配镜子

记住:roughness 控制镜子清晰度,metalness 控制是不是金属。


本文是 Three.js + GLSL + WebGPU 学习系列的第 3 篇笔记。课程评分:9.5/10。