Different colors of instances

I am using instancing to render thousands of objects which are in move.
The instances have different positions, as I have a model array, which contains transformation matrices - one per instance. These matrices are updated every iteration.
All of the instances have the same color, which is sent through an uniform.

Everything works fine for the above, however I want to be able to change the colors of some instances. My idea is to create array of colors (glm::vec3 type), which will be send in similar way to model matrices.

So, take a look at the code below. Initialization takes place here (this is called only once):

	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * positions.size(), positions.data(), GL_STATIC_DRAW); // positions: vertexes of a sphere, they are generated earlier
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
	glEnableVertexAttribArray(0); //0 = location

	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices.size(), indices.data(), GL_STATIC_DRAW);

	// model matrices
	glGenBuffers(1, &bufferMatrices);
	glBindBuffer(GL_ARRAY_BUFFER, bufferMatrices);
	glBufferData(GL_ARRAY_BUFFER, number_of_instances * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

	std::size_t vec4Size = sizeof(glm::vec4);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(1 * vec4Size));
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
	glEnableVertexAttribArray(4);
	glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));

	glVertexAttribDivisor(1, 1);
	glVertexAttribDivisor(2, 1);
	glVertexAttribDivisor(3, 1);
	glVertexAttribDivisor(4, 1);

	// color vectors - this code was added and it does not work
	glGenBuffers(1, &bufferColors);
	glBindBuffer(GL_ARRAY_BUFFER, bufferColors);
	glBufferData(GL_ARRAY_BUFFER, number_of_instances * sizeof(glm::vec3), &colorVectors[0], GL_STATIC_DRAW); // I think, that the problem is here

	std::size_t vec3Size = sizeof(glm::vec3);
	glEnableVertexAttribArray(5);
	glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, vec3Size, (void*)0);

	glVertexAttribDivisor(5, 1);

	glBindVertexArray(0);

I suppose, that the problem is that I can’t call glBufferData() function twice. But how to resolve this problem?

My draw instance method, which contains glDrawElementsInstanced() function is given below (it is called every iteration):

	shader.use();

	glBindVertexArray(VAO);

	//glBindBuffer(GL_ARRAY_BUFFER, bufferMatrices);
	// Model Matrices
	glBufferData(GL_ARRAY_BUFFER, number_of_instances * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

	std::size_t vec4Size = sizeof(glm::vec4);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(1 * vec4Size));
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
	glEnableVertexAttribArray(4);
	glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));

	glVertexAttribDivisor(1, 1);
	glVertexAttribDivisor(2, 1);
	glVertexAttribDivisor(3, 1);
	glVertexAttribDivisor(4, 1);


	//// Color Vectors
	glBufferData(GL_ARRAY_BUFFER, number_of_instances * sizeof(glm::vec3), &colorVectors[0], GL_STATIC_DRAW);

	std::size_t vec3Size = sizeof(glm::vec3);
	glEnableVertexAttribArray(5);
	glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, vec3Size, (void*)0);

	glVertexAttribDivisor(5, 1);

	glDrawElementsInstanced(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0, number_of_instances);

My vertex shader:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in mat4 instanceMatrix;
layout (location = 5) in vec3 instanceColor;
out vec3 Normal;
out vec3 VColor;

uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = aPos; //for a sphere this is true
    gl_Position = projection * view * instanceMatrix * vec4(aPos.x, aPos.y, aPos.z, 1.0f);
    VColor = instanceColor;
}

My fragment shader:

#version 330 core
in vec3 Normal;
in vec3 VColor;
out vec4 FragColor;

struct Light
{
    vec3 direction;
    vec3 ambient;
    vec3 diffuse;
};

uniform vec3 spriteColor;

void main()
{
    Light light;
    light.direction = vec3(-0.2f, -1.0f, -0.3f);
    light.ambient = vec3(0.4f, 0.4f, 0.4f);
    light.diffuse = vec3(0.5f, 0.5f, 0.5f);

    vec3 ambient = light.ambient * VColor; //spriteColor was here earlier instead of VColor

    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(-light.direction);
    float diff = max(dot(norm, lightDir), 0.0f);
    vec3 diffuse = light.diffuse * diff * VColor;

    vec3 result = ambient + diffuse;
    FragColor = vec4(result, 1.0f);
}

I have also tried to create another VAO to call second glBufferData() after binding it, however this did not work.
How can I solve the problem?

Sounds totally reasonable.

I don’t know where your bug is. But I do see that you’re “pushing” all of the per-instance data into the shader via vertex attributes + attr divisors (4x4 matrices, colors, etc.) While you can do this, you can hit a limit with this due to the small max number of vertex attributes. In this case, it’s probably more common to pass the per-instance data into the shader via a UBO, SSBO, or texture and then “pull” it into the shader using gl_InstanceID for indexing into that resource. With this approach, it’s simpler to add more instance data later too.

As an example, see below.

struct InstanceData
{
    mat4    matrix;
    vec4    color;
};

layout( std430, binding = 1 ) buffer InstanceDataArray
{
    InstanceData instance[];

} mySSBO;

...
mat4 mat = mySSBO.instance[ gl_InstanceID ].matrix;

That said, I’d still hunt down your bug. Your approach is fine, it’s just some small detail in the implementation (that I don’t see right-off-the-bat).

1 Like

Um why are you doing a bunch of glVertexAttribPointer calls here? I see that you’re uploading new data to the buffer, but the attribute mapping to buffers is already contained in the VAO.

Also, why did you comment out your bind call? You need that because GL_ARRAY_BUFFER is not part of the VAO. You also need a bind call before you update your colors, because they use a different VAO.

Be aware that this makes base-instance rendering impossible unless you have GL 4.6 or ARB_shader_draw_parameters.

1 Like

Thank you! That is the solution. The problem was, that I did not call the second bind call before calling the second glBufferData() function.
So, the following line was needed before calling glBufferData():

	glBindBuffer(GL_ARRAY_BUFFER, bufferColors);

And of course I have uncommented the first binding (bufferMatrices).
I thought, that it was contained in the VAO.

You are right, they are not needed. I see it now. I needed them before, because when, the GL_ARRAY_BUFFER wasn’t binded, the objects weren’t move without the glVertexAttribPointer calls. But now, since I binded the buffer, I do not need those calls anymore. Thank you.

Dark_Photon thanks for your reply. Maybe your idea will help me in the future.

The VAO stores the buffer associated with each attribute array (i.e. the buffer which was bound to the GL_ARRAY_BUFFER target at the time of the glVertexAttribPointer call), and the current GL_ELEMENT_ARRAY_BUFFER binding. It doesn’t store the current GL_ARRAY_BUFFER binding; that’s context state.

If you want the attributes to source data from a different buffer, you need to re-do the glVertexAttribPointer calls (or use the newer API which replaces glVertexAttribPointer with 3 separate functions: glVertexAttribFormat, glVertexAttribBinding and glBindVertexBuffer). If you change the data in a buffer which is already being used for an attribute, the attribute will continue to use that buffer and will see the updated data.

1 Like