You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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ń

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 19:02:02