Passing in many lights without using up uniforms? Maybe in a texture?

I don’t care about performance for now, this is just to get a scalable solution for passing in lights. For now I will probably just use a few anyway but I want to set things up for either deferred lighting or maybe tiled forward rendering.

Seems like I could make a 1d texture and then send all my float data in and unpack on the other end. Or is this going to be slow, or perhaps be impossible? I don’t see any reference to using a method like this from google and I don’t want to waste a bunch of time on it if it’s a loser.

I don’t see how people are passing in these ‘hundreds of lights’ these deferred and tiled rendering algorithms require, though. They must be doing it somehow!

this is just to get a scalable solution for passing in lights

How do you define “scalable”? Scalability is usually defined in terms of performance. After all, any solution will work if you throw enough hardware at it. You could re-render each mesh for every light, using additive blending to add the different contributions together.

More to the point, what is not “scalable” about uniforms? Uniform blocks can store, on GL 4.x hardware, at least 16KB of data. Even if you’re talking point lights, which have position and intensity, that’s 512 lights, minimum. A 1D texture might be able to beat that, but there’s no guarantee.

Now, a buffer texture is another matter entirely. Buffer texture storage is generally much larger than uniform block storage, but with the downside that they are effectively memory fetches, so they may be slower than UBO access.

Buffer textures though don’t have a whole lot of utility in GL 4.x, since we now have SSBOs. They’re like UBOs, only you could write to them (but you don’t have to use them that way) and they have a lot more storage. The minimum storage there is 16MB, with most implementations allowing you to make them as big as memory.

Thanks for your answer, Alfonse.

I’ve been making an engine for my game a while now but I am still pretty inexperienced with OGL. I didn’t realize that you could store so much data in a UBO, I thought it had the same limits as passing them one at a time.

By scalable I meant it would work with either 10 lights…of 100+. I am just on OGL 3.3 for now, but so long as I can do the same basic thing in 3.3 then it is OK if the limit is much lower for now - when the new 1050 gtx comes out I will be upgrading and switching to 4.5 anyway.

I also encountered similar problems. Thanks for sharing.
<a href=“2quotes.net”>Inspirational Quotes</a> : “Happiness is an attitude. We either make ourselves miserable, or happy and strong. The amount of work is the same.”


struct PointLight {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float constant;	
    float linear;	
    float quadratic;	
    float pad0;	
};

layout (std140) uniform PointLightData
{
    PointLight pointLights[25];
};



    for(int i=0; i<pointLightCount; i++)
    {
	//result+=CalcPointLight(light, norm, viewDir, objectColor, specularColor);
	//result+=CalcPointLight(pointLights[i], norm, viewDir, objectColor, specularColor);
	//result+=CalcPointLight(PointLights[i], norm, viewDir, objectColor, specularColor);
	//result+=CalcPointLight(PointLightData.pointLights[i], norm, viewDir, objectColor, specularColor);
	//result+=CalcPointLight(PointLightData[i], norm, viewDir, objectColor, specularColor);

    }


Ok, I am using UBO now but when I uncomment any of those lines where I try to use the lights in the shader, I get a crash.

The memory alignment should all be OK because my vec3 in C++ land is already aligned to 16 bytes.


    GLuint uniformLightsBinding = glGetUniformBlockIndex(standardShader.program, "PointLightData");
    glUniformBlockBinding(standardShader.program, uniformLightsBinding, 0);

	GLuint uboPointLights;
    glGenBuffers(1, &uboPointLights);
    glBindBuffer(GL_UNIFORM_BUFFER, uboPointLights);
    glBufferData(GL_UNIFORM_BUFFER, pointLightsPerPass * sizeof(PointLight), NULL, GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

	glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboPointLights, 0, pointLightsPerPass * sizeof(PointLight));


result+=CalcPointLight(pointLights[i], norm, viewDir, objectColor, specularColor);

Actually when I use then one I get a pure black screen. So I am probably at least addressing it in the right way. Any other idea what could be wrong?


	GLuint uboCameraData;
    glGenBuffers(1, &uboCameraData);
    glBindBuffer(GL_UNIFORM_BUFFER, uboCameraData);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(Mat4), NULL, GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

	glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboCameraData, 0, 2 * sizeof(Mat4));


		glBindBuffer(GL_UNIFORM_BUFFER, uboPointLights);
		glBufferSubData(GL_UNIFORM_BUFFER, 0, pointLightsPerPass * sizeof(PointLight), pls.getContents());
		glBindBuffer(GL_UNIFORM_BUFFER, 0);

		glBindBuffer(GL_UNIFORM_BUFFER, uboCameraData);
		glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Mat4), projection.getContents());
		glBufferSubData(GL_UNIFORM_BUFFER, sizeof(Mat4), sizeof(Mat4), view.getContents());
		glBindBuffer(GL_UNIFORM_BUFFER, 0);

I do the same thing with the view and model vectors so it seems like it ought to work!


	GLuint uboPointLights;
    glGenBuffers(1, &uboPointLights);
    glBindBuffer(GL_UNIFORM_BUFFER, uboPointLights);
    glBufferData(GL_UNIFORM_BUFFER, pointLightsPerPass * sizeof(PointLight), NULL, GL_STATIC_DRAW);
    //glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glBindBufferBase(GL_UNIFORM_BUFFER, uniformLightsBinding, uboPointLights);

	glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboPointLights, 0, pointLightsPerPass * sizeof(PointLight));


    GLuint uniformCameraDataBinding = glGetUniformBlockIndex(standardShader.program, "CameraData");
    glUniformBlockBinding(standardShader.program, uniformCameraDataBinding, 1);

	GLuint uboCameraData;
    glGenBuffers(1, &uboCameraData);
    glBindBuffer(GL_UNIFORM_BUFFER, uboCameraData);
    glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(Mat4), NULL, GL_STATIC_DRAW);
    //glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glBindBufferBase(GL_UNIFORM_BUFFER, uniformCameraDataBinding, uboCameraData);

	glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboCameraData, 0, 2 * sizeof(Mat4));


		glBindBufferBase(GL_UNIFORM_BUFFER, uniformLightsBinding, uboPointLights);
		glBufferSubData(GL_UNIFORM_BUFFER, 0, pointLightsPerPass * sizeof(PointLight), pls.getContents());
		glBindBuffer(GL_UNIFORM_BUFFER, 0);

		glBindBufferBase(GL_UNIFORM_BUFFER, uniformCameraDataBinding, uboCameraData);
		glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Mat4), projection.getContents());
		glBufferSubData(GL_UNIFORM_BUFFER, sizeof(Mat4), sizeof(Mat4), view.getContents());
		glBindBuffer(GL_UNIFORM_BUFFER, 0);

Near as I can tell, using multiple UBOs means you have to call glBindBufferBase. Still not working though, sadly.

Using even a single UBO means you have to call glBindBufferBase (or glBindBufferRange); that’s how you bind UBOs to named uniform blocks.

glBindBuffer(GL_UNIFORM_BUFFER) doesn’t do anything except allow you to use GL_UNIFORM_BUFFER as a target for glBufferData etc. It doesn’t bind the buffer to a named uniform block.

maybe this site has some useful informations for you:

regarding your shader, you can explicitly set the binding point for your light block:


// use this layout for all blocks
// using std140 means that different programs can share that block
layout (std140) uniform;

// set binding point index to 0
layout (binding = 0) uniform Matrices {
	mat4 View;
	mat4 Projection;
} Camera;

// set binding point index to 1
layout (binding = 1) uniform PointLightData
{
    PointLight pointLights[25];
};

https://www.opengl.org/wiki/Interface_Block_(GLSL)#Uniform_blocks

then in your application, you have to call:


// camera:
glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer_camera);
// lights:
glBindBufferBase(GL_UNIFORM_BUFFER, 1, buffer_pointlights);

of course, you must have created both buffers before binding them to those “binding points”
but you have to care about “padding”, take a look at this: (site 137)

I forgot I posted this part here but I did get it working finally.

From what I read bindbufferrange doesn’t work for multiple ubos and you must use bindbufferbase. That seems to be true.

Thanks for the info about the explicit binding point, that would probably have been easier.


	long bindingPoint1 = 1;
	GLuint uniformLightDataBinding = glGetUniformBlockIndex(standardShader.program, "LightData");
    glUniformBlockBinding(standardShader.program, uniformLightDataBinding, bindingPoint1);

	GLuint uboLightData;
    glGenBuffers(1, &uboLightData);
    glBindBuffer(GL_UNIFORM_BUFFER, uboLightData);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(PointLight), NULL, GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint1, uboLightData);
	
	Point3 la(0.01f, 0.01f, 0.01f);
	Point3 la2(0.01f, 0.01f, 0.01f);


	glBindBuffer(GL_UNIFORM_BUFFER, uboLightData);
	glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(PointLight), &light);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);

The other change I had to make was to change the 0 to bindingPoint1 in glBindBufferBase and glUniformBlockBinding.

That makes perfect sense, but in the documentation and tutorials I could find online it was not really obvious.

Also, I had tried all those combos at various points but was still using the bufferrange thing which seemed to kill it. I just upgraded my card to an ogl 4.5 card though so maybe it was just some driver weirdness.

Well, it’s working and it’s not. I thought I would quickly resolve the issue but the colors for the light are going crazy so something is wrong.


struct PointLight {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float constant;	
    float linear;	
    float quadratic;
    float pad0;	
};


layout (std140) uniform LightData
{
    PointLight pl[100];
};


struct PointLight
{
public:
	Point3 position;
	Point3 ambient;
	Point3 diffuse;
	Point3 specular;

    float constant;
    float linear;
    float quadratic;
	float pad0;

};

	class Point3
	{
	public:
		Point3(float x, float y, float z):x(x), y(y), z(z), index(0){};
		Point3():x(0), y(0), z(0), index(0){};
		float x;
		float y;
		float z;
		long index;//index is useful for some things and is also there to pad alignment to 16 bytes.
		static const float EPSILON;

		inline Point3& operator -(){x = -x; y = -y; z = -z; return (*this);}
		inline Point3& operator =(const Point3& p){x = p.x; y = p.y; z = p.z; return (*this);}
		inline Point3& operator +=(const Point3& p){x += p.x; y += p.y; z += p.z; return (*this);}
		inline Point3& operator -=(const Point3& p){x -= p.x; y -= p.y; z -= p.z; return (*this);}
		inline bool Point3::operator ==(const Point3& p)const{
			return (x==p.x&&y==p.y&&z==p.z);
		}
		inline bool Point3::operator !=(const Point3& p)const{return !operator==(p);}
		inline Point3& operator /=(float f){x /= f; y /= f; z /= f; return (*this);}

		SString asString() const;
		float length();

	};

The alignment in my C++ object should be the same as std140 because the Point3 is actually 3 floats and a long with a total size of 16 bytes.

Is there something I need to do when declaring the PointLight struct to make it use std140 or is it unnecessary?

Ok now it is FINALLY working.

I got rid of the blockbinding crap and went to binding = 0 and binding = 1.

Suddenly everything started working properly. Why what who how? Who knows, but for now I am just glad it works!

Thanks again for help, guys.