使用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); }
问题根源
你完全搞错了实例化渲染的核心逻辑:
- 顶点VBO误用:顶点VBO应该只存储单个四边形的归一化基础顶点(比如
[-0.5,-0.5], [0.5,-0.5], [0.5,0.5], [-0.5,0.5]),所有实例复用这组顶点。但你现在把每个四边形的缩放后顶点都塞进VBO,而顶点属性0的divisor是0(每个顶点取新值),导致所有实例都读取VBO的前4个顶点(第一个四边形的数据),所以尺寸全部一致。 - 实例化逻辑错误:当你把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
相关产品推荐
相关产品推荐

