第16节:自定义几何体 - 从顶点构建3D世界

发布于:2025-08-18 ⋅ 阅读:(12) ⋅ 点赞:(0)

第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 基础三角形创建
// 创建空几何体
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映射
// 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); // 三角形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 实时地形变形
// 顶点着色器修改
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; // 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:如何高效更新顶点数据?

// 获取顶点数组引用
const positions = geometry.attributes.position.array;

// 直接修改数据
positions[vertexIndex * 3 + 1] = newY; // 修改Y坐标

// 标记需要更新
geometry.attributes.position.needsUpdate = true;

// 更新法线(可选)
geometry.computeVertexNormals(); 

Q2:为什么我的自定义几何体没有光照?

  • 原因:缺少法线数据
  • 解决方案:
    1. 调用geometry.computeVertexNormals()自动计算
    2. 手动设置法线属性:
    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编写自定义着色器

你将掌握

  1. GLSL语法精髓

    • 数据类型/向量操作/矩阵变换
    • 片元着色器 vs 顶点着色器
  2. 特效开发四部曲

    输入参数
    顶点变换
    光栅化
    片元计算
  3. 实战特效案例

    • 动态波浪水面 🌊
    • 全息投影效果 👽
    • 地形等高线 🗺️
  4. 着色器调试技巧

    • 颜色调试法
    • 数值可视化工具

🚀 进入图形编程的魔法世界,用代码直接操控GPU创造视觉奇迹!


网站公告

今日签到

点亮在社区的每一天
去签到