I've been creating a game engine with Kotlin and LWJGL. I wanted to render scenes that support many light sources, so I worked on implementing a deferred renderer.
My basic pipeline is as follows (just point lights for now):
- Render scene to GBuffer [2ms]
- Render fullscreen quad with ambient light shader [0.2ms]
- Fill an UBO with data (color, strength, matrix, ...) for all the lights [0ms]
- Draw light volume spheres (instanced) [25ms]:
if (numActiveLights > 0) {
OpenGL.enable(GL_CULL_FACE)
OpenGL.cullFace(GL_FRONT)
OpenGL.enable(GL_DEPTH_TEST)
OpenGL.depthFunc(GL_GEQUAL)
blinnShader.bind()
Primitives.unitSphere.renderInstanced(numActiveLights)
}
- Post processing (AO, FXAA etc.) [1-2ms]
I've added the per-frame GPU times of each step in the pipeline, and I'm sure the problem is fairly obvious. Even at just 95 lights, this call takes upwards of 25ms, which ruins the framerate.
Since the problem also occurs (15+ ms) with just an extremely simple shader (FragColor = vec4(1,1,1,1)
, no lighting calculation / texture accesses), I think the problem is not with the lighting calculations themselves. I suspect it is because of the large amounts of overdraw.
I've seen deferred renderers handle thousands of lights, so clearly I'm doing something wrong. How can I go about optimizing this?
Edit: About the sphere, I'm generating triangles for a sphere with a resolution of 20 vertices using this bit of code (did not write it myself). The sphere is centered at [0,0,0] and has a radius of 1 (I called that a "unit sphere"). The lighting shader later has a matrix to make the actual light volume out of that.
The sphere mesh is loaded to a simple VAO with one GL_FLOAT
VBO attached to it for the vertices. renderInstanced()
then just draws them instanced like this:
fun renderInstanced(amount: Int) {
glBindVertexArray(vao)
// type: GL_TRIANGLES
// vertexCount: Class attribute that contains number of verts
glDrawArraysInstanced(type, 0, vertexCount, amount)
}