OpenGL 3.3中如何分别存储顶点、颜色、索引数据以优化体素场景渲染
方案合理性确认
你提出的优化思路完全合理,是体素类项目主流的成熟渲染方案,相比实例化立方体的实现性能提升可达10倍以上,且无纹理依赖的前提下实现成本极低。你提到的相邻面共用顶点重复存储的问题,这套方案可以完美规避,同时逐四边形存储颜色的设计也不会出现属性冗余。
渲染流程配置步骤
1. 预准备静态基础数据
提前预存渲染所需的固定数据,不用每个chunk重复存储:
- 预存单位立方体6个朝向的四边形顶点偏移,每个面4个顶点,用整数存储即可,示例:
// 6个面分别为:东、西、南、北、上、下 const int face_offsets[6][4][3] = { {{1,0,0}, {1,1,0}, {1,1,1}, {1,0,1}}, // 东面 // 剩下5个面按相同规则补充即可 }; - 预存单个四边形的三角形索引,每个四边形拆为2个三角形共6个索引:
const unsigned int quad_indices[] = {0,1,2, 0,2,3}
2. 配置渲染管线(实例化渲染方案,最贴合你的需求)
用OpenGL实例化渲染实现逐四边形传属性,完全不需要重复存储顶点:
- 初始化静态VAO/VBO/IBO,存储单个通用四边形的顶点数据
// 通用单位四边形局部顶点,z轴固定为0 const float quad_vertices[] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f }; unsigned int quad_VBO, quad_IBO, quad_VAO; glGenVertexArrays(1, &quad_VAO); glBindVertexArray(quad_VAO); glGenBuffers(1, &quad_VBO); glBindBuffer(GL_ARRAY_BUFFER, quad_VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0); glEnableVertexAttribArray(0); glGenBuffers(1, &quad_IBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_IBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW); - 每个chunk维护实例数据数组,存储每个可见四边形的专属属性
// 单四边形实例数据结构 struct QuadInstance { int pos[3]; // 四边形所属方块的世界坐标原点 unsigned char face_dir; // 面朝向,对应预存的6个方向 unsigned char color[3]; // 面颜色 }; std::vector<QuadInstance> chunk_quads; // 每个chunk单独维护,面更新时重写 - 配置实例属性的顶点指针,设置逐实例更新的规则
unsigned int instance_VBO; glGenBuffers(1, &instance_VBO); glBindBuffer(GL_ARRAY_BUFFER, instance_VBO); glBufferData(GL_ARRAY_BUFFER, chunk_quads.size()*sizeof(QuadInstance), chunk_quads.data(), GL_DYNAMIC_DRAW); // 实例位置属性 glVertexAttribPointer(1, 3, GL_INT, GL_FALSE, sizeof(QuadInstance), (void*)offsetof(QuadInstance, pos)); glEnableVertexAttribArray(1); glVertexAttribDivisor(1, 1); // 每1个实例更新一次属性 // 面朝向属性 glVertexAttribPointer(2, 1, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(QuadInstance), (void*)offsetof(QuadInstance, face_dir)); glEnableVertexAttribArray(2); glVertexAttribDivisor(2, 1); // 颜色属性 glVertexAttribPointer(3, 3, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(QuadInstance), (void*)offsetof(QuadInstance, color)); glEnableVertexAttribArray(3); glVertexAttribDivisor(3, 1); - 渲染调用,一个draw call完成单个chunk所有面的渲染
glBindVertexArray(quad_VAO); glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, chunk_quads.size()); - Shader逻辑补充
在顶点着色器中根据面朝向计算顶点的最终世界坐标即可,示例GLSL代码:
#version 330 core layout (location = 0) in vec3 aLocalPos; layout (location = 1) in ivec3 aBlockPos; layout (location = 2) in int aFaceDir; layout (location = 3) in vec3 aColor; out vec3 vColor; // 预存6个面的顶点偏移,和CPU端的定义保持一致 const ivec3 face_offsets[6][4] = { {{1,0,0}, {1,1,0}, {1,1,1}, {1,0,1}}, // 剩下5个面补充完整 }; uniform mat4 mvp; void main() { // 计算最终世界坐标 ivec3 vertex_offset = face_offsets[aFaceDir][int(aLocalPos.x + 2*aLocalPos.y)]; vec3 world_pos = vec3(aBlockPos + vertex_offset); gl_Position = mvp * vec4(world_pos, 1.0); vColor = aColor; }
基础框架搭建建议
- 方块数据用1字节的类型ID存储即可,最多支持256种不同材质的方块,足够落沙模拟使用
- Chunk建议采用16x16x16的尺寸,坐标转数组索引用
index = x + y*16 + z*256即可,计算效率高 - 网格生成阶段先做面剔除:只有当前方块的相邻面是空气时,才生成对应朝向的面,可直接砍掉90%左右的不可见面,大幅降低渲染压力
内容的提问来源于stack exchange,提问作者Will Mungas
相关产品推荐
相关产品推荐

