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

使用glVertexAttribDivisor的Transform VBO致实例化渲染尺寸异常

实例化渲染多尺寸四边形时尺寸异常问题

我尝试用实例化渲染实现多尺寸四边形(由两个三角形组成),配置了包含顶点数据VBO、变换数据VBO和索引EBO的VAO。但使用Transform VBO后,glDrawElementsInstanced忽略了各四边形的实际尺寸,所有四边形都用同一尺寸——右侧本该是左侧一半大小的四边形也和左侧一样大。如果把transform值设为0,四边形尺寸正确但位置不对。


相关代码

渲染初始化代码

Renderer::Renderer(Shader* staticShader)
    : m_ShaderStatic(staticShader)
{
    // Generate all needed buffers
    glGenVertexArrays(1, &m_VAO);
    glGenBuffers(1, &m_VertVBO);
    glGenBuffers(1, &m_TransVBO);
    glGenBuffers(1, &m_EBO);

    // Bind VAO
    glBindVertexArray(m_VAO);
    
    // Bind and set up vertex VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_VertVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GL_FLOAT) * 2, NULL, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 2, (void*)0);
    glEnableVertexAttribArray(0);

    // Bind and set up transform VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_TransVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GL_FLOAT) * 2, NULL, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 2, (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribDivisor(1, 1);

    // Bind and set up EBO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 6, NULL, GL_DYNAMIC_DRAW);

    // Unbind everything
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

添加数据的AddStatic函数

void Renderer::AddStatic(float x, float y, float width, float height, float rotation)
{
    // Bind VAO
    glBindVertexArray(m_VAO);

    // Configure vertex data
    for (int i = 0; i < 8; i+=2)
    {
        m_Vertices.push_back(QuadVertices[i] * width);
        m_Vertices.push_back(QuadVertices[i+1] * height);
    }
    // Set vertex data
    glBindBuffer(GL_ARRAY_BUFFER, m_VertVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_Vertices.size(), m_Vertices.data(), GL_DYNAMIC_DRAW);

    // Configure transform data
    m_Transforms.push_back(x);
    m_Transforms.push_back(y);
    // Set transform data
    glBindBuffer(GL_ARRAY_BUFFER, m_TransVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_Transforms.size(), m_Transforms.data(), GL_DYNAMIC_DRAW);

    // Configure index data
    m_Indices.push_back(0 + 4 * m_QuadCount);
    m_Indices.push_back(1 + 4 * m_QuadCount);
    m_Indices.push_back(3 + 4 * m_QuadCount);
    m_Indices.push_back(3 + 4 * m_QuadCount);
    m_Indices.push_back(2 + 4 * m_QuadCount);
    m_Indices.push_back(0 + 4 * m_QuadCount);
    // Set index data
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * m_Indices.size(), m_Indices.data(), GL_DYNAMIC_DRAW);

    // Unbind all
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    // Increment the amount of quads loaded
    m_QuadCount++;
}

绘制调用代码

void Renderer::Render()
{
    // Bind VAO
    glBindVertexArray(m_VAO);
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
    
    // Bind Shader
    m_ShaderStatic->use();

    // Draw
    glDrawElementsInstanced(GL_TRIANGLES, m_Indices.size(), GL_UNSIGNED_INT, 0, m_QuadCount);
}

顶点着色器代码

#version 330 core

layout (location = 0) in vec2 vertex;
layout (location = 1) in vec2 transform;

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};

out vec4 color;

void main()
{
    gl_Position = projection * view * vec4(vertex + transform, 0.0, 1.0);
    color = vec4(1.0, 1.0, 1.0, 1.0);
}

问题根源

你完全搞错了实例化渲染的核心逻辑:

  1. 顶点VBO误用:顶点VBO应该只存储单个四边形的归一化基础顶点(比如[-0.5,-0.5], [0.5,-0.5], [0.5,0.5], [-0.5,0.5]),所有实例复用这组顶点。但你现在把每个四边形的缩放后顶点都塞进VBO,而顶点属性0的divisor是0(每个顶点取新值),导致所有实例都读取VBO的前4个顶点(第一个四边形的数据),所以尺寸全部一致。
  2. 实例化逻辑错误:当你把transform设为0时,本质是在做普通非实例化渲染(只是误用了glDrawElementsInstanced),此时索引累加能读取到所有四边形的顶点数据,所以尺寸正确,但因为没有平移参数,位置错误。

修复步骤

1. 修正顶点VBO的初始化

顶点VBO只存一组归一化基础顶点,后续不再修改:

// 假设QuadVertices是归一化基础顶点:
float QuadVertices[] = {-0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f};

// 在Renderer构造函数中初始化顶点VBO:
glBindBuffer(GL_ARRAY_BUFFER, m_VertVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(QuadVertices), QuadVertices, GL_STATIC_DRAW);

2. 扩展实例属性,添加缩放数据

新增缩放VBO作为实例属性,每个实例单独存储宽高:

// 在Renderer构造函数中添加缩放实例属性(location=2)
glGenBuffers(1, &m_ScaleVBO);
glBindBuffer(GL_ARRAY_BUFFER, m_ScaleVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GL_FLOAT)*2, NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT)*2, (void*)0);
glEnableVertexAttribArray(2);
glVertexAttribDivisor(2, 1); // 标记为实例属性,每个实例取一次值

3. 修改AddStatic函数,仅更新实例属性和索引

不再修改顶点VBO,只维护实例的平移、缩放数据,索引只需初始化一次:

void Renderer::AddStatic(float x, float y, float width, float height, float rotation)
{
    glBindVertexArray(m_VAO);

    // 添加实例的平移与缩放数据
    m_Transforms.push_back(x);
    m_Transforms.push_back(y);
    m_Scales.push_back(width);
    m_Scales.push_back(height);

    // 更新平移VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_TransVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*m_Transforms.size(), m_Transforms.data(), GL_DYNAMIC_DRAW);
    // 更新缩放VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_ScaleVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*m_Scales.size(), m_Scales.data(), GL_DYNAMIC_DRAW);

    // 索引仅初始化一次,所有实例复用同一组索引
    if(m_QuadCount == 0){
        unsigned int baseIndices[] = {0,1,3,3,2,0};
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(baseIndices), baseIndices, GL_STATIC_DRAW);
    }

    m_QuadCount++;
    glBindVertexArray(0);
}

4. 修改顶点着色器,应用缩放+平移

先对基础顶点做缩放,再叠加平移:

#version 330 core

layout (location = 0) in vec2 vertex;
layout (location = 1) in vec2 transform;
layout (location = 2) in vec2 scale;

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};

out vec4 color;

void main()
{
    vec2 scaledVertex = vertex * scale; // 先缩放基础顶点
    gl_Position = projection * view * vec4(scaledVertex + transform, 0.0, 1.0); // 再加平移
    color = vec4(1.0, 1.0, 1.0, 1.0);
}

5. 修正绘制调用

因为所有实例复用同一组索引,绘制时传入固定的索引数量6,实例数为四边形总数:

void Renderer::Render()
{
    glBindVertexArray(m_VAO);
    m_ShaderStatic->use();
    glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, m_QuadCount);
}

额外优化建议

  • 可以把平移和缩放合并为一个vec4实例属性(xy为平移,zw为缩放),减少VBO数量,提升性能。
  • 使用glBufferSubData代替glBufferData更新实例属性,避免每次重新分配内存。
  • 如果需要旋转,可以将实例属性扩展为完整的mat4变换矩阵,支持更复杂的空间变换。

内容的提问来源于stack exchange,提问作者Pandabear1125

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:03:19