材质基础
材质(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.DoubleSideMeshNormalMaterial
法线材质,将法线方向映射为颜色,常用于调试。
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;最佳实践
材质选择原则
-
性能优先:根据场景复杂度选择合适的材质类型
- 简单场景:优先使用 MeshBasicMaterial
- 需要光照:MeshLambertMaterial 或 MeshPhongMaterial
- 真实渲染:MeshStandardMaterial 或 MeshPhysicalMaterial
-
共享材质实例:相同外观的物体共享材质实例
javascript// 推荐:共享材质 const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); meshes.forEach(mesh => mesh.material = sharedMaterial); // 避免:重复创建 meshes.forEach(mesh => { mesh.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); }); -
贴图优化:
- 尺寸使用 2 的幂次方(256, 512, 1024, 2048...)
- 使用压缩格式(JPEG, PNG, KTX)
- 合理设置过滤方式
-
及时释放资源:
javascript// 释放材质和贴图 material.dispose(); if (material.map) material.map.dispose(); -
透明度处理:
- 开启透明时设置
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 基类常用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | number | 自动生成 | 材质唯一标识 |
uuid | string | 自动生成 | UUID |
name | string | '' | 材质名称 |
type | string | 类名 | 材质类型 |
visible | boolean | true | 是否可见 |
transparent | boolean | false | 是否透明 |
opacity | number | 1.0 | 透明度(0-1) |
side | Side | FrontSide | 渲染面 |
depthTest | boolean | true | 深度测试 |
depthWrite | boolean | true | 深度写入 |
blending | Blending | NormalBlending | 混合模式 |
Material 基类常用方法
| 方法 | 说明 |
|---|---|
clone() | 克隆材质 |
copy(source) | 复制属性 |
dispose() | 释放资源 |
toJSON() | 转换为 JSON |
setValues(values) | 批量设置属性 |