PDA

View Full Version : Passing array of vec3 to fragment shader



icius74
02-15-2016, 09:44 AM
Hello all,

I've been pulling my hair out trying to figure out why I'm having so much trouble passing an array of glm::vec3 to my fragment shader. I know I'm missing something stupid, but I just can't see it. I'm declaring an array of Point Light positions in my OpenGL code like so:


glm::vec3 pointLightPositions[] = {
glm::vec3( -3.5f, 4.0f, -4.0f),
glm::vec3( 0.0f, 4.0f, -4.0f),
glm::vec3( 3.5f, 4.0f, -4.0f),
glm::vec3( -3.5f, 4.0f, 0.0f),
glm::vec3( 0.0f, 4.0f, 0.0f),
glm::vec3( 3.5f, 4.0f, 0.0f),
glm::vec3( -3.5f, 4.0f, 4.0f),
glm::vec3( 0.0f, 4.0f, 4.0f),
glm::vec3( 3.5f, 4.0f, 4.0f)
};

Then in my fragment shader I do a simple declaration like so:


uniform vec3 pointPositions[9];

Then in my OpenGL code I'm using this to set the values:


for(int i = 0; i < 9; ++i)
{
glUniform3f(glGetUniformLocation(floorShader.Progr am, "pointPositions[i]"),
pointLightPositions[i].x, pointLightPositions[i].y, pointLightPositions[i].z);
}


I get no compile errors with this code and everything seems fine, but it acts like there is no data in the uniform array. (i.e. all of my lights are clustered at 0,0,0).
If populate the array in the shader itself like so:


vec3 pointPositions[9] = vec3[](
vec3( -3.5, 4.0, -4.0),
vec3( 0.0, 4.0, -4.0),
vec3( 3.5, 4.0, -4.0),
vec3( -3.5, 4.0, 0.0),
vec3( 0.0, 4.0, 0.0),
vec3( 3.5, 4.0, 0.0),
vec3( -3.5, 4.0, 4.0),
vec3( 0.0, 4.0, 4.0),
vec3( 3.5, 4.0, 4.0)
);

Then everything works fine and the lights are in their proper positions. What I really want is to be able to use glUniform3fv and pass the whole array in one shot, but I can't seem to figure that out either. (What I really, really want is to use a Uniform Buffer Object, but I won't go into the nightmare time I had trying to do that)

It seems like such a simple request, pass an array of vec3 to the fragment shader. Please help save my sanity and tell me my stupid mistake.

Per the posting guidelines, here is my environment:

OS: Linux Mint 14 64 bit
Graphics Card: Nvidia GeForce GT 630
Driver: Nvidia binary driver 352.63
Using Shaders: YES
OpenGL Core: 3.3
Toolkit: GLFW

GClements
02-15-2016, 11:21 AM
glUniform3f(glGetUniformLocation(floorShader.Progr am, "pointPositions[i]"),


"pointPositions[i]" isn't a valid variable name. You need to replace "i" with the integer literal for the index.

icius74
02-15-2016, 12:19 PM
Well that definitely ranks with stupid mistakes. Thanks for that.


So I took the for loop out of the equation just for testing and ended up with this:




glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[0]"), 1,
glm::value_ptr(pointLightPositions[0]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[1]"), 1,
glm::value_ptr(pointLightPositions[1]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[2]"), 1,
glm::value_ptr(pointLightPositions[2]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[3]"), 1,
glm::value_ptr(pointLightPositions[3]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[4]"), 1,
glm::value_ptr(pointLightPositions[4]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[5]"), 1,
glm::value_ptr(pointLightPositions[5]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[6]"), 1,
glm::value_ptr(pointLightPositions[6]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[7]"), 1,
glm::value_ptr(pointLightPositions[7]));

glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions[8]"), 1,
glm::value_ptr(pointLightPositions[8]));


So this works, but seems quite laborious. Is there no better way other than iterating over the array in some fashion?

I tried this:


glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions"),
9, &pointLightPositions);

Which of course produced this:

error: cannot convert ‘glm::vec3 (*)[9] {aka glm::tvec3<float, (glm::precision)0u> (*)[9]}’ to ‘const GLfloat* {aka const float*}’ in argument passing

fair enough....

Also tried this:


glUniform3fv(glGetUniformLocation(floorShader.Prog ram, "pointPositions"),
9, glm::value_ptr(pointLightPositions));

Which produced this:

error: no matching function for call to ‘value_ptr(glm::vec3 (*)[9])’

Duly noted...

Looking at the OpenGL page on glUniform https://www.opengl.org/sdk/docs/man/html/glUniform.xhtml it says this about the second argument "count"

count
For the vector (glUniform*v) commands, specifies the number of elements that are to be modified. This should be 1 if the targeted uniform variable is not an array, and 1 or more if it is an array.

To me that implies that there is a way to pass an array of vectors to glUniform3fv...but how?

GClements
02-15-2016, 01:12 PM
Is there no better way other than iterating over the array in some fashion?

glUniform3fv().


To me that implies that there is a way to pass an array of vectors to glUniform3fv...but how?

OpenGL itself knows nothing about GLM types; it only uses primitive arithmetic types and arrays of those.

It should suffice to either cast the array (with reinterpret_cast<> or a C-style cast) to a const GLfloat*, or use glm::value_ptr() on the first vector in the array.

icius74
02-15-2016, 02:24 PM
GClements,

Thank you so much. It seems so obvious now. So the winning statement is:


glUniform3fv(glGetUniformLocation(matObjShader.Pro gram, "pointPositions"), 9,
glm::value_ptr(pointLightPositions[0]));

Which works perfectly.

This seems similar in some way to Array Pointer Decay, but I'm too burned out at the moment to make the connection.

Take care. I hope others find this and save themselves the frustration I felt.

GClements
02-15-2016, 04:11 PM
This seems similar in some way to Array Pointer Decay, but I'm too burned out at the moment to make the connection.
The value parameter to glUniform3fv() is a const GLfloat*, i.e. a pointer to a GLfloat. It needs to point to the first of 3 * count values stored contiguously (i.e. like a 1-dimensional array).

If you were using bare arrays (i.e. GLfloat pointLightPositions[9][3]), you'd pass &pointLightPositions[0][0] (or just pointLightPositions[0], which is the same thing in C and C++). C/C++ arrays have no padding between elements, so e.g. arr[A][B][C] and arr[A*B*C] have exactly the same memory layout. Similarly for std::array and std::vector in C++.

All that matters is you have 3*9=27 GLfloats stored contiguously in memory, and you pass a pointer to the first one. All of GLM's vector and matrix types have the same memory layout as an array of their value_type, in order that they can be used like this.

icius74
02-16-2016, 05:10 AM
Thanks again for the explanation, that definitely makes things more clear. I wonder if this concept was the root of my issues with UBOs? I'll have to look into that.

GClements
02-16-2016, 11:44 AM
I wonder if this concept was the root of my issues with UBOs?
With UBOs, the data typically isn't packed (e.g. an array of vec3s will typically be stored as array of vec4s, each with the fourth component unused). You either need to use the std140 layout (and observe the std140 layout rules), or explicitly query the member offsets (and the strides for vectors and matrices).

For variables in the default uniform block, glUniform() will deal with such issues automatically, With UBOs, you have to deal with them yourself.