Recommended Posts

Ichi    223

Hello,

i got a quick question about passing a float array to a shader and receiving it as a struct defined inside the shader.

both my light structs (application and shader) contain 15 floats in the same order.

now if i send my array of structs of 15 floats with

glUniform1fv(glGetUniformLocation(programID, "lights"), 15 * lights.size(), lights[0].data());

uniform float lights[MAX_LIGHTS * 15];

everything works fine but i would to get it this way

uniform Light lights[MAX_LIGHTS];

which doesnt seem to work :/

so how do i not receive an array of numlights * 15 floats but an array of light objects with 15 floats?

Share on other sites
Wh0p    407

I had a similar Problem. Make sure gGetUniformLocation is not -1 (thats what I got when using an array of struct).

Generally i woud suggest to keep away from the array of structs idea and instead use a struct of array, that is a struct like

struct Lights

{

vec3 positions[MAX_LIGHTS];

vec3 intensity[MAX_LIGHTS];

...

};

in many cases this woul be more cache efficient (think about updating on the client).

And then there would be another approach by using a samplerBuffer for each component in your shader, that you can texelFetch your lights from. This would enable you to have a variable count of lightsources.

Hope this could help

Edited by Wh0p

Share on other sites
tanzanite7    1410

uniform float lights[MAX_LIGHTS * 15];

uniform Light lights[MAX_LIGHTS];

Did you account for the padding?

I do not see how is Light defined - but it likely has some padding (check the spec). Also, did you define a standard layout for the structure?

Share on other sites
Ichi    223

yes the uniformlocaion of an object allways seems to be -1. it only works if i ask it for a member like "lights.pos".

isnt there any way to get the uniform location of an object and write byte for byte into it like i tryed in my first post?

I now use a struct of arrays which feels very uncomfortable in my application. my light class looks like this
http://pastebin.com/80hK9UNJ

http://pastebin.com/unUHbDND

now i have to make funktions for all events like copy a light, add one, remove one and so on...

i'd more like to still have the array of structs in my application which as you said is not cache efficient in lots of cases and would mean i'd have to convert all my lights to arrays if i want to give them to the shader.

do you may have any advice?

did you define a standard layout for the structure?

standard layout? i did define following layout: http://pastebin.com/SGgSTzCy

Edited by Ichi

Share on other sites
tanzanite7    1410

standard layout? i did define following layout: http://pastebin.com/SGgSTzCy

So, no layout whatsoever. If you do not ask for some standard layout then the driver is free to reorder all the stuff in whatever way it wants ... hence why:

yes the uniformlocaion of an object allways seems to be -1. it only works if i ask it for a member like "lights.pos".

... you have to query every member location separately etc.

Ask for fixed/standardised layout, like std140 / std430. Further information: http://www.opengl.org/wiki/Interface_Block_%28GLSL%29

Share on other sites
Wh0p    407

now i have to make funktions for all events like copy a light, add one, remove one and so on...

The most convenient method for this would to store the lights in a textureBuffer (http://www.opengl.org/wiki/Buffer_Texture) and get rid of the uniform struct array.

If I look at the light struct of yours you'll need 15 floats. So you could use a GL_RGBA32F format and put the lights into 4 aligned texels.

Then just write a getter function in you shader that looks like this

uniform samplerBuffer LightsBuffer;

[...]

Light SampleLight (int id)
{
id *= 4;
Light light;
vec4 tmp = texelFetch (LightsBuffer, id);
lights.pos = tmp;
tmp = texelFetch (LightsBuffer, id + 1);
lights.color = tmp.xyz;
lights.constantAttenuation = tmp.w;
tmp = texelFetch (LightsBuffer, id + 2);
lights.linearAttenuation = tmp.x;
lights.spotCutoff = tmp.z;
lights.spotExponent = tmp.w;
tmp = texelFetch (LightsBuffer, id + 3);
lights.spotDir = tmp.xyz;
}


This way you just would have to implement an update method in your light class, that copies the light data into your texture.

Hopefully I could give you an idea.

Share on other sites
HappyCoder    5052

I would recommend using Uniform Buffer Objects. You create a buffer, similar to a vertex buffer, that holds only uniform data. In your c++ code you can copy structured data into the buffer that matches structured data in the glsl code.

C++ Code

struct Light
{
Vector4f lightPosition;
Vector4f lightColor;
Vector4f lightAttenuation;
};

Light* lights = new Light[lightCount];

// populate light info here

// create the buffer, you only need to do this once
GLuint uniformBuffer;
glGenBuffers( 1, &uniformBuffer );
glBindBuffer( GL_UNIFORM_BUFFER, uniformBuffer );

// fill the buffer, you do this anytime any of the lights change
glBufferData( GL_UNIFORM_BUFFER, sizeof(Light) * lightCount, lights,
GL_DYNAMIC_DRAW );
// binding to index 0, the glsl code needs to specify this same
// index using 'layout(binding=0)'
glBindBufferBase( GL_UNIFORM_BUFFER, 0, uniformBuffer );

GLSL code

#version 420

// Match the structure in the C++ code
// I don't use vec3 because padding
// rules sometimes differ between c++
// and OpenGL
struct Light
{
vec4 position;
vec4 color;
vec4 attenuation;
};

// force this block to be assigned to index 0
// std140 specifies that the structured data should
// follow a specific set of packing rules. This will
// make the packing consistent across OpenGL implementations
layout(binding=0,std140) uniform LightBlock
{
Light lights[lightCount];
};

void main()
{
// use the lights
vec4 foo = lights[0].position;
//...
};

One advantage of this approach is you can use a struct in C++ to specify the data. This is much more intuitive. Also, you can use the same buffer and only need to bind it once between draws calls that use the same data.

Edited by HappyCoder