几何体与材质
几何体定义了 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
- 纹理贴图可以增加物体的细节和真实感
- 网格是几何体和材质的组合,可以设置位置、旋转、缩放等变换
下一章我们将学习光照系统,让场景中的物体产生真实的光影效果。