OpenGL 4.6批量渲染无画面输出问题求助
核心问题分析
你的批量渲染程序仅显示深色背景,核心问题集中在顶点数据处理、OpenGL状态设置、内存管理三个方面,具体如下:
1. 顶点属性数组被禁用
构造函数中设置完顶点属性指针后,立即调用了glDisableVertexAttribArray(0/1/2),导致渲染时OpenGL无法读取顶点位置、颜色和纹理坐标数据。
// 错误代码: glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (8 * sizeof(float)), (void*)0); glDisableVertexAttribArray(0); // 禁用后渲染时无法读取顶点数据
2. 顶点数据大小计算完全错误
push方法中用指针值相减计算已存储顶点数据大小,这是逻辑错误——指针值是内存地址,并非数据长度:
// 错误代码: int size1 = *(batches[batches.size() - 1].verticies + 1) - *batches[batches.size() - 1].verticies;
实际需要在batch_data中维护变量,追踪当前已添加的顶点数据量。
3. glBufferSubData数据大小参数错误
调用glBufferSubData时,用sizeof(batch.verticies)作为数据大小,这是指针的字节数(64位系统为8字节),并非实际顶点数据的字节数,导致仅极少量数据被上传到GPU:
// 错误代码: glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(batch.verticies), &batch.verticies[0]);
4. EBO索引数据不足以支持批量渲染
当前EBO仅存储了一组四边形的6个索引,批量渲染多个四边形时,glDrawElements会重复使用这组索引,导致顶点索引越界或重复绘制同一物体,无法正确渲染。
5. 内存管理混乱
- 每次
push分配新内存但不释放旧内存,引发内存泄漏; init_batch用malloc分配内存,后续用new,分配/释放方式不统一,易引发内存错误;- 未正确初始化
batch_data的顶点计数变量,无法追踪数据长度。
6. 纹理坐标计算错误
当前纹理坐标直接使用顶点位置加0.5,会导致纹理坐标超出0-1的标准范围,若纹理环绕方式设置为GL_CLAMP_TO_EDGE,超出部分会采样边缘像素,甚至因坐标完全错误显示黑色。
修复步骤
1. 启用顶点属性数组并保持启用
修改构造函数,移除所有glDisableVertexAttribArray调用,确保顶点属性数组始终处于启用状态:
Batch2D::Batch2D(int const &m_count) { count = m_count; program = getMinimalProgram("texture_vertex.glsl", "texture_fragment.glsl"); GLuint const indices[] = { 0, 1, 3, // first triangle 1, 2, 3 // second triangle }; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, (sizeof(float) * 32) * count, NULL, GL_DYNAMIC_COPY); glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW); // position attribute glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (8 * sizeof(float)), (void*)0); // color attribute glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, (8 * sizeof(float)), (void*)(3 * sizeof(float))); // texture coord attribute glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, (8 * sizeof(float)), (void*)(6 * sizeof(float))); // 移除所有glDisableVertexAttribArray调用 }
2. 修正顶点数据大小追踪
修改batch_data结构,添加变量记录当前总float数量:
typedef struct { GLsizei index_count; // 原count改为index_count,记录索引数量 GLuint texture; float* verticies; int vertex_float_count; // 新增:记录当前总float数量(每个顶点8个float) GLuint* elements; int elements_count; }batch_data;
修改init_batch初始化该变量:
void Batch2D::init_batch(batch_data &batch) { batch.index_count = 0; batch.vertex_float_count = 0; batch.texture = 0; batch.verticies = new float[count*32]; // 统一用new分配内存 }
修改push方法中的数据处理逻辑:
void Batch2D::push(vec2 const vec[4]) { if (batches.empty()) { batch_data b; init_batch(b); batches.push_back(b); } auto& current_batch = batches.back(); // 检查当前批次是否已满(每个四边形占32个float) if (current_batch.vertex_float_count + 32 > count*32) { flush(); batch_data b; init_batch(b); batches.push_back(b); current_batch = batches.back(); } float vertices[] = { // positions // colors // texture coords vec[0].x,vec[0].y, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right 修正为标准纹理坐标 vec[1].x,vec[1].y, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right vec[2].x,vec[2].y, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left vec[3].x,vec[3].y, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left }; // 复制新顶点数据到当前批次 std::copy(vertices, vertices + 32, current_batch.verticies + current_batch.vertex_float_count); current_batch.vertex_float_count += 32; current_batch.index_count += 6; // 每个四边形对应6个索引 }
3. 修正glBufferSubData的大小参数
修改draw方法中的数据上传代码:
void Batch2D::draw(batch_data const &batch) { glBindTexture(GL_TEXTURE_2D, batch.texture); glUseProgram(program); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 正确计算数据大小:总float数 * float字节数 glBufferSubData(GL_ARRAY_BUFFER, 0, batch.vertex_float_count * sizeof(float), batch.verticies); glBindVertexArray(VAO); // 改用glDrawArrays避免EBO索引不足问题,每个四边形对应2个三角形(6个顶点) glDrawArrays(GL_TRIANGLES, 0, batch.vertex_float_count / 8 * 3); }
4. 修复内存泄漏
在flush方法中释放每个批次的顶点内存:
void Batch2D::flush() { for (batch_data &b : batches) { draw(b); delete[] b.verticies; // 释放new分配的内存 } batches.clear(); }
额外建议
- 优先使用
std::vector<float>代替原始指针管理顶点数据,避免手动内存管理错误; - 批量渲染不同纹理的物体时,按纹理分组,每个纹理对应一个批次,减少纹理切换开销;
- 若坚持使用EBO批量渲染,需为每个批次生成对应索引数据(每个四边形的索引偏移为4的倍数);
- 可使用
glMapBuffer代替glBufferSubData,提升动态数据上传效率。
内容的提问来源于stack exchange,提问作者Wojciech Nowocień

