SFML community forums

Help => General => Topic started by: Fililip on March 29, 2018, 10:20:51 am

Title: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 10:20:51 am
Hello. I have a problem I've been trying to fix for about 2 weeks but couldn't find anything that would help me.
Everything started when I stumbled upon a 2D light shader that works flawlessly and does everything I want it to (which is basically creating these cool point lights):

#version 450

uniform vec2 lightPos;
uniform vec3 lightColor;
uniform float intensity;

out vec4 color;

void main()
{
        vec3 lAtt = vec3(0.01, 0.1, 0.00001);
       
        float dist = distance(gl_FragCoord.xy, lightPos.xy);
        float att = 1.0 / (lAtt.x + lAtt.y * dist + lAtt.z * dist * dist);
        vec4 finLight = vec4(vec3(att) * lightColor, 1.0) * intensity;
       
        color = finLight;
}

How I drew these lights was pretty simple: I rendered every single object in the scene and used this shader on a render texture, which I found out to be very efficient because I'm applying the light only once for each frame and not for every object for each frame.

However, that was not something I wanted in the long run due to the fact that it only worked for ONE light but since I'm in development of a game that obviously requires more than one light (particles, lights around objects, etc.) and so I started to look for a better solution than this.

The first thing I came up with is a buffed version of this shader which allowed me to render up to 256 lights and which worked pretty well (and was WAY more efficient with rendering a hundred lights in comparison to the next version of it which I'll explain in a moment):

#version 450

struct Light
{
        vec2 position;
        vec3 color;
        float intensity;
};

uniform int lightCount;
uniform Light lights[256];
uniform sampler2D texture;
uniform vec2 size;

out vec4 clr;

void main()
{
        vec3 lAtt = vec3(0.0001, 0.0001, 0.001);

        vec2 uv = gl_FragCoord.xy;

        vec4 outc = vec4(0.0);

        for (int i = 0; i < lightCount; i++)
        {
                float dist = distance(lights[i].position, uv);
                float att = 1.0 / (lAtt.x + lAtt.y * dist + lAtt.z * dist * dist);
                outc += vec4(vec3(att), 1.0) * lights[i].intensity * vec4(lights[i].color, 1.0);
        }

        vec4 pixel = texture2D(texture, uv / size);

        clr = pixel * outc;
}

So everything was fun and all, but this solution seems to be hated all over the internet since I'm passing HUGE amounts of data to the shader (which I heard is not recommended) and it still had a light limit which I did not like because it's obvious that for many particles or huge maps this is a real pain in the .... .

So I kept looking for solutions, and found one that, yes, allowed me to draw as many lights as I wanted, but on the other hand used a TON of resources of my GPU (GTX 1070).

How it worked was simple: I used the first version of the lighting shader, but used this shader multiple times in this manner:

pseudocode:

    for each object:
        draw every object to a rendertexture
    for each light:
        draw each light to another render texture with the lighting shader and add up the results with additive blending

    multiply the lights' and the scene's texture to achieve a lit scene

    draw the lit scene

 

So, now, here's my question: what did I do wrong? How can I improve this so it can render even 10,000 lights?

Any help is appreciated!
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on March 29, 2018, 10:28:03 am
So what exactly is wrong with your final solution? It looks ok: one RT for the scene, another for the lights, and then you multiply to get the final result. I don't see anything wrong with it.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 10:34:14 am
Yes, but when I draw a 100 lights, this is what I get: (screenshot attached)
It gets even worse with 500 lights, where the framerate drops to 40.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 10:42:18 am
BTW, below's the real code responsible for this.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on March 29, 2018, 10:44:08 am
If the bottleneck (*) is the shader, then you can draw the lights as simple sprites with no shader: find/build a black & white image of a light, then you just have to change its position, size and color before drawing.

Also, no need to call display() after every draw on the RT; this might kill your performances too.

(*) you might want to try OpenGL profilers instead of guessing ;)
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 10:55:15 am
Sorry, I'm not really "prepared" yet to go deep into OpenGL (I'm a huuuuge newbie), I would really love to be able to make 2D games with just SFML.
As for the shader, I don't think it is the bottleneck, but rather drawing 500x the lights (which are essentially fullscreen quads, so it's like drawing to 500 screens at the same time) (am I right though?)
But I'd really like to make this effect with shaders because I know it can be done efficiently and it would be a good place for me to start learning shaders.
So is there any way for you to help me with this?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 11:32:24 am
BTW, will culling work for this? (rendering only the light-affected surface)
If yes, how can I achieve it?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on March 29, 2018, 12:43:14 pm
I suggested 2 simple potential improvements, don't you want to test them?

Quote
Sorry, I'm not really "prepared" yet to go deep into OpenGL (I'm a huuuuge newbie), I would really love to be able to make 2D games with just SFML.
The OpenGL profiler would just be a way to identify the bottleneck, in order to know what to optimize. No need to go deep into OpenGL stuff.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 02:53:35 pm
I figured it out. It was the debug mode's slow performance. Compiling it in release mode improved the performance A LOT.
Now I can safely and efficiently handle even 400/500 lights without a problem.
My only question is: how did this guy (https://www.youtube.com/watch?v=ToAKdrcb_dU) achieve such great results (10,000 lights and more than 10 fps)?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on March 29, 2018, 03:09:08 pm
Quote
I figured it out. It was the debug mode's slow performance. Compiling it in release mode improved the performance A LOT.
Indeed, measuring performances in debug mode is pretty useless... :P

Quote
Now I can safely and efficiently handle even 400/500 lights without a problem.
That's great but I'm still not convinced that this is the most efficient way to do it.

Quote
My only question is: how did this guy achieve such great results (10,000 lights and more than 10 fps)?
Deferred rendering is a totally different way of doing lighting calculation: everything is done in screen space rather than world space. So basically the number of lights doesn't have much impact.

You can have a look at this recent SFML project for interesting explanations and links on the subject:
https://en.sfml-dev.org/forums/index.php?topic=23751.0
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 03:12:10 pm
So how would I go about testing the performance of my application with the profiler?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on March 29, 2018, 03:32:58 pm
I've never used it so you'll have to document yourself if you're interested :)
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on March 29, 2018, 03:39:08 pm
And also, how can I turn my lighting system into a deferred rendering one?
I get the main idea, but how are you passing lights to a deferred rendering shader?
The LearnOpenGL website has a fixed value of 32 lights in their example, and to me it isn't a better solution.
I would like to be able to render as many lights as I could think of without a big impact on performance.
So what's with the passes? How do they happen?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on April 01, 2018, 11:37:21 am
Okay, I figured it out. I had to render a circleshape for each light and use the lighting shader on it, rather than on the entire fullscreen quad for pointlights. Now I can safely render even 2000 lights in the entire window's viewport, at which I'm somewhat-starting to get 80 or lower FPS, but that's still good imo. (it may be related to my GPU being pretty good at this though)
But can I go even further than that about it? What sort of other optimization technique can I use to make this app handle even more lights (if that's possible that is) with even less of a performance impact?
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on April 01, 2018, 05:54:15 pm
Why do you still insist on using a shader to draw lights, since it basically always generates the same shape? Using a texture (and no shader) should be much faster.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on April 01, 2018, 07:19:18 pm
I was trying to learn how to do deferred rendering properly and since I don't really want to go 3D yet, I just wanted to try to implement it in 2D to see if I understood it correctly.
You know, I've been trying to understand deferred shading for like 3 weeks and I still think I did not implement it correctly. (I just could not get any help with my problem, and I've been looking for the solution everywhere)
The only thing I wanted to achieve was to finally understand how it should be implemented.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on April 02, 2018, 09:15:36 am
If you want to optimize your SFML based lighting system, we can probably help you. But now if your goal is to learn deferred rendering, then the SFML forum may not be the best place, we're not experts at this kind of stuff ;)
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on April 02, 2018, 12:20:33 pm
So which tutorials do you actually recommend that'll help me understand EVERYTHING that needs to be done to display these huge amounts of lights? I could not really find what I was looking for since most of them revolve around 3D and I'm not working in 3D, but rather 2D.
(Because if I understood the entire process correctly, deferred rendering (2D) is basically rendering the scene once, then all the lights by drawing circles, using a lighting shader on each one of them, and multiplying the result (or instantly drawing on one texture) to finally blit the finalized scene to the window's buffer.)
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Laurent on April 02, 2018, 05:58:12 pm
Quote from: Laurent
You can have a look at this recent SFML project for interesting explanations and links on the subject:
https://en.sfml-dev.org/forums/index.php?topic=23751.0

Quote
So which tutorials do you actually recommend that'll help me understand EVERYTHING that needs to be done to display these huge amounts of lights?
I have already said all I know, if you don't want to try my suggestions then there's nothing else I can do for you.
Title: Re: How to handle tons of 2D lights using GLSL shaders efficiently?
Post by: Fililip on April 02, 2018, 07:14:48 pm
I checked out this topic and it doesn't really explain how it's done (it's not a tutorial), and the only thing linked to it is the LearnOpenGL website which does not really help me understand how things work in 2D.