PDA

View Full Version : Sending an array of structs to shader via an Uniform Buffer Object



cevapanda
09-19-2016, 03:59 AM
Hi all!

I've been banging my head with this for a few days now but I can't seem to get this working for some reason.

I have an array of structs in C++ which I'd like to send to my fragment shader by using an uniform buffer object. As much I managed to gather during these past days, my code is currently in this format:

C++:


struct Light {
glm::vec4 Position;
glm::vec4 Color;
float Linear;
float Quadratic;
float Radius;
};

const GLuint NR_LIGHTS = 99;
Light lights[NR_LIGHTS];

shaderLightingPass.Use();
GLuint uniformBlockIndexLights = glGetUniformBlockIndex(shaderLightingPass.Program, "LightBlock");
glUniformBlockBinding(shaderLightingPass.Program, uniformBlockIndexLights, 0);

// Uniform buffer object for lights
GLuint uboLights;
glGenBuffers(1, &uboLights);
glBindBuffer(GL_UNIFORM_BUFFER, uboLights);
glBufferData(GL_UNIFORM_BUFFER, sizeof(lights), NULL, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, uniformBlockIndexLights, uboLights);
glBindBuffer(GL_UNIFORM_BUFFER, 0);


Then in Lighting Pass I set the individual lights in a for loop and set the values per array and after that send them to the UBO:



for (int i = 0; i < NR_LIGHTS; i++)
{
lights[i].Position = glm::vec4(lightPositions[i], 1.0f);
lights[i].Color = glm::vec4(lightColors[i], 1.0f);

const GLfloat constant = 1.0;
const GLfloat linear = 0.7;
const GLfloat quadratic = 1.8;
lights[i].Linear = linear;
lights[i].Quadratic = quadratic;

const GLfloat maxBrigthness = std::fmaxf(std::fmaxf(lightColors[i].r, lightColors[i].g), lightColors[i].b);
GLfloat radius = (-linear + sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * maxBrigthness))) / (2 * quadratic);
lights[i].Radius = radius;
}

glBindBuffer(GL_UNIFORM_BUFFER, uboLights);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(lights), lights);
glBindBuffer(GL_UNIFORM_BUFFER, 0);



In my fragment shader I've defined the struct and the uniform block like this:


const int NR_LIGHTS = 99;

struct Light {
vec3 Position;
vec3 Color;
float Linear;
float Quadratic;
float Radius;
};

layout (std140) uniform LightBlock {
Light lights[NR_LIGHTS];
};


And in the lighting calculations I'll sample the lights[i].values normally.

I used an uniform array previously for this, but it requires me to set the uniform values individually in a for loop and as far as I've understood it's better to use an uniform buffer for this kind of purpose.

What I'd like to know here is that what is the correct way to send this kind of an array of structs to the shader? Obviously this way isn't working right now. I've tried also a few other ways to send the data, e.g. sending each lights[i] array separately and including the offset in the glBufferSubData().

Any help would be greatly appreciated. I've been looking around both here on the forums and docs, but I just can't seem to form the sending part correctly in my C++ code. Thank you in advance!

john_connor
09-19-2016, 04:58 AM
i'm not sure, but i think you have to make the memory of 1 struct "Light" consuming X * sizeof(vec4)

right now, your struct Light (as it is defined in your c-code) consumes:

struct Light {
vec4 Position; // 1 x sizeof(vec4)
vec4 Color; // 1 x sizeof(vec4)
float Linear;
float Quadratic;
float Radius; // 3 x sizeof(float)...
};

by adding a "padding" variable at the end of the struct, it should work:

struct Light {
vec4 Position;
vec4 Color;
float Linear;
float Quadratic;
float Radius;
float PADDING;
};

look for more explicit infos in here:
http://www.opengl.org/registry/doc/glspec45.core.pdf
(page 137/138)

you can explicitly set the binding point index of the "LightBlock" by doing this:


layout (std140, binding = 1) uniform LightBlock {
Light lights[NR_LIGHTS];
};

then in your c-code:


// allocate memory for light buffer
glBindBuffer(GL_UNIFORM_BUFFER, mylightbuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(Light) * NR_LIGHTS, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

// bind light buffer to location 1
glBindBufferBase(GL_UNIFORM_BUFFER, 1, mylightbuffer);

now you should have access to "mylightbuffer" in your shaders

cevapanda
09-19-2016, 10:38 AM
i'm not sure, but i think you have to make the memory of 1 struct "Light" consuming X * sizeof(vec4)

right now, your struct Light (as it is defined in your c-code) consumes:

struct Light {
vec4 Position; // 1 x sizeof(vec4)
vec4 Color; // 1 x sizeof(vec4)
float Linear;
float Quadratic;
float Radius; // 3 x sizeof(float)...
};

by adding a "padding" variable at the end of the struct, it should work:

struct Light {
vec4 Position;
vec4 Color;
float Linear;
float Quadratic;
float Radius;
float PADDING;
};


Hi and thanks for the quick reply! I tried this out and you are indeed right. It's working perfectly now for me.
This is very embarrassing as I thought that I had understood how to do the layout correctly and there was nothing wrong with it. So I thought it had to be something else that's not working.
I'll make sure to study the parts of the document you linked :)

Thank you again!