跳到主要内容

几何体与材质

几何体定义了 3D 物体的形状,材质定义了物体的外观。将几何体和材质结合,就创建了一个可以在场景中显示的网格对象(Mesh)。本章将详细介绍 Three.js 提供的各种几何体和材质类型。

几何体(Geometry)

几何体存储了物体的顶点数据,包括位置、法线、UV 坐标、顶点颜色等信息。Three.js 提供了丰富的内置几何体,也支持自定义几何体。

内置几何体

Three.js 提供了多种常用几何体,可以直接创建使用:

立方体(BoxGeometry)

const geometry = new THREE.BoxGeometry(
1, // 宽度
1, // 高度
1, // 深度
1, // 宽度分段数
1, // 高度分段数
1 // 深度分段数
);

分段数决定了每个面被分成多少个小三角形。分段数越多,后续可以做更多的变形效果,但也会增加顶点数量。

球体(SphereGeometry)

const geometry = new THREE.SphereGeometry(
1, // 半径
32, // 水平分段数
16 // 垂直分段数
);

水平分段数控制经度方向的细分,垂直分段数控制纬度方向的细分。较高的分段数可以让球体更平滑。

圆柱体(CylinderGeometry)

const geometry = new THREE.CylinderGeometry(
1, // 顶部半径
1, // 底部半径
2, // 高度
32 // 径向分段数
);

当顶部半径为 0 时,可以创建圆锥体。

平面(PlaneGeometry)

const geometry = new THREE.PlaneGeometry(
2, // 宽度
2, // 高度
1, // 宽度分段数
1 // 高度分段数
);

平面默认是单面的,从背面看是透明的。如果需要双面显示,需要设置材质的 side 属性。

圆环(TorusGeometry)

const geometry = new THREE.TorusGeometry(
1, // 圆环半径
0.3, // 管道半径
16, // 径向分段数
48 // 管道分段数
);

圆环结(TorusKnotGeometry)

const geometry = new THREE.TorusKnotGeometry(
1, // 半径
0.3, // 管道半径
100, // 管道分段数
16 // 径向分段数
);

圆环结是一种有趣的几何体,常用于展示效果。

其他常用几何体

// 圆锥体
new THREE.ConeGeometry(radius, height, radialSegments);

// 十二面体
new THREE.DodecahedronGeometry(radius);

// 二十面体
new THREE.IcosahedronGeometry(radius);

// 八面体
new THREE.OctahedronGeometry(radius);

// 四面体
new THREE.TetrahedronGeometry(radius);

// 圆形
new THREE.CircleGeometry(radius, segments);

// 圆环
new THREE.RingGeometry(innerRadius, outerRadius, segments);

BufferGeometry

Three.js 现在主要使用 BufferGeometry,它直接操作顶点缓冲区,性能更好:

// 创建空的 BufferGeometry
const geometry = new THREE.BufferGeometry();

// 定义顶点位置
const vertices = new Float32Array([
-1, -1, 0, // 顶点 1
1, -1, 0, // 顶点 2
0, 1, 0 // 顶点 3
]);

// 设置位置属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// 创建网格
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);

BufferAttribute 的第二个参数表示每个顶点的数据维度,3 表示 xyz 三个坐标。

顶点属性

几何体可以存储多种顶点属性:

// 位置属性(必需)
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

// 法线属性(用于光照计算)
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

// UV 坐标(用于纹理映射)
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

// 顶点颜色
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

几何体变换

创建几何体后,可以进行一些变换操作:

// 平移
geometry.translate(x, y, z);

// 旋转
geometry.rotateX(angle);
geometry.rotateY(angle);
geometry.rotateZ(angle);

// 缩放
geometry.scale(x, y, z);

// 居中
geometry.center();

// 计算包围盒
geometry.computeBoundingBox();
geometry.computeBoundingSphere();

包围盒用于视锥体裁剪和碰撞检测,Three.js 会自动计算,但手动修改顶点后需要重新计算。

合并几何体

多个几何体可以合并成一个,减少绘制调用次数,提高性能:

import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';

const geometry1 = new THREE.BoxGeometry(1, 1, 1);
const geometry2 = new THREE.SphereGeometry(0.5, 16, 16);

const mergedGeometry = mergeGeometries([geometry1, geometry2]);

材质(Material)

材质决定了物体的外观,包括颜色、光泽度、透明度、纹理等属性。Three.js 提供了多种材质类型,适用于不同的场景。

基础材质

MeshBasicMaterial

最简单的材质,不受光照影响,只显示颜色或纹理:

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

side 属性可选值:

  • THREE.FrontSide:只渲染正面(默认)
  • THREE.BackSide:只渲染背面
  • THREE.DoubleSide:渲染两面

MeshNormalMaterial

根据法线方向显示颜色,常用于调试:

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

受光照材质

MeshLambertMaterial

基于 Lambert 光照模型的材质,计算漫反射,适合哑光表面:

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

Lambert 材质不计算高光,渲染速度快,适合性能敏感的场景。

MeshPhongMaterial

基于 Phong 光照模型的材质,计算漫反射和高光:

const material = new THREE.MeshPhongMaterial({
color: 0x00ff00,
specular: 0xffffff, // 高光颜色
shininess: 30, // 高光强度
emissive: 0x000000
});

shininess 值越大,高光越集中,表面看起来越光滑。

MeshStandardMaterial

基于物理的渲染(PBR)材质,是最常用的材质类型:

const material = new THREE.MeshStandardMaterial({
color: 0x00ff00, // 基础颜色
metalness: 0.5, // 金属度(0-1)
roughness: 0.5, // 粗糙度(0-1)
emissive: 0x000000, // 自发光颜色
emissiveIntensity: 1,
envMapIntensity: 1 // 环境贴图强度
});

metalness 控制材质的金属感。金属材质会反射环境颜色,非金属材质会显示自身的颜色。

roughness 控制表面的粗糙程度。值为 0 时表面完全光滑,反射清晰;值为 1 时表面非常粗糙,反射模糊。

MeshPhysicalMaterial

MeshStandardMaterial 的扩展,提供更多物理属性:

const material = new THREE.MeshPhysicalMaterial({
color: 0x00ff00,
metalness: 0,
roughness: 0.5,
clearcoat: 1, // 清漆层强度
clearcoatRoughness: 0, // 清漆层粗糙度
transmission: 0, // 透光度(玻璃效果)
thickness: 0.5, // 厚度(影响折射)
ior: 1.5, // 折射率
sheen: 0, // 光泽度
sheenColor: 0xffffff,
sheenRoughness: 0.5
});

transmission 可以创建玻璃、水等透明材质效果。ior(折射率)影响光线的折射程度,水的折射率约为 1.33,玻璃约为 1.5。

特殊材质

MeshToonMaterial

卡通渲染材质,产生分层的卡通着色效果:

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

PointsMaterial

点材质,用于渲染点云:

const material = new THREE.PointsMaterial({
color: 0x00ff00,
size: 0.1,
sizeAttenuation: true // 是否随距离衰减
});

SpriteMaterial

精灵材质,始终面向相机:

const material = new THREE.SpriteMaterial({
color: 0x00ff00,
map: texture,
transparent: true
});

纹理贴图

材质可以使用多种纹理贴图来增加细节:

const textureLoader = new THREE.TextureLoader();

const material = new THREE.MeshStandardMaterial({
map: textureLoader.load('diffuse.jpg'), // 漫反射贴图
normalMap: textureLoader.load('normal.jpg'), // 法线贴图
roughnessMap: textureLoader.load('roughness.jpg'), // 粗糙度贴图
metalnessMap: textureLoader.load('metalness.jpg'), // 金属度贴图
aoMap: textureLoader.load('ao.jpg'), // 环境光遮蔽贴图
emissiveMap: textureLoader.load('emissive.jpg'), // 自发光贴图
alphaMap: textureLoader.load('alpha.jpg') // 透明度贴图
});

法线贴图可以在不增加顶点的情况下模拟表面细节,通过扰动法线方向来产生凹凸效果。

环境光遮蔽贴图在角落和缝隙处添加阴影,增加立体感。

纹理设置

const texture = textureLoader.load('texture.jpg');

// 重复模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);

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

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

// 偏移和旋转
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.center.set(0.5, 0.5);

网格(Mesh)

网格是几何体和材质的组合,是场景中可见的 3D 对象:

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

// 添加到场景
scene.add(mesh);

网格属性

// 位置
mesh.position.set(0, 1, 0);

// 旋转(欧拉角)
mesh.rotation.set(0, Math.PI / 4, 0);

// 缩放
mesh.scale.set(1, 2, 1);

// 是否可见
mesh.visible = true;

// 渲染顺序
mesh.renderOrder = 0;

// 是否投射阴影
mesh.castShadow = true;

// 是否接收阴影
mesh.receiveShadow = true;

更新几何体和材质

// 更换几何体
mesh.geometry = newGeometry;

// 更换材质
mesh.material = newMaterial;

// 更新材质属性
mesh.material.color.set(0xff0000);
mesh.material.needsUpdate = true;

修改材质的某些属性后,需要设置 needsUpdate = true 来通知 Three.js 更新。

共享几何体和材质

多个网格可以共享同一个几何体或材质,节省内存:

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });

for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
);
scene.add(mesh);
}

如果需要每个网格有不同的颜色,可以克隆材质:

const mesh = new THREE.Mesh(geometry, material.clone());
mesh.material.color.set(Math.random() * 0xffffff);

完整示例

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

// 场景设置
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(5, 5, 5);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

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

// 创建各种几何体展示
const geometries = [
new THREE.BoxGeometry(1, 1, 1),
new THREE.SphereGeometry(0.6, 32, 32),
new THREE.CylinderGeometry(0.5, 0.5, 1, 32),
new THREE.TorusGeometry(0.5, 0.2, 16, 48),
new THREE.TorusKnotGeometry(0.4, 0.15, 100, 16),
new THREE.IcosahedronGeometry(0.6)
];

const materials = [
new THREE.MeshStandardMaterial({ color: 0xff6b6b, metalness: 0.3, roughness: 0.4 }),
new THREE.MeshStandardMaterial({ color: 0x4ecdc4, metalness: 0.8, roughness: 0.2 }),
new THREE.MeshStandardMaterial({ color: 0xffe66d, metalness: 0.1, roughness: 0.8 }),
new THREE.MeshStandardMaterial({ color: 0x95e1d3, metalness: 0.5, roughness: 0.5 }),
new THREE.MeshStandardMaterial({ color: 0xf38181, metalness: 0.6, roughness: 0.3 }),
new THREE.MeshStandardMaterial({ color: 0xaa96da, metalness: 0.4, roughness: 0.6 })
];

geometries.forEach((geometry, i) => {
const mesh = new THREE.Mesh(geometry, materials[i]);
mesh.position.x = (i % 3 - 1) * 2.5;
mesh.position.z = (Math.floor(i / 3) - 0.5) * 2.5;
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
});

// 地面
const groundGeometry = new THREE.PlaneGeometry(15, 15);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333344 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.8;
ground.receiveShadow = true;
scene.add(ground);

// 光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.set(2048, 2048);
scene.add(directionalLight);

// 动画
const clock = new THREE.Clock();

function animate() {
requestAnimationFrame(animate);

const elapsed = clock.getElapsedTime();

scene.children.forEach((child, i) => {
if (child.isMesh && child !== ground) {
child.rotation.y = elapsed * 0.5;
child.position.y = Math.sin(elapsed + i) * 0.2;
}
});

controls.update();
renderer.render(scene, camera);
}

animate();

// 响应窗口变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

小结

几何体和材质是创建 3D 物体的两个关键要素:

  • 几何体定义形状,Three.js 提供了丰富的内置几何体,也支持自定义顶点数据
  • 材质定义外观,从简单的 MeshBasicMaterial 到复杂的 MeshPhysicalMaterial
  • 纹理贴图可以增加物体的细节和真实感
  • 网格是几何体和材质的组合,可以设置位置、旋转、缩放等变换

下一章我们将学习光照系统,让场景中的物体产生真实的光影效果。