第16节:自定义几何体 - 从顶点构建3D世界
第16节:自定义几何体 - 从顶点构建3D世界
深入BufferGeometry底层原理与动态地形生成
1. 核心概念解析
1.1 BufferGeometry vs Geometry
特性 | Geometry (传统) | BufferGeometry (现代) |
---|---|---|
数据结构 | 面向对象(顶点对象) | 类型化数组(Float32Array) |
内存效率 | 低(冗余数据) | 高(紧凑存储) |
性能 | 较慢(CPU处理) | 极快(GPU直接读取) |
适用场景 | 简单几何体/学习用途 | 复杂模型/动态几何/性能敏感场景 |
更新机制 | 易修改但性能差 | 难修改但渲染快 |
⚠️ Three.js r125+已弃用
Geometry
,全面转向BufferGeometry
1.2 顶点数据流
2. 构建自定义几何体
2.1 基础三角形创建
// 创建空几何体
const geometry = new THREE.BufferGeometry();// 定义顶点坐标(3个点构成三角形)
const vertices = new Float32Array([// 顶点10, 0, 0, // 顶点21, 0, 0, // 顶点30.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映射
// 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 分形地形生成
// 地形参数配置
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); // 三角形1indices.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 实时地形变形
// 顶点着色器修改
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(实例化渲染)
// 创建基础几何体
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; // xpositions[i * 3 + 1] = Math.random() * 20; // ypositions[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. 学习路线图
7. 常见问题解答
Q1:如何高效更新顶点数据?
// 获取顶点数组引用
const positions = geometry.attributes.position.array;// 直接修改数据
positions[vertexIndex * 3 + 1] = newY; // 修改Y坐标// 标记需要更新
geometry.attributes.position.needsUpdate = true;// 更新法线(可选)
geometry.computeVertexNormals();
Q2:为什么我的自定义几何体没有光照?
- 原因:缺少法线数据
- 解决方案:
- 调用
geometry.computeVertexNormals()
自动计算 - 手动设置法线属性:
const normals = new Float32Array([...]); // 每个顶点法向量 geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
- 调用
Q3:如何实现LOD(多细节层次)?
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创造视觉奇迹!