Search code examples
openglglslshader

How to instance draw with different transformations for multiple objects


Im having a little problem with glDrawArraysInstanced().

Right now Im trying to draw a chess board with pieces.

I have all the models loaded in properly.

Ive tried drawing pawns only with instance drawing and it worked. I would send an array with transformation vec3s to shader through a uniform and move throught the array with gl_InstanceID

That would be done with this for loop (individual draw call for each model):

for (auto& i : this->models) {
    i->draw(this->shaders[0], count);
}

which eventually leads to:

glDrawArraysInstanced(GL_TRIANGLES, 0, vertices.size(), count);

where the vertex shader is:

#version 460

layout(location = 0) in vec3 vertex_pos;
layout(location = 1) in vec2 vertex_texcoord;
layout(location = 2) in vec3 vertex_normal;

out vec3 vs_pos;
out vec2 vs_texcoord;
out vec3 vs_normal;
flat out int InstanceID;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

uniform vec3 offsets[16];

void main(void){
    vec3 offset = offsets[gl_InstanceID];  //saving transformation in the offset 

    InstanceID = gl_InstanceID; //unimportant

    vs_pos = vec4(modelMatrix * vec4(vertex_pos + offset, 1.f)).xyz; //using the offset

    vs_texcoord = vec2(vertex_texcoord.x,1.f-vertex_texcoord.y);

    vs_normal = mat3(transpose(inverse(modelMatrix))) * vertex_normal;

    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertex_pos + offset,1.f); //using the offset
}

Now my problem is that I dont know how to draw multiple objects in this way and change their transformations since gl_InstanceID starts from 0 on each draw call and thus my array with transformations would be used again from the beggining (which would just draw next pieces on pawns positions).

Any help will be appreciated.


Solution

  • You've got two problems. Or rather, you have one problem, but the natural solution will create a second problem for you.

    The natural solution to your problem is to use one of the base-instance rendering functions, like glDrawElementsInstancedBaseInstance. These allow you to specify a starting instance for your instanced rendering calls.

    This will precipitate a second problem: gl_InstanceID does not respect the base instance. It will always be on the range [0, instancecount). Only instance arrays respect the base instance. So instead of using a uniform to provide your per-instance data, you must use instance array rendering. This means storing the per-instance data in a buffer object (which you should have done anyway) and accessing it via a VS input whose VAO specifies that the particular attribute is instanced.

    This also has the advantage of not restricting your instance count to uniform limitations.

    OpenGL 4.6/ARB_shader_draw_parameters allows access to the gl_BaseInstance vertex shader input, which provides the baseinstance value specified by the draw command. So if you don't want to/can't use instanced arrays (for example, the amount of per-instance data is too big for the attribute limitations), you will have to rely on that extension/4.6 functionality. Recent desktop GL drivers offer this functionality, so if your hardware is decently new, you should be able to use it.