Appearance
自定义几何体
虽然 Three.js 提供了丰富的内置几何体,但在实际开发中,我们经常需要创建自定义的几何体来满足特定的需求。通过 THREE.BufferGeometry
,我们可以完全控制几何体的每一个细节,包括顶点位置、面的构成、法线方向以及 UV 坐标等。
自定义几何体是 Three.js 的核心技能之一,掌握它能让你创建出独特的 3D 形状和效果。
BufferGeometry 基础概念
BufferGeometry
是 Three.js 中用于存储几何体数据的高效数据结构。它使用**缓冲区属性(Buffer Attributes)**来存储几何体的各种信息:
- position(位置):顶点的 3D 坐标 (x, y, z)
- normal(法线):用于光照计算的法线向量
- uv(UV 坐标):用于纹理映射的 2D 坐标 (u, v)
- index(索引):定义面的顶点索引
- color(颜色):顶点颜色(可选)
创建简单三角形
让我们从最基础的三角形开始,了解自定义几何体的创建过程:
javascript
// 创建一个自定义的三角形几何体
function createTriangleGeometry() {
const geometry = new THREE.BufferGeometry();
// 定义三个顶点的位置 (x, y, z)
const vertices = new Float32Array([
-1.0,
-1.0,
0.0, // 左下角
1.0,
-1.0,
0.0, // 右下角
0.0,
1.0,
0.0, // 顶部
]);
// 将顶点数据添加到几何体
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
return geometry;
}
// 使用自定义几何体
const geometry = createTriangleGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
创建矩形平面
让我们创建一个更复杂的例子 - 矩形平面,并学习如何使用索引来定义面:
javascript
function createRectangleGeometry(width = 2, height = 2) {
const geometry = new THREE.BufferGeometry();
// 定义四个顶点
const vertices = new Float32Array([
-width / 2,
-height / 2,
0, // 左下 (0)
width / 2,
-height / 2,
0, // 右下 (1)
width / 2,
height / 2,
0, // 右上 (2)
-width / 2,
height / 2,
0, // 左上 (3)
]);
// 定义索引,每三个顶点组成一个三角形面
// 矩形由两个三角形组成
const indices = [
0,
1,
2, // 第一个三角形:左下、右下、右上
2,
3,
0, // 第二个三角形:右上、左上、左下
];
// 设置几何体属性
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
return geometry;
}
添加法线向量
法线向量对于光照计算至关重要。我们可以手动计算或使用 Three.js 的内置方法:
javascript
function createRectangleWithNormals(width = 2, height = 2) {
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
-width / 2,
-height / 2,
0,
width / 2,
-height / 2,
0,
width / 2,
height / 2,
0,
-width / 2,
height / 2,
0,
]);
// 手动定义法线向量(向Z轴正方向)
const normals = new Float32Array([
0,
0,
1, // 顶点0的法线
0,
0,
1, // 顶点1的法线
0,
0,
1, // 顶点2的法线
0,
0,
1, // 顶点3的法线
]);
const indices = [0, 1, 2, 2, 3, 0];
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
geometry.setIndex(indices);
// 或者使用自动计算法线(推荐)
// geometry.computeVertexNormals();
return geometry;
}
UV 坐标映射
UV 坐标是纹理映射的关键,它定义了纹理图像如何贴到 3D 模型上。UV 坐标使用二维坐标系,范围通常是 0 到 1:
javascript
function createRectangleWithUV(width = 2, height = 2) {
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
-width / 2,
-height / 2,
0, // 左下
width / 2,
-height / 2,
0, // 右下
width / 2,
height / 2,
0, // 右上
-width / 2,
height / 2,
0, // 左上
]);
// UV坐标:将纹理完整地映射到矩形上
const uvs = new Float32Array([
0,
0, // 左下对应纹理左下角 (0,0)
1,
0, // 右下对应纹理右下角 (1,0)
1,
1, // 右上对应纹理右上角 (1,1)
0,
1, // 左上对应纹理左上角 (0,1)
]);
const indices = [0, 1, 2, 2, 3, 0];
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
geometry.setIndex(indices);
geometry.computeVertexNormals();
return geometry;
}
// 使用带纹理的材质
const geometry = createRectangleWithUV(4, 4);
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("path/to/texture.jpg");
const material = new THREE.MeshBasicMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
创建立方体几何体
让我们创建一个完整的立方体,展示更复杂的几何体构建:
javascript
function createCubeGeometry(size = 1) {
const geometry = new THREE.BufferGeometry();
const s = size / 2;
// 立方体的8个顶点
const vertices = new Float32Array([
// 前面 (z = s)
-s,
-s,
s, // 0: 左下前
s,
-s,
s, // 1: 右下前
s,
s,
s, // 2: 右上前
-s,
s,
s, // 3: 左上前
// 后面 (z = -s)
-s,
-s,
-s, // 4: 左下后
s,
-s,
-s, // 5: 右下后
s,
s,
-s, // 6: 右上后
-s,
s,
-s, // 7: 左上后
]);
// 定义立方体的12个三角形面(6个面 × 2个三角形)
const indices = [
// 前面
0, 1, 2, 2, 3, 0,
// 后面
4, 6, 5, 6, 4, 7,
// 左面
4, 0, 3, 3, 7, 4,
// 右面
1, 5, 6, 6, 2, 1,
// 顶面
3, 2, 6, 6, 7, 3,
// 底面
4, 5, 1, 1, 0, 4,
];
// 为每个面定义UV坐标
const uvs = new Float32Array([
// 前面
0, 0, 1, 0, 1, 1, 0, 1,
// 后面
0, 0, 1, 0, 1, 1, 0, 1,
]);
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
geometry.computeVertexNormals();
return geometry;
}
UV 坐标进阶技巧
1. 纹理重复
javascript
// 创建重复纹理的UV坐标
const uvs = new Float32Array([
0,
0,
2,
0,
2,
2,
0,
2, // UV值超过1会产生重复效果
]);
// 设置纹理重复模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
2. 纹理偏移和旋转
javascript
// 纹理偏移
texture.offset.set(0.5, 0.5);
// 纹理旋转(绕中心点)
texture.rotation = Math.PI / 4; // 45度
// 纹理缩放
texture.repeat.set(2, 2);
3. 多纹理 UV 映射
javascript
function createMultiUVGeometry() {
const geometry = new THREE.BufferGeometry();
// ... 顶点数据 ...
// 第一套UV坐标(用于基础纹理)
const uv1 = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
// 第二套UV坐标(用于细节纹理)
const uv2 = new Float32Array([
0,
0,
4,
0,
4,
4,
0,
4, // 更高频率的重复
]);
geometry.setAttribute("uv", new THREE.BufferAttribute(uv1, 2));
geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));
return geometry;
}
实用工具函数
计算法线向量
javascript
function calculateNormal(v1, v2, v3) {
const normal = new THREE.Vector3();
const edge1 = new THREE.Vector3().subVectors(v2, v1);
const edge2 = new THREE.Vector3().subVectors(v3, v1);
normal.crossVectors(edge1, edge2).normalize();
return normal;
}
生成球形 UV 坐标
javascript
function sphericalUV(vertex) {
const normal = vertex.clone().normalize();
const u = 0.5 + Math.atan2(normal.z, normal.x) / (2 * Math.PI);
const v = 0.5 - Math.asin(normal.y) / Math.PI;
return [u, v];
}
性能优化建议
- 使用索引:避免重复顶点,减少内存使用
- 合并几何体:使用
BufferGeometryUtils.mergeBufferGeometries()
合并多个几何体 - 适当的精度:根据需要选择
Float32Array
或Float64Array
- 预计算法线:对于静态几何体,预先计算法线向量
总结
自定义几何体的创建涉及以下关键步骤:
- 创建 BufferGeometry 实例
- 定义顶点位置:使用
position
属性 - 设置面索引:使用
setIndex()
方法 - 添加法线向量:手动定义或使用
computeVertexNormals()
- 设置 UV 坐标:用于纹理映射
- 优化性能:合理使用索引和合并几何体
通过掌握这些技能,你可以创建出任何想要的 3D 形状,为你的 Three.js 项目带来无限的创意可能性。
相关资源: