Help with glDrawElementsInstanced

Started by
4 comments, last by Nanoha 7 years, 7 months ago

SOLVED

I've been reading through the OpenGL instancing tutorial on learnopengl.com, and I'm trying to implement it using glDrawElementsInstanced, since I have Assimp doing my mesh loading. My problem is that the "floor" (see the wood-textured model in the attached pic) is drawing perfectly fine, but the positional offsets for instancing that I'm passing to OpenGL have no effect. What could be the problem? Thanks in advance! biggrin.png

floorVS.glsl:


#version 430
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 offset; // For 3D instancing

layout (std140, binding = 0) uniform Matrices {
  mat4 projection;
  mat4 view;
};
uniform mat4 model;

out vec2 TexCoords;
out vec3 FragPos;
out vec3 Normal;

void main() {
  gl_Position = projection * view * model * vec4(position + offset, 1.0); // Add provided offset
  FragPos = vec3(model * vec4(position, 1.0f));
  Normal = mat3(transpose(inverse(model))) * normal;
  TexCoords = texCoords;
}

I have a Model class that contains numerous Mesh classes (since a complete model may contain several objects), so I need to make sure each mesh gets the offset data:

This function is run per mesh to separately add instancing data:


void Mesh::SetInstancing(const std::vector<glm::vec3>& instanceOffsets) {
	m_instanceOffsets = instanceOffsets;

	glGenBuffers(1, &m_instanceVBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_instanceVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * m_instanceOffsets.size(), &m_instanceOffsets.at(0), GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Vertex Instance offset
	glEnableVertexAttribArray(3);
	glBindBuffer(GL_ARRAY_BUFFER, m_instanceVBO);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glVertexAttribDivisor(3, 1); // Tell OpenGL this is an instanced vertex attribute.

	glBindVertexArray(0);
}

Now to draw the mesh. The bindTexures function is irrelevant to my question (it just loops through a vector of Textures and binds them):


void Mesh::DrawInstanced(const Shader& shader) {
	
	bindTextures(shader);

	// Draw instanced mesh
	glBindVertexArray(m_vao);
	glDrawElementsInstanced(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, 0, m_instanceOffsets.size());
	glBindVertexArray(0);
}

Picture:

There should be 2 floor object being rendered here (and the displayed floor should not be at the origin):

[attachment=33104:help.png]

Please let me know of any more clarification is needed!

Advertisement

I may be missing something here so please disregard if I am being an idiot but are you sure you want offsets to be a vertex attribute rather than a uniform? From what I understand, your vertex code would allow you to individually move each vertex independently rather than move the whole thing at once. Is that what you are trying to achieve?

Could you also show the code where you setup these arrays? position/normal/texture/offsets.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

*snip*

Here is the code for the other arrays (this function is called in each Mesh's c-tor) and the Vertex struct (if it helps any). The setup for the offset attribute is separate because some models may not need to be drawn using instancing.:


void Mesh::setupMesh() {

	glGenVertexArrays(1, &m_vao);
	glGenBuffers(1, &m_vbo);
	glGenBuffers(1, &m_ebo);

	glBindVertexArray(m_vao);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

	glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex), &m_vertices.at(0), GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(GLuint), &m_indices.at(0), GL_STATIC_DRAW);

	// Vertex Attributes

	// Vertex Positions
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
	// Vertex Normals
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));
	// Vertex Texture Coords
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));

	glBindVertexArray(0);
}

Vertex:


struct Vertex {
	glm::vec3 Position;
	glm::vec3 Normal;
	glm::vec2 TexCoords;
};

And yes, this technically should allow me to move each vertex individually (learnopengl.com discussed how the same mesh data is just shared to the other instance, except for an offset from the original position variable in the Vertex struct, instead of allocating a new "floor" model and wasting memory).

At the bottom of your SetInstancing method you bind the vertex array to 0 but I don't see it being bound initially. From your other method it looks like you are binding m_vao at the start then binding it to 0 at the end of the methods. Have you perhaps just missed binding m_vao on that one particular method?

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

At the bottom of your SetInstancing method you bind the vertex array to 0 but I don't see it being bound initially. From your other method it looks like you are binding m_vao at the start then binding it to 0 at the end of the methods. Have you perhaps just missed binding m_vao on that one particular method?

That fixed it! I'm still trying to wrap my head around OpenGL being STATE-DRIVEN (it doesn't help that I'm mixing object oriented C++ and procedural OpenGL, but I guess that's part of the learning process) rolleyes.gif

Thanks so much! biggrin.png

At the bottom of your SetInstancing method you bind the vertex array to 0 but I don't see it being bound initially. From your other method it looks like you are binding m_vao at the start then binding it to 0 at the end of the methods. Have you perhaps just missed binding m_vao on that one particular method?

That fixed it! I'm still trying to wrap my head around OpenGL being STATE-DRIVEN (it doesn't help that I'm mixing object oriented C++ and procedural OpenGL, but I guess that's part of the learning process) rolleyes.gif

Thanks so much! biggrin.png

Yeah little things like this are easy to miss. I sometimes make little objects that ensure a thing is bound and unbind in the destructor similar to how lock guards work and I throw one at the start of my function.

e.g.


TextureBinder(int desired)
{
   m_desired = desired;
   // get currently bound texture and store it
   m_previous = GetCurrentTexture();
   // if it's not the desired texture then bind it
   if(m_desired != m_previous)
      BindTexture(m_desireD);
}

~TextureBinder()
{
   // rebind the previous one if it was different
   if(m_desired != m_previous)
      BindTexture(m_previous);
}

// Then in a method where I am messing with that texture:
void A::DoSomething()
{
   TextureBinder binder(m_texture);
   // modify it
   // it is automatically reset to the previous when the method ends
}

That does add some overhead (resetting the value) but if you are doing it anyway then it's no worse and it does help avoid little mistakes like the one you had there.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

This topic is closed to new replies.

Advertisement