I am currently implementing "instancing" into my game engine to allow the drawing of 1000 tree models following the tutorial found here: https://learnopengl.com/Advanced-OpenGL/Instancing.
I have the draw call:
glDrawElementsInstanced(GL_TRIANGLES, m_drawCount, GL_UNSIGNED_INT, 0, 100);
Then in my shader I have the uniform:
uniform vec3 A_offsets[100];
This uniform is then used like so:
vec3 offset = A_offsets[gl_InstanceID];
gl_Position = T_MVP * vec4(position + offset, 1.0);
A_offsets
up to this point is empty, so my 100 models (I am testing with 100 first, then when it works I can make this 1000) are draw successfully but in the same position, this was just to test the setup at least works, which it does.
But now I need to fill each index of A_offsets
array and I am a little confused on how to do that. My engine stores the name and type of all uniforms using a parser on my shader files (.glsl
), and in my game loop I get the uniforms by name or type and set values to them in a updateUniforms()
call before the above draw()
call. A simple vec3
uniform is being filled like so:
if(uniformType == "vec3")
SetUniformVector3f(uniformName, material.GetVector3f(uniformName));
The setUnifromVector3f
function is as follows:
void Shader::SetUniformVector3f(const std::string& uniformName, const Vector3f& value) const
{
glUniform3f(m_shaderData->GetUniformMap().at(uniformName), value.GetX(), value.GetY(), value.GetZ());
}
This would set the value of a vec3
unifrom before the draw()
call, my question is this - how would I do the equivalent to set the value of each element (index) in my vec3
array uniform A_offsets
? Ultimately I would want to assign each index of an array (std::vector<Vector3f>
) I have already defined to each index of A_offsets
array. I am yet to find an example online that uses perhaps glUniform3fv(...)
to fill each index of a uniform that is an array.
UPDATE(One day after asking original question):
I have made some progress since asking this question and did not know quite how to convey this, creating a completely new GDSE question seemed silly and commenting on or answering the original question is not really what either of those features are for if all I am doing is giving an update on the problem, so I am just continuing the original question, in a way.
I have made developments that in my opinion have gotten instancing to work! I can see what looks like 1000 tree models drawn in my game with very low frame rate (16ms per frame draw), however, the rest of my game world (the terrain model, water model etc) are definitely "off", their positions are strange and my collision with them is way out of whack.
My slightly updated code is as follows:
Draw call:
glDrawElementsInstanced(GL_TRIANGLES, m_drawCount, GL_UNSIGNED_INT, 0, 1000);
Shader uniform:
uniform vec3 A_offsets[1000];
Uniform in use in the shader:
vec3 offset = A_offsets[gl_InstanceID];
gl_Position = T_MVP * vec4(position + offset, 1.0);
And now the new code, the filling of the uniform array A_offsets
- First, I have a new variable:
glm::vec3 translations[1000];
This then gets filled when I create my simple shader program(I give a random value between a range for the x and z components of the vec3's, y component is just 0):
m_fileName = fileName;
if (fileName == "forward-ambient") {
for (int i = 0; i < 1000; i++) {
translations[i] = glm::vec3(-550 + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (550 - (-550)))), 0, -550 + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (550 - (-550)))));
}
}
And then in my updateUniforms()
call before the draw()
call I pass the translations
array to a function that passes it to the shader to set the values in uniform A_offsets
array:
else if (uniformName.substr(0, 2) == "A_") {//"A_" for "A_offsets"
SetUniformVector3fv(uniformName, translations);
}
function setUniformVector3fv(...)
is as follows:
void Shader::SetUniformVector3fv(const std::string& uniformName, const glm::vec3 test[]) const
{
glUniform3fv(m_shaderData->GetUniformMap().at(uniformName), 1000, (const GLfloat *)&test[0]);
}
The above code (in my mind) fills each index of my uniform array A_offsets
with the value in each index of the previously setup translations
array. I believe this works in rendering 1000 instances of my tree model as can be seen in the following image:
The issue is now this - the rest of my meshes (the terrain and water) are all messed up, in that my collision with them is way off (you can see in the image I am floating quite high above the ground), not to mention the general position of them has changed quite a bit, and my lighting (Directional Light) is not hitting them correctly or at all by the look of it (you can see the whole scene is evenly lit very dimly almost as if only the ambient light is working).
I suspect that the issue is this line in the shader:
gl_Position = T_MVP * vec4(position + offset, 1.0);
Because I have it currently that for things that need drawn normally (the terrain and water meshes) they call this regular draw function:
glDrawElements(GL_TRIANGLES, m_drawCount, GL_UNSIGNED_INT, 0);
But for meshes that need instancing (the trees) I call the new draw function:
glDrawElementsInstanced(GL_TRIANGLES, m_drawCount, GL_UNSIGNED_INT, 0, 1000);
Because of this when the terrain and water meshes are drawn they will enter the shader and at the line of code vec3 offset = A_offsets[gl_InstanceID];
and gl_Position = T_MVP * vec4(position + offset, 1.0);
I have no idea what the value of offset
will be that is effecting the position
as gl_InstanceID
wont even be a thing for the regular draw call (glDrawElements
) so I have no idea what will happend at the line vec3 offset = A_offsets[gl_InstanceID];
, and in fact for when things that dont need instancing are being drawn I dont even want them to use the uniform A_offsets
at all. So I must assume this is effecting the position, collision and lighting of the terrain and water meshes. Ultimately for when the water and terrain meshes are being drawn the code:
uniform vec3 A_offsets[1000];
//...
vec3 offset = A_offsets[gl_InstanceID];
gl_Position = T_MVP * vec4(position + offset, 1.0);
Should just be:
//Not using uniform A_offsets at all
gl_Position = T_MVP * vec4(position, 1.0);
Does anyone have any ideas?
I think a solution could be: depending on weather I am drawing a tree model or not pass updateUniforms()
a bool
, true
for the tree and false
for every other type of mesh, then depending on the value of the bool update a uniform
bool
to be true
or false
, then in the shader depedning on that bool value (if
else
logic) use the following code for true
(the model is my tree):
uniform vec3 A_offsets[1000];
//...
vec3 offset = A_offsets[gl_InstanceID];
gl_Position = T_MVP * vec4(position + offset, 1.0);
And for false
(model is not the tree) use the follwing code:
//Not using uniform A_offsets at all
gl_Position = T_MVP * vec4(position, 1.0);