🔧 Phase 0.5: 缓冲区与属性深度解析

理解 WebGL 数据管理的核心机制

💡 本节核心问题

1. 交错数组 vs 独立数组:位置和颜色数据应该放在一个缓冲区还是分开?

2. 索引缓冲区:如何避免重复定义顶点?(立方体 8 个顶点 vs 36 个顶点)

3. VAO(顶点数组对象):如何简化状态管理?

📦 方案 1: 独立数组(Separate Arrays)

位置和颜色分别存储在两个独立的缓冲区中

// 位置缓冲区 const positions = [x1,y1,z1, x2,y2,z2, ...] // 颜色缓冲区 const colors = [r1,g1,b1, r2,g2,b2, ...]
优点 代码清晰,易于理解
缺点 需要多次绑定切换

🔀 方案 2: 交错数组(Interleaved Array)

位置和颜色交错存储在同一个缓冲区中

// 交错数据 const data = [ x1,y1,z1, r1,g1,b1, // 顶点1 x2,y2,z2, r2,g2,b2, // 顶点2 ... ]
优点 性能更好,缓存友好
缺点 配置稍复杂

❌ 方案 3: 不使用索引(重复顶点)

正方形需要 6 个顶点(2 个三角形 × 3 个顶点)

// 6 个顶点(有重复) const vertices = [ -0.5,-0.5, 0.5,-0.5, 0.5,0.5, // 三角形1 -0.5,-0.5, 0.5,0.5, -0.5,0.5 // 三角形2 ]
顶点数 6 个(重复 2 个)
内存 浪费 33%

✅ 方案 4: 使用索引(Element Array)

正方形只需 4 个顶点 + 6 个索引

// 4 个顶点(无重复) const vertices = [-0.5,-0.5, 0.5,-0.5, 0.5,0.5, -0.5,0.5] // 6 个索引 const indices = [0,1,2, 0,2,3]
顶点数 4 个(节省 33%)
内存 更高效

📊 性能对比:立方体案例

方案 顶点数量 索引数量 内存占用 性能
不使用索引 36 个(6 面 × 2 三角形 × 3 顶点) 0 36 × 6 floats = 864 字节 ❌ 重复数据多
使用索引 8 个(立方体 8 个角) 36 个 8 × 6 floats + 36 × 2 bytes = 264 字节 ✅ 节省 70% 内存

🎯 vertexAttribPointer 参数详解

gl.vertexAttribPointer( index, // attribute 位置 size, // 每个顶点的分量数(1-4) type, // 数据类型(gl.FLOAT, gl.BYTE 等) normalized, // 是否归一化(true/false) stride, // 步长(字节) offset // 偏移量(字节) )

stride(步长):从一个顶点的数据开始到下一个顶点的数据开始的字节数

offset(偏移量):当前属性在顶点数据中的起始位置(字节)

独立数组的参数

// 位置缓冲区 gl.vertexAttribPointer( aPosition, 3, // vec3 gl.FLOAT, false, 0, // stride = 0(紧密排列) 0 // offset = 0 )

交错数组的参数

// 位置(在交错数组中) gl.vertexAttribPointer( aPosition, 3, // vec3 gl.FLOAT, false, 24, // stride = 6 floats × 4 bytes 0 // offset = 0(位置在前) ) // 颜色(在交错数组中) gl.vertexAttribPointer( aColor, 3, // vec3 gl.FLOAT, false, 24, // stride = 6 floats × 4 bytes 12 // offset = 3 floats × 4 bytes )