首页 > 解决方案 > glDrawElementsInstanced: VertexAttribute mat4 not applied per instance

问题描述

I have a problem with rendering multiple instances of an object, using one VertexArrayObject and four VertexBufferObjects.

I cannot get my head around what's wrong with my approach. Here are the basics:

My relatively simple Vertex-Shader-code:

#version 330 core

precision highp float;

layout (location=0) in vec3 position;
layout (location=1) in vec2 texcoord;
layout (location=3) in mat4 modelViewMatrix;

out vec2 textureCoord;

uniform mat4 pr_matrix;

void main() {
    textureCoord = vec2(texcoord.x, texcoord.y);
    
    vec4 mvPos = modelViewMatrix * vec4(position, 1.0);
    gl_Position = pr_matrix * mvPos;
}

As you can see, I try to pass the model view matrix (model and camera_view combined) as an VertexAttribute.

As far as I know, a VertexAttribute is limited to a max of vec4, which means my mat4 will actually take up 4 * vec4 locations.

Each VAO and VBO exists only once. I do not use a separate one for each "gameobject", as some online-tutorials do. Therefore I update each of the Buffers at specific positions. Buf first, let me show you the following code, which initializes them:

// VAO

this.vao = glGenVertexArrays();
glBindVertexArray(this.vao);


// buffer for vertex positions

this.positionVBO = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, this.positionVBO);

//  upload null data to allocate vbo storage in memory
glBufferData(GL_ARRAY_BUFFER, vertexpoints * Float.BYTES, GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);


// buffer for texture coordinates

this.textureVBO = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, this.textureVBO);

glBufferData(GL_ARRAY_BUFFER, texturepoints * Float.BYTES, GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);


// buffer for transform matrices

this.matricesVBO = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, this.matricesVBO);

glBufferData(GL_ARRAY_BUFFER, mtrxsize * Float.BYTES, GL_DYNAMIC_DRAW);

// Byte size of one vec4
int vec4Size = 4 * Float.BYTES;

glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, false, 4 * vec4Size, 0);
glVertexAttribDivisor(3, 1);

glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, false, 4 * vec4Size, 1 * vec4Size);
glVertexAttribDivisor(4, 1);

glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, false, 4 * vec4Size, 2 * vec4Size);
glVertexAttribDivisor(5, 1);

glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, false, 4 * vec4Size, 3 * vec4Size);
glVertexAttribDivisor(6, 1);


//buffer for indices

this.indicesVBO = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indicesVBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, indsize * Integer.BYTES, GL_DYNAMIC_DRAW);


//unbind buffers and array

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

As far as I'm aware, this initialization should correspond to the 4 VertexAttributes, defined in the Vertex-Shader.

For test purposes, I initialize 4 gameobjects (A1, A2, B1, B2), each with:

for each gameobject I push its data to the VBOs, using the following logic:

long vertoff = gameObjectVertOffset;   // offset of the current gameobject's vertex points in position data
long texoff = gameObjectTexOffset;     // offset of the current gameobject's texture points in texture data
long indoff = gameObjectIndOffset;     // offset of the current gameobject's indices in index data
long instoff = gameObjectMatrixOffset; // offset of the current gameobject's matrix (vec4) in matrices data

// upload new position data
if(gameObjectVertBuf.capacity() > 0) {
     gameObjectVertBuf.flip();
     glBindBuffer(GL_ARRAY_BUFFER, this.positionVBO);
     glBufferSubData(GL_ARRAY_BUFFER, vertoff * Float.BYTES, gameObjectVertBuf);
}

// upload new texture data
if(gameObjectTexBuf.capacity() > 0) {
     gameObjectTexBuf.flip();
     glBindBuffer(GL_ARRAY_BUFFER, this.textureVBO);
     glBufferSubData(GL_ARRAY_BUFFER, texoff * Float.BYTES, gameObjectTexBuf);
}

// upload new indices data
if(gameObjectIndBuf.capacity() > 0) {
     gameObjectIndBuf.flip();
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indicesVBO);
     glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, indoff * Integer.BYTES, gameObjectIndBuf);
}

// upload new model matrix data
if(gameObjectMatrixBuf.capacity() > 0) {
     gameObjectMatrixBuf.flip();
     glBindBuffer(GL_ARRAY_BUFFER, this.matricesVBO);
     glBufferSubData(GL_ARRAY_BUFFER, instoff * Float.BYTES, gameObjectMatrixBuf);
}

Now to the actual rendering:

I want to draw element A 2 times and after that, element B 2 times. for the instanced rendering, I group together the gameobjects, I knew i could render in one call, inside lists.

I now have two lists, each with two elements in them:

Once per list I now do:

  numInstances = 2;

  this.vao.bind();

  shaderprogram.useProgram();

  glDrawElementsInstanced(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, (int) (offset * Integer.BYTES), numInstances);

  offset += 6 * numInstances; // 6 indices * 2 instances

The Problem:

This results in the first two elements being rendered correctly, but the second two (from the second list / glDrawElementsInstanced() call) are rendered with the transformation matrices of the first two elements.

It does not matter, which list of objects are rendered first. The second iteration always seems to use the modelViewMatrix attributes from the first ones.

As far as I understood, the glVertexAttribDivisor() call should limit the iteration of the matrices per instance instead of per vertex.

What am I missing here?

标签: javaopengllwjglgldrawarrays

解决方案


后两个(来自第二个列表 / glDrawElementsInstanced() 调用)使用前两个元素的转换矩阵呈现。

这就是你要求做的。系统如何知道它需要使用数组中的两个元素而不是前两个?它所看到的只是另一个绘图调用,它们之间的 VAO 状态没有变化。

系统跟不上之前绘制调用中使用了多少实例。那是你的工作。

现在,您可以更改相关属性的缓冲区绑定,但使用base-instance rendering更容易。在这些绘图函数中,您可以指定应用于实例化属性的实例索引的偏移量。因此,如果要从实例索引 2 开始渲染两个实例,请执行以下操作:

glDrawElementsInstancedBaseInstance(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, (int) (offset * Integer.BYTES), 2, 2);

基础实例渲染是 GL 4.2 的一项功能。


推荐阅读