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?
the second two (from the second list / glDrawElementsInstanced() call) are rendered with the transformation matrices of the first two elements.
That's what you asked to do. How would the system know that it would need to use the second two elements from the array instead of the first two? All it sees is another draw call, and there are no changes to VAO state between them.
The system doesn't keep up with how many instances have been used in prior draw calls. That's your job.
Now, you could change the buffer binding for the attributes in question, but it's easier to use base-instance rendering. In these drawing functions, you specify an offset that is applied to the instance index for instanced attributes. So if you want to render two instances starting at instance index 2, you do this:
glDrawElementsInstancedBaseInstance(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, (int) (offset * Integer.BYTES), 2, 2);
Base instanced rendering is a GL 4.2 feature.