Skip to content

材质,华丽的外衣

材质(Material)是 Three.js 中非常重要的概念,它决定了物体表面的外观和光照响应特性。如果说几何体是物体的骨架,那么材质就是物体的皮肤和外衣,给物体带来丰富多彩的视觉效果。通过不同的材质类型和属性配置,我们可以模拟出各种真实世界中的材料效果,如金属、塑料、玻璃、布料等。

在 Three.js 中,每个 Mesh 对象都需要一个几何体(Geometry)和一个材质(Material):

javascript
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);

材质决定了物体的外观特征:

  • 颜色(Color):物体的基础颜色
  • 透明度(Opacity):物体的透明程度
  • 纹理(Texture):贴在物体表面的图像
  • 光照响应:物体如何与光源交互
  • 反射和折射:物体的反光和透光特性

常用材质

以下材质在开发过程中较为常见,各种材质的构建速度从最快到最慢:MeshBasicMaterialMeshLambertMaterialMeshPhongMaterialMeshStandardMaterialMeshPhysicalMaterial。构建速度越慢的材质,做出的场景越逼真。

1、MeshBasicMaterial 基础材质

MeshBasicMaterial 是最简单的材质类型,它不受光照影响,总是显示为同样的颜色。这种材质渲染速度最快,适合用于简单的效果或调试时使用。

javascript
const material = new THREE.MeshBasicMaterial({
  color: 0x00ff00, // 绿色
  transparent: true, // 启用透明
  opacity: 0.8, // 透明度
  wireframe: false, // 是否显示线框
  side: THREE.FrontSide, // 渲染哪一面
});

主要特点:

  • 不受光照影响
  • 性能最佳
  • 颜色均匀分布

适用场景:

  • UI 元素
  • 调试模型
  • 简单的颜色展示
  • 对性能要求极高的场景

2、MeshLambertMaterial 兰伯特材质

MeshLambertMaterial 是一种非光泽表面的材质,具有漫反射特性。它能够很好地模拟无光泽表面,如粗糙的纸张、石头或未抛光的木材,但不能模拟具有镜面高光的光泽表面(例如涂漆木材)。

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

主要特点:

  • 漫反射光照模型
  • 性能较好
  • 受环境光和方向光影响

适用场景:

  • 粗糙表面材料,如纸张、布料
  • 需要简单光照的场景

3、MeshPhongMaterial 冯氏材质

MeshPhongMaterial 是一种具有镜面高光的材质,能够产生光泽效果。它使用 Phong 反射模型,可以模拟金属、塑料等有光泽的材料。

javascript
const material = new THREE.MeshPhongMaterial({
  color: 0x00ff00,
  specular: 0x111111, // 镜面反射颜色
  shininess: 30, // 光泽度
  emissive: 0x000000,
  emissiveIntensity: 0,
  transparent: false,
  opacity: 1.0,
});

主要特点:

  • 具有镜面高光
  • 能模拟光泽表面

适用场景:

  • 金属,塑料、陶瓷等

4、MeshStandardMaterial 标准材质

MeshStandardMaterial 是基于物理的渲染(PBR)材质,是 Three.js 中最先进和最真实的材质类型。它使用金属度和粗糙度来控制材质的外观,能够更准确地模拟真实世界的材料。

javascript
const material = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
  metalness: 0.0, // 金属度 (0-1)
  roughness: 0.5, // 粗糙度 (0-1)
  emissive: 0x000000,
  emissiveIntensity: 0,
  envMapIntensity: 1.0, // 环境贴图强度
  transparent: false,
  opacity: 1.0,
});

主要特点:

  • 基于物理的渲染(PBR)
  • 最真实的光照效果
  • 支持金属度和粗糙度
  • 适合需要高质量渲染的场景

金属度(Metalness)

  • 0.0:非金属材料(木材、塑料、石头)
  • 1.0:纯金属材料(铁、铜、金)

粗糙度(Roughness)

  • 0.0:完全光滑,像镜子
  • 1.0:完全粗糙,无反射

适用场景

  • 现代 PBR 渲染
  • 需要真实感的场景
  • 金属和非金属材料
  • 大多数生产环境

5、MeshPhysicalMaterial 物理材质

MeshPhysicalMaterialMeshStandardMaterial 的扩展,提供了更多的物理特性,如透明度、折射、光泽层等高级效果。也正因如此,其性能会低于MeshStandardMaterial

javascript
const material = new THREE.MeshPhysicalMaterial({
  color: 0x00ff00,
  metalness: 0.0,
  roughness: 0.0,
  transmission: 1.0, // 透射率
  thickness: 1.0, // 厚度
  ior: 1.5, // 折射率
  clearcoat: 1.0, // 透明涂层
  clearcoatRoughness: 0.0, // 涂层粗糙度
});

主要特点:

  • 支持透明和折射效果
  • 清漆涂层效果
  • 最高质量的渲染
  • 性能消耗最大

适用场景:

  • 玻璃、水晶等透明材料
  • 汽车漆面效果
  • 高端产品展示
  • 对视觉质量要求最高的场景

6、常用材质示例

下面在同一个场景中创建不同的材质,便于观察各材质的区别。

image-20250803214604863

javascript
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

// 场景对象
const scene = new THREE.Scene();
scene.background = new THREE.Color("#000000");

// 渲染器
const canvas = document.querySelector("#canvas") as HTMLCanvasElement;
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 开启阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 软阴影

// 相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 创建环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

// 创建主光源
const mainLight = new THREE.DirectionalLight(0xffffff, 1);
mainLight.position.set(5, 8, 5);
mainLight.castShadow = true;
mainLight.intensity = 2;
scene.add(mainLight);

// 创建辅助光源
const secondaryLight = new THREE.DirectionalLight(0xffffff, 0.3);
secondaryLight.position.set(-5, 5, -5);
scene.add(secondaryLight);

// 创建球体几何体(所有球体使用相同的几何体)
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);

// 从左到右按顺序创建5个球体
// 1. MeshBasicMaterial - 基础材质
const basicMaterial = new THREE.MeshBasicMaterial({
  color: "#ff6b6b",
  wireframe: false,
});
const basicSphere = new THREE.Mesh(sphereGeometry, basicMaterial);
basicSphere.position.set(-8, 0, 0);
scene.add(basicSphere);

// 2. MeshLambertMaterial - 兰伯特材质
const lambertMaterial = new THREE.MeshLambertMaterial({
  color: "#4ecdc4",
});
const lambertSphere = new THREE.Mesh(sphereGeometry, lambertMaterial);
lambertSphere.position.set(-4, 0, 0);
scene.add(lambertSphere);

// 3. MeshPhongMaterial - 冯氏材质
const phongMaterial = new THREE.MeshPhongMaterial({
  color: "#45b7d1",
  shininess: 100, // 光泽度,值越大越亮
  specular: "#ffffff", // 镜面反射颜色
});
const phongSphere = new THREE.Mesh(sphereGeometry, phongMaterial);
phongSphere.position.set(0, 0, 0);
scene.add(phongSphere);

// 4. MeshStandardMaterial - 标准材质
const standardMaterial = new THREE.MeshStandardMaterial({
  color: "#f7b731",
  metalness: 0.3, // 金属度:0=非金属,1=完全金属
  roughness: 0.4, // 粗糙度:0=光滑镜面,1=完全粗糙
});
const standardSphere = new THREE.Mesh(sphereGeometry, standardMaterial);
standardSphere.position.set(4, 0, 0);
scene.add(standardSphere);

// 5. MeshPhysicalMaterial - 物理材质
const physicalMaterial = new THREE.MeshPhysicalMaterial({
  color: "#a55eea",
  metalness: 0.7,
  roughness: 0.2,
  clearcoat: 1.0, // 清漆层强度
  clearcoatRoughness: 0.1, // 清漆层粗糙度
  transmission: 0.1, // 透射程度
  thickness: 0.5, // 厚度(影响透射效果)
});
const physicalSphere = new THREE.Mesh(sphereGeometry, physicalMaterial);
physicalSphere.position.set(8, 0, 0);
scene.add(physicalSphere);

// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 窗口大小改变时,更新渲染器和相机
window.addEventListener("resize", () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
});

PBR材质

PBR 材质,全称是 Physically Based Rendering(基于物理的渲染) 材质,是一种更加真实模拟光照与材质交互的渲染方式。它广泛用于现代 3D 引擎(如 Three.js、Unity、Unreal)中,以提升图像的真实感。上述常用材质中的MeshStandardMaterial MeshPhysicalMaterial属于PBR材质。

1、核心理念

PBR 不是某种具体的材质,而是一种渲染方法论,它基于物理原理模拟光的行为,让材质在不同光照环境下具有一致且真实的外观。

2、常见属性

PBR 材质通常使用以下几种贴图或参数:

属性含义说明
Albedo / Base Color材质的基础颜色,不包含阴影、光照或高光。
Metallic(金属度)控制表面是否为金属,非金属为 0,纯金属为 1。
Roughness(粗糙度)控制表面光滑或粗糙程度,0 是非常光滑(有清晰高光),1 是完全粗糙(无高光)。
Normal Map模拟表面细节纹理(如凹凸),不改变几何形状。
AO(Ambient Occlusion)环境光遮蔽模拟角落等区域的阴影,增加空间感。
Height / Displacement提供表面凹凸的实际位移效果(可选,用于高级效果)。

3、属性示例

(1)roughness(粗糙度)从0-1的变化,图中从左到右的值依次为:0, 0.25, 0.5, 0.75, 1.0

image-20250803222014352

javascript
for (let i = 0; i < 5; i++) {
    const roughness = i / 4; // 0, 0.25, 0.5, 0.75, 1.0

    const material = new THREE.MeshStandardMaterial({
      color: 0x00ff00, // 绿色
      roughness: roughness,
      metalness: 0.0, // 非金属
    });

    const mesh = new THREE.Mesh(sphereGeometry, material);
    mesh.castShadow = true;
    mesh.position.set(i * 3 - 6, 2, 6);
    scene.add(mesh);
  }

(2)metalness(金属度)从0-1的变化,图中从左到右的值依次为:0, 0.25, 0.5, 0.75, 1.0

image-20250803222433927

javascript
for (let i = 0; i < 5; i++) {
    const metalness = i / 4; // 0, 0.25, 0.5, 0.75, 1.0

    const material = new THREE.MeshStandardMaterial({
      color: 0xff6600, // 橙色
      roughness: 0.1, // 保持较低粗糙度以突出金属效果
      metalness: metalness,
    });

    const mesh = new THREE.Mesh(sphereGeometry, material);
    mesh.position.set(i * 3 - 6, 2, 2);
    mesh.castShadow = true;
    scene.add(mesh);
  }

ShaderMaterial材质

1、基本概念

在 Three.js 中,ShaderMaterial(着色器材质) 是一种允许你使用 自定义 GLSL 着色器代码 来完全控制材质外观的方式。它是 Three.js 提供的最底层、最灵活的材质类型,适合需要实现复杂视觉效果或非标准渲染的场景。

很多酷炫的效果都是使用它来完成的,例如渐变的立方体等效果。关于该材质的更多介绍将在后续文章中做详细说明,此处只了解基本概念即可。

2、示例效果

通过ShaderMaterial材质创建一个渐变的立方体。

image-20250803224740957

javascript
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

// 场景对象
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);

// 渲染器
const canvas = document.querySelector("#canvas") as HTMLCanvasElement;
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);

// 相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(200, 200, 0);
camera.lookAt(0, 0, 0);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 创建网格
const size = 100;
const divisions = 10;
const gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);

// 创建立方体
const geometry = new THREE.BoxGeometry(20, 60, 20);

// 创建渐变材质
const material = new THREE.ShaderMaterial({
  uniforms: {
    topColor: { value: new THREE.Color(0.5, 0.8, 1.0) },
    bottomColor: { value: new THREE.Color(0.1, 0.1, 0.5) },
  },
  vertexShader: `
    varying vec2 vUv;
    varying vec3 vNormal;
    void main() {
      vUv = uv;
      vNormal = normal;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 topColor;
    uniform vec3 bottomColor;
    varying vec2 vUv;
    varying vec3 vNormal;
    void main() {
      vec3 color;
      if (vNormal.y > 0.99) {
        color = bottomColor;  // 底部颜色
      } else if (vNormal.y < -0.99) {
        color = topColor;     // 顶部颜色
      } else {
        color = mix(topColor, bottomColor, vUv.y); 
      }
      gl_FragColor = vec4(color, 1.0);
    }
  `,
});

const mesh = new THREE.Mesh(geometry, material);
mesh.position.y += 30;
scene.add(mesh);

// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 窗口大小改变时,更新渲染器和相机
window.addEventListener("resize", () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
});