{T}

材质基础

材质(Material)定义了 3D 对象的外观,包括颜色、纹理、透明度、光照响应等属性。Three.js 提供了多种材质类型以适应不同的渲染需求。

概述

材质决定了几何体如何被渲染。不同类型的材质有不同的特性和用途,从简单的纯色渲染到复杂的物理渲染,Three.js 提供了完整的材质体系。

系统架构

Three.js 材质系统采用面向对象的设计,所有材质类型都继承自基类 THREE.Material

材质类层次结构

code
THREE.Material (基类)
├── THREE.MeshBasicMaterial          # 基础材质(不受光照影响)
├── THREE.MeshLambertMaterial        # Lambert 材质(漫反射)
├── THREE.MeshPhongMaterial          # Phong 材质(高光反射)
├── THREE.MeshStandardMaterial       # 标准 PBR 材质
├── THREE.MeshPhysicalMaterial       # 物理 PBR 材质(扩展)
├── THREE.MeshToonMaterial           # 卡通材质
├── THREE.MeshNormalMaterial         # 法线材质(调试)
├── THREE.MeshDepthMaterial          # 深度材质
├── THREE.MeshDistanceMaterial       # 距离材质
├── THREE.LineBasicMaterial          # 线条材质
├── THREE.LineDashedMaterial         # 虚线材质
├── THREE.PointsMaterial             # 点材质
├── THREE.SpriteMaterial             # 精灵材质
├── THREE.ShaderMaterial             # 自定义着色器材质
└── THREE.RawShaderMaterial          # 原始着色器材质

核心概念

  • 材质(Material):定义物体表面的视觉属性
  • 着色器(Shader):GPU 上执行的程序,负责计算每个像素的颜色
  • 贴图(Texture):用于增强材质细节的图像
  • 光照模型:决定材质如何响应光照的数学模型

材质类型对比

性能与特性对比表

材质类型性能消耗光照支持适用场景主要特性
MeshBasicMaterial⭐ 最低❌ 不支持UI元素、调试不受光照影响,渲染最快
MeshLambertMaterial⭐⭐ 低✅ 漫反射哑光表面适合无光泽物体
MeshPhongMaterial⭐⭐⭐ 中✅ 高光反射光泽表面支持高光,适合塑料、金属
MeshStandardMaterial⭐⭐⭐⭐ 高✅ PBR真实渲染物理真实,金属度/粗糙度
MeshPhysicalMaterial⭐⭐⭐⭐⭐ 最高✅ PBR+高级效果清漆、透射、折射等
MeshToonMaterial⭐⭐ 低✅ 卡通渲染卡通风格分层着色效果

材质选择指南

code
需要真实感渲染?
├─ 是 → 需要高级效果(玻璃、车漆)?
│       ├─ 是 → MeshPhysicalMaterial
│       └─ 否 → MeshStandardMaterial
└─ 否 → 需要光照?
        ├─ 是 → 需要高光?
        │       ├─ 是 → MeshPhongMaterial
        │       └─ 否 → MeshLambertMaterial
        └─ 否 → MeshBasicMaterial

基础材质类型

MeshBasicMaterial

最基础的材质,不受光照影响,适合简单的纯色或纹理显示。

javascript
import * as THREE from 'three';

// 创建基础材质
const material = new THREE.MeshBasicMaterial({
  color: 0x00ff00  // 绿色
});

// 常用属性
const basicMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,        // 颜色
  wireframe: false,       // 是否显示线框
  wireframeLinewidth: 1,  // 线框宽度
  transparent: false,     // 是否透明
  opacity: 1,             // 透明度
  visible: true,          // 是否可见
  side: THREE.FrontSide   // 渲染面
});

// 使用纹理
const texture = new THREE.TextureLoader().load('texture.jpg');
const texturedMaterial = new THREE.MeshBasicMaterial({
  map: texture
});

渲染面选项

javascript
// 正面(默认)
side: THREE.FrontSide

// 背面
side: THREE.BackSide

// 双面
side: THREE.DoubleSide

MeshNormalMaterial

法线材质,将法线方向映射为颜色,常用于调试。

javascript
const material = new THREE.MeshNormalMaterial({
  flatShading: false,  // 是否使用平面着色
  wireframe: false
});

// 示例:查看几何体的法线分布
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshNormalMaterial()
);
scene.add(sphere);

MeshLambertMaterial

Lambert 材质,一种非光泽表面材质,适合哑光物体。

javascript
const material = new THREE.MeshLambertMaterial({
  color: 0x00ff00,
  emissive: 0x000000,      // 自发光颜色
  emissiveIntensity: 1,    // 自发光强度
  wireframe: false
});

// Lambert 材质需要光源才能正确显示
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);

const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

MeshPhongMaterial

Phong 材质,支持高光反射,适合光泽表面。

javascript
const material = new THREE.MeshPhongMaterial({
  color: 0x00ff00,
  specular: 0x111111,      // 高光颜色
  shininess: 30,           // 高光强度(0-100)
  emissive: 0x000000,      // 自发光颜色
  emissiveIntensity: 1,
  flatShading: false
});

// 示例:创建光滑的球体
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshPhongMaterial({
    color: 0xff0000,
    specular: 0xffffff,
    shininess: 100
  })
);
scene.add(sphere);

MeshStandardMaterial

标准物理材质,基于 PBR(Physically Based Rendering),提供最真实的效果。

javascript
const material = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
  metalness: 0.5,          // 金属度(0-1)
  roughness: 0.5,          // 粗糙度(0-1)
  emissive: 0x000000,
  emissiveIntensity: 1,
  
  // 环境贴图
  envMap: null,
  envMapIntensity: 1,
  
  // 法线贴图
  normalMap: null,
  normalScale: new THREE.Vector2(1, 1),
  
  // 其他贴图
  aoMap: null,             // 环境光遮蔽贴图
  aoMapIntensity: 1,
  
  roughnessMap: null,      // 粗糙度贴图
  metalnessMap: null       // 金属度贴图
});

// 示例:金属材质
const metalMaterial = new THREE.MeshStandardMaterial({
  color: 0xffd700,
  metalness: 1,
  roughness: 0.2
});

// 示例:塑料材质
const plasticMaterial = new THREE.MeshStandardMaterial({
  color: 0x0066ff,
  metalness: 0,
  roughness: 0.5
});

MeshPhysicalMaterial

物理材质,MeshStandardMaterial 的扩展,提供更多物理属性。

javascript
const material = new THREE.MeshPhysicalMaterial({
  // 继承 StandardMaterial 的所有属性
  color: 0xffffff,
  metalness: 0,
  roughness: 0.5,
  
  // 物理材质特有属性
  clearcoat: 0,            // 清漆层强度(0-1)
  clearcoatRoughness: 0,   // 清漆层粗糙度
  clearcoatMap: null,      // 清漆层贴图
  clearcoatNormalMap: null,
  clearcoatNormalScale: new THREE.Vector2(1, 1),
  
  // 透明度
  transmission: 0,         // 透光度(0-1)
  thickness: 0,            // 厚度
  attenuationDistance: 0,  // 衰减距离
  attenuationColor: new THREE.Color(0xffffff),
  
  // 折射
  ior: 1.5,               // 折射率(Index of Refraction)
  
  // 光泽
  sheen: 0,               // 光泽强度
  sheenColor: new THREE.Color(0xffffff),
  sheenRoughness: 1,
  
  // 反射
  reflectivity: 0.5,      // 反射率
  envMapIntensity: 1
});

// 示例:玻璃材质
const glassMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0,
  transmission: 0.9,
  transparent: true,
  ior: 1.5,
  thickness: 0.5
});

// 示例:车漆材质
const carPaintMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xff0000,
  metalness: 0.9,
  roughness: 0.1,
  clearcoat: 1,
  clearcoatRoughness: 0.1
});

特殊材质类型

MeshToonMaterial

卡通材质,实现卡通渲染效果。

javascript
const material = new THREE.MeshToonMaterial({
  color: 0x00ff00,
  gradientMap: null  // 渐变纹理
});

// 创建渐变纹理
const gradientTexture = new THREE.DataTexture(
  new Uint8Array([0, 128, 255]),  // 颜色值
  3,  // 宽度
  1   // 高度
);
gradientTexture.needsUpdate = true;

const toonMaterial = new THREE.MeshToonMaterial({
  color: 0xffffff,
  gradientMap: gradientTexture
});

MeshDepthMaterial

深度材质,根据深度渲染物体。

javascript
const material = new THREE.MeshDepthMaterial({
  depthPacking: THREE.BasicDepthPacking,  // 或 RGBADepthPacking
  wireframe: false
});

// 常用于深度预处理或特效

MeshDistanceMaterial

距离材质,用于阴影计算。

javascript
const material = new THREE.MeshDistanceMaterial({
  referencePosition: new THREE.Vector3(0, 0, 0),
  nearDistance: 1,
  farDistance: 100
});

// 由 Three.js 内部使用

线条材质

LineBasicMaterial

基础线条材质。

javascript
const material = new THREE.LineBasicMaterial({
  color: 0x0000ff,
  linewidth: 2,        // 线宽(注:WebGL 限制,通常只能为 1)
  linecap: 'round',    // 线端样式
  linejoin: 'round'    // 线连接样式
});

const points = [
  new THREE.Vector3(-1, 0, 0),
  new THREE.Vector3(1, 0, 0),
  new THREE.Vector3(0, 1, 0)
];

const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);

LineDashedMaterial

虚线材质。

javascript
const material = new THREE.LineDashedMaterial({
  color: 0x00ff00,
  dashSize: 0.1,     // 虚线长度
  gapSize: 0.05,     // 间隔长度
  linewidth: 1
});

const line = new THREE.Line(geometry, material);
line.computeLineDistances();  // 必须调用
scene.add(line);

点材质

PointsMaterial

点材质,用于渲染粒子。

javascript
const material = new THREE.PointsMaterial({
  color: 0xff0000,
  size: 0.1,           // 点大小
  sizeAttenuation: true, // 是否随距离衰减
  map: null,           // 纹理贴图
  transparent: false,
  opacity: 1,
  vertexColors: false  // 是否使用顶点颜色
});

// 创建粒子系统
const particles = new THREE.Points(
  new THREE.BufferGeometry(),
  material
);
scene.add(particles);

精灵材质

SpriteMaterial

精灵材质,始终面向相机的平面。

javascript
const material = new THREE.SpriteMaterial({
  color: 0xffffff,
  map: texture,
  transparent: false,
  opacity: 1,
  rotation: 0,         // 旋转角度
  sizeAttenuation: true
});

const sprite = new THREE.Sprite(material);
sprite.scale.set(1, 1, 1);
scene.add(sprite);

着色器材质

ShaderMaterial

自定义着色器材质。

javascript
const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    color: { value: new THREE.Color(0x00ff00) }
  },
  vertexShader: `
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform float time;
    uniform vec3 color;
    varying vec2 vUv;
    
    void main() {
      float intensity = sin(time + vUv.x * 10.0) * 0.5 + 0.5;
      gl_FragColor = vec4(color * intensity, 1.0);
    }
  `,
  transparent: true
});

// 在动画循环中更新 uniform
function animate() {
  requestAnimationFrame(animate);
  material.uniforms.time.value = performance.now() * 0.001;
  renderer.render(scene, camera);
}

RawShaderMaterial

原始着色器材质,不自动注入内置属性。

javascript
const material = new THREE.RawShaderMaterial({
  uniforms: {
    modelViewMatrix: { value: new THREE.Matrix4() },
    projectionMatrix: { value: new THREE.Matrix4() }
  },
  vertexShader: `
    attribute vec3 position;
    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision mediump float;
    
    void main() {
      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
  `
});

材质通用属性

颜色属性

javascript
// 设置颜色
material.color = new THREE.Color(0xff0000);
material.color.setHex(0x00ff00);
material.color.setRGB(1, 0, 0);
material.color.setHSL(0.5, 1, 0.5);

// 获取颜色
const hex = material.color.getHex();
const rgb = { r: 0, g: 0, b: 0 };
material.color.getRGB(rgb);

透明度

javascript
material.transparent = true;
material.opacity = 0.5;  // 0-1

// 透明度渲染顺序
material.depthWrite = true;   // 是否写入深度缓冲
material.depthTest = true;    // 是否测试深度
material.blending = THREE.NormalBlending;

// 混合模式
THREE.NormalBlending      // 正常混合
THREE.AdditiveBlending    // 加法混合
THREE.SubtractiveBlending // 减法混合
THREE.MultiplyBlending    // 乘法混合

渲染面

javascript
// 单面渲染(正面)
material.side = THREE.FrontSide;

// 单面渲染(背面)
material.side = THREE.BackSide;

// 双面渲染
material.side = THREE.DoubleSide;

多边形偏移

javascript
// 解决 z-fighting 问题
material.polygonOffset = true;
material.polygonOffsetFactor = 1;
material.polygonOffsetUnits = 1;

材质贴图

基础贴图

javascript
const textureLoader = new THREE.TextureLoader();

// 加载贴图
const map = textureLoader.load('diffuse.jpg');
const normalMap = textureLoader.load('normal.jpg');
const roughnessMap = textureLoader.load('roughness.jpg');
const metalnessMap = textureLoader.load('metalness.jpg');
const aoMap = textureLoader.load('ao.jpg');
const emissiveMap = textureLoader.load('emissive.jpg');

const material = new THREE.MeshStandardMaterial({
  map: map,                    // 漫反射贴图
  normalMap: normalMap,        // 法线贴图
  roughnessMap: roughnessMap,  // 粗糙度贴图
  metalnessMap: metalnessMap,  // 金属度贴图
  aoMap: aoMap,                // 环境光遮蔽贴图
  emissiveMap: emissiveMap,    // 自发光贴图
  emissive: new THREE.Color(0xffffff)
});

贴图属性设置

javascript
// UV 变换
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.center.set(0.5, 0.5);

// 过滤方式
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;

// 各向异性过滤
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

材质克隆与复制

克隆材质

javascript
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });

// 浅拷贝
const material2 = material1.clone();
material2.color.setHex(0x00ff00);  // 不影响 material1

// 深拷贝
const material3 = material1.clone();
material3.color = new THREE.Color(0x0000ff);

复制属性

javascript
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial();

// 复制属性
material2.copy(material1);

使用示例

创建多种材质的物体

javascript
import * as THREE from 'three';

// 创建场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建不同材质的球体
const materials = [
  new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }),
  new THREE.MeshLambertMaterial({ color: 0x00ff00 }),
  new THREE.MeshPhongMaterial({ color: 0x0000ff, shininess: 100 }),
  new THREE.MeshStandardMaterial({ color: 0xffff00, metalness: 0.5, roughness: 0.5 })
];

const geometries = materials.map((material, index) => {
  const mesh = new THREE.Mesh(
    new THREE.SphereGeometry(0.5, 32, 32),
    material
  );
  mesh.position.x = (index - 1.5) * 1.5;
  scene.add(mesh);
  return mesh;
});

// 添加光源
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);

const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

camera.position.z = 5;

// 动画
function animate() {
  requestAnimationFrame(animate);
  geometries.forEach(mesh => mesh.rotation.y += 0.01);
  renderer.render(scene, camera);
}
animate();

动态切换材质

javascript
const geometry = new THREE.BoxGeometry(1, 1, 1);

const materials = {
  basic: new THREE.MeshBasicMaterial({ color: 0xff0000 }),
  standard: new THREE.MeshStandardMaterial({ color: 0x00ff00, metalness: 0.5, roughness: 0.5 }),
  phong: new THREE.MeshPhongMaterial({ color: 0x0000ff, shininess: 100 })
};

const mesh = new THREE.Mesh(geometry, materials.basic);
scene.add(mesh);

// 切换材质
let currentMaterial = 'basic';
function switchMaterial() {
  currentMaterial = currentMaterial === 'basic' ? 'standard' : 
                   currentMaterial === 'standard' ? 'phong' : 'basic';
  mesh.material = materials[currentMaterial];
}

// 每秒切换
setInterval(switchMaterial, 1000);

性能考虑

材质数量

javascript
// 不推荐:每个物体使用不同材质
for (let i = 0; i < 100; i++) {
  const material = new THREE.MeshStandardMaterial({ 
    color: new THREE.Color().setHSL(i / 100, 1, 0.5) 
  });
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
}

// 推荐:共享材质
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
for (let i = 0; i < 100; i++) {
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
}

材质编译

javascript
// 预编译材质
renderer.compile(scene, camera);

// 或者延迟编译
material.needsUpdate = true;

最佳实践

材质选择原则

  1. 性能优先:根据场景复杂度选择合适的材质类型

    • 简单场景:优先使用 MeshBasicMaterial
    • 需要光照:MeshLambertMaterial 或 MeshPhongMaterial
    • 真实渲染:MeshStandardMaterial 或 MeshPhysicalMaterial
  2. 共享材质实例:相同外观的物体共享材质实例

    javascript
    // 推荐:共享材质
    const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
    meshes.forEach(mesh => mesh.material = sharedMaterial);
    
    // 避免:重复创建
    meshes.forEach(mesh => {
      mesh.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
    });
  3. 贴图优化

    • 尺寸使用 2 的幂次方(256, 512, 1024, 2048...)
    • 使用压缩格式(JPEG, PNG, KTX)
    • 合理设置过滤方式
  4. 及时释放资源

    javascript
    // 释放材质和贴图
    material.dispose();
    if (material.map) material.map.dispose();
  5. 透明度处理

    • 开启透明时设置 material.transparent = true
    • 注意渲染顺序 material.renderOrder
    • 合理设置深度测试 material.depthWrite

常见问题

1. 材质不显示或显示异常

问题:材质创建后物体不可见或显示不正常。

解决方案

javascript
// 检查光源(Lambert/Phong/Standard 材质需要光源)
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);

// 检查材质是否可见
material.visible = true;

// 检查透明度设置
material.transparent = true;
material.opacity = 0.5;

2. 线框宽度无效

问题:设置了 wireframeLinewidth 但线宽没有变化。

原因:WebGL 限制,大多数浏览器只支持线宽为 1。

解决方案

javascript
// 使用 LineSegments 替代
const edges = new THREE.EdgesGeometry(geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ 
  color: 0xffffff, 
  linewidth: 2 
}));

3. Z-fighting(闪烁问题)

问题:两个重叠面出现闪烁。

解决方案

javascript
// 方法1:多边形偏移
material.polygonOffset = true;
material.polygonOffsetFactor = 1;
material.polygonOffsetUnits = 1;

// 方法2:调整几何体位置
mesh.position.z += 0.001;

4. 透明物体渲染顺序问题

问题:透明物体显示不正确,后面的物体可见性问题。

解决方案

javascript
// 设置渲染顺序
transparentMesh.renderOrder = 1;
opaqueMesh.renderOrder = 0;

// 禁用深度写入
material.depthWrite = false;

5. 贴图不显示

问题:加载了贴图但材质上没有显示。

解决方案

javascript
// 检查 UV 坐标
console.log(geometry.attributes.uv); // 确保存在 UV

// 检查贴图加载状态
textureLoader.load('texture.jpg', (texture) => {
  material.map = texture;
  material.needsUpdate = true;
});

API 参考

Material 基类常用属性

属性类型默认值说明
idnumber自动生成材质唯一标识
uuidstring自动生成UUID
namestring''材质名称
typestring类名材质类型
visiblebooleantrue是否可见
transparentbooleanfalse是否透明
opacitynumber1.0透明度(0-1)
sideSideFrontSide渲染面
depthTestbooleantrue深度测试
depthWritebooleantrue深度写入
blendingBlendingNormalBlending混合模式

Material 基类常用方法

方法说明
clone()克隆材质
copy(source)复制属性
dispose()释放资源
toJSON()转换为 JSON
setValues(values)批量设置属性

相关链接