第16节:自定义几何体 - 从顶点构建3D世界
深入BufferGeometry底层原理与动态地形生成

1. 核心概念解析
1.1 BufferGeometry vs Geometry
特性 | Geometry (传统) |
BufferGeometry (现代) |
---|---|---|
数据结构 | 面向对象(顶点对象) | 类型化数组(Float32Array) |
内存效率 | 低(冗余数据) | 高(紧凑存储) |
性能 | 较慢(CPU处理) | 极快(GPU直接读取) |
适用场景 | 简单几何体/学习用途 | 复杂模型/动态几何/性能敏感场景 |
更新机制 | 易修改但性能差 | 难修改但渲染快 |
⚠️ Three.js r125+已弃用
Geometry
,全面转向BufferGeometry
1.2 顶点数据流
顶点坐标 顶点着色器 法线向量 UV坐标 片元着色器 像素输出
2. 构建自定义几何体
2.1 基础三角形创建
javascript
// 创建空几何体
const geometry = new THREE.BufferGeometry();
// 定义顶点坐标(3个点构成三角形)
const vertices = new Float32Array([
// 顶点1
0, 0, 0,
// 顶点2
1, 0, 0,
// 顶点3
0.5, 1, 0
]);
// 设置顶点属性
geometry.setAttribute(
'position',
new THREE.BufferAttribute(vertices, 3) // 3个数值表示一个点
);
// 定义索引(连接顺序)
const indices = new Uint16Array([0, 1, 2]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 计算法线(光照必需)
geometry.computeVertexNormals();
// 创建材质
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
wireframe: false
});
// 生成网格
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
2.2 添加UV映射
javascript
// UV坐标(纹理映射)
const uvs = new Float32Array([
0, 0, // 顶点1对应纹理左下
1, 0, // 顶点2对应纹理右下
0.5, 1 // 顶点3对应纹理顶部
]);
geometry.setAttribute(
'uv',
new THREE.BufferAttribute(uvs, 2) // 2个数值表示一组UV
);
// 应用纹理
const textureLoader = new THREE.TextureLoader();
material.map = textureLoader.load('/textures/rock.jpg');
3. 动态地形生成
3.1 噪声算法对比
算法 | 特点 | 适用场景 | 性能 |
---|---|---|---|
Perlin | 自然连续,梯度噪声 | 地形/云层 | ★★★☆☆ |
Simplex | 计算高效,高维优势 | 实时生成 | ★★★★☆ |
Worley | 细胞状结构 | 石材/皮肤纹理 | ★★☆☆☆ |
Value | 块状效果 | 像素艺术 | ★★★★★ |
3.2 分形地形生成
javascript
// 地形参数配置
const WIDTH = 100; // 地形宽度(顶点数)
const DEPTH = 100; // 地形深度(顶点数)
const SPACING = 0.2; // 顶点间距
const HEIGHT_SCALE = 2; // 高度缩放
// 生成顶点数据
const vertices = [];
for (let z = 0; z < DEPTH; z++) {
for (let x = 0; x < WIDTH; x++) {
// 使用Simplex噪声生成高度
const y = noise.simplex2(x * 0.1, z * 0.1) * HEIGHT_SCALE;
vertices.push(x * SPACING, y, z * SPACING);
}
}
// 创建几何体
const terrainGeometry = new THREE.BufferGeometry();
terrainGeometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3)
);
// 生成索引(三角形面)
const indices = [];
for (let z = 0; z < DEPTH-1; z++) {
for (let x = 0; x < WIDTH-1; x++) {
const a = z * WIDTH + x;
const b = a + 1;
const c = a + WIDTH;
const d = c + 1;
// 两个三角形组成一个面片
indices.push(a, b, c); // 三角形1
indices.push(b, d, c); // 三角形2
}
}
terrainGeometry.setIndex(indices);
terrainGeometry.computeVertexNormals(); // 计算法线
// 添加材质
const material = new THREE.MeshStandardMaterial({
color: 0x3a7c40,
wireframe: false,
flatShading: false
});
const terrain = new THREE.Mesh(terrainGeometry, material);
scene.add(terrain);
3.3 实时地形变形
javascript
// 顶点着色器修改
const positionAttribute = terrain.geometry.getAttribute('position');
const originalVertices = positionAttribute.array.slice(); // 备份原始数据
function deformTerrain() {
const vertices = positionAttribute.array;
const time = performance.now() * 0.001;
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i];
const z = vertices[i + 2];
// 添加波浪效果
const waveY = Math.sin(x * 2 + time) * Math.cos(z * 2 + time) * 0.3;
// 恢复原始高度并添加波动
vertices[i + 1] = originalVertices[i + 1] + waveY;
}
positionAttribute.needsUpdate = true; // 标记需要更新
terrain.geometry.computeVertexNormals(); // 重新计算法线
}
// 每帧更新
function animate() {
requestAnimationFrame(animate);
deformTerrain();
renderer.render(scene, camera);
}
animate();
4. 性能优化技巧
4.1 顶点处理优化
操作 | 正确做法 | 错误做法 |
---|---|---|
几何体更新 | 直接修改ArrayBuffer | 创建新BufferAttribute |
法线计算 | 仅变形后调用computeVertexNormals | 每帧调用 |
内存管理 | 复用BufferGeometry | 频繁创建新几何体 |
4.2 GPU Instancing(实例化渲染)
javascript
// 创建基础几何体
const baseGeometry = new THREE.BoxGeometry(1, 1, 1);
// 创建实例化几何体
const instanceCount = 1000;
const instancedGeometry = new THREE.InstancedBufferGeometry();
instancedGeometry.copy(baseGeometry);
// 生成实例位置
const positions = new Float32Array(instanceCount * 3);
for (let i = 0; i < instanceCount; i++) {
positions[i * 3] = Math.random() * 100 - 50; // x
positions[i * 3 + 1] = Math.random() * 20; // y
positions[i * 3 + 2] = Math.random() * 100 - 50; // z
}
instancedGeometry.setAttribute(
'instancePosition',
new THREE.InstancedBufferAttribute(positions, 3)
);
// 着色器修改
const material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec3 instancePosition;
void main() {
vec3 pos = position + instancePosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `...`
});
const mesh = new THREE.Mesh(instancedGeometry, material);
scene.add(mesh);
### **5. 实战案例:3D分形地形生成器**
```javascript
// 完整地形生成器类
class FractalTerrain {
constructor(width = 100, depth = 100, options = {}) {
this.width = width;
this.depth = depth;
this.options = {
spacing: 0.5,
heightScale: 2,
noiseScale: 0.1,
...options
};
this.geometry = new THREE.BufferGeometry();
this.generate();
}
// 生成地形
generate() {
const { spacing, heightScale, noiseScale } = this.options;
const vertices = [];
const uvs = [];
// 生成顶点
for (let z = 0; z < this.depth; z++) {
for (let x = 0; x < this.width; x++) {
// 分形噪声(多倍频叠加)
let y = 0;
let amplitude = 1;
let frequency = 1;
for (let i = 0; i < 5; i++) {
y += noise.simplex2(
x * noiseScale * frequency,
z * noiseScale * frequency
) * amplitude;
amplitude *= 0.5;
frequency *= 2;
}
y *= heightScale;
vertices.push(x * spacing, y, z * spacing);
uvs.push(x / this.width, z / this.depth);
}
}
// 设置顶点属性
this.geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3)
);
this.geometry.setAttribute(
'uv',
new THREE.Float32BufferAttribute(uvs, 2)
);
// 生成索引
this.generateIndices();
this.geometry.computeVertexNormals();
}
// 生成三角形索引
generateIndices() {
const indices = [];
for (let z = 0; z < this.depth - 1; z++) {
for (let x = 0; x < this.width - 1; x++) {
const a = z * this.width + x;
const b = a + 1;
const c = a + this.width;
const d = c + 1;
indices.push(a, b, c);
indices.push(b, d, c);
}
}
this.geometry.setIndex(indices);
}
// 获取网格对象
getMesh(material) {
return new THREE.Mesh(this.geometry, material);
}
}
// 使用示例
const terrain = new FractalTerrain(200, 200, {
heightScale: 5,
noiseScale: 0.05
});
const material = new THREE.MeshStandardMaterial({
color: 0x3a7c40,
wireframe: false
});
scene.add(terrain.getMesh(material));
6. 学习路线图
基础三角形 参数化几何体 动态顶点更新 噪声地形生成 GPU实例化 ComputeShader
7. 常见问题解答
Q1:如何高效更新顶点数据?
javascript
// 获取顶点数组引用
const positions = geometry.attributes.position.array;
// 直接修改数据
positions[vertexIndex * 3 + 1] = newY; // 修改Y坐标
// 标记需要更新
geometry.attributes.position.needsUpdate = true;
// 更新法线(可选)
geometry.computeVertexNormals();
Q2:为什么我的自定义几何体没有光照?
-
原因:缺少法线数据
-
解决方案:
- 调用
geometry.computeVertexNormals()
自动计算 - 手动设置法线属性:
javascriptconst normals = new Float32Array([...]); // 每个顶点法向量 geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
- 调用
Q3:如何实现LOD(多细节层次)?
javascript
const lod = new THREE.LOD();
// 高细节模型(近处)
const highDetail = generateTerrain(200, 200, 0.05);
highDetail.updateMatrix();
lod.addLevel(highDetail, 0);
// 中细节模型(中距离)
const midDetail = generateTerrain(100, 100, 0.1);
midDetail.updateMatrix();
lod.addLevel(midDetail, 50);
// 低细节模型(远处)
const lowDetail = generateTerrain(50, 50, 0.2);
lowDetail.updateMatrix();
lod.addLevel(lowDetail, 100);
scene.add(lod);
下一节预告:高级材质 - ShaderMaterial揭秘
第17节:用GLSL编写自定义着色器
你将掌握:
-
GLSL语法精髓
- 数据类型/向量操作/矩阵变换
- 片元着色器 vs 顶点着色器
-
特效开发四部曲:
输入参数 顶点变换 光栅化 片元计算
-
实战特效案例:
- 动态波浪水面 🌊
- 全息投影效果 👽
- 地形等高线 🗺️
-
着色器调试技巧:
- 颜色调试法
- 数值可视化工具
🚀 进入图形编程的魔法世界,用代码直接操控GPU创造视觉奇迹!