Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Some lighting stuff (+ how to implement it yourself)  (Read 9336 times)

0 Members and 1 Guest are viewing this topic.

Antonio9227

  • Newbie
  • *
  • Posts: 25
    • View Profile
Some lighting stuff (+ how to implement it yourself)
« on: June 30, 2018, 12:01:18 am »
Hello there! So a couple of days ago I got really curious about how I could simulate some 2D lights in SFML. After a bit of work, this is what I came up with.


(I didn't realize fraps recorded my background music too)


I kinda cheated because the scene itself was not made by me, it's just some screenshot I found on the internet. (which you can see by clicking here)
This is far from finished but I think it looks kinda cool. I know there are lots of better projects, such as Let there be light, that are probably far more optimized and work better overall.


Here you can see the walls I set for this specific scene.

I'm pretty sure there are things that could be done better so let me know if you have any suggestions/criticisms.

So how does it work? There are three steps to generate a lightmap (which is basically more or less a texture generated depending on the lights), then we take the lightmap and draw it over our scene using the multiply blending mode. By using this blending mode each pixel that is already draw will be multiplied with the pixel from the lightmap that is directly above it. You can try using Photoshop or some other picture editing software to get a feeling of how this blendmode works.

To generate the lightmap we have to:

1. Generate/calculate the light mesh
First of all we need some kind of vertex array that describes the span of our light. To do this we have a couple of options. This is the hardest part you'll have to think about, but don't worry, it's not nearly as hard as you might think.
We could for example take a circle with a radius of how far we want the light to reach and do something called "polygon clipping" to clip away any object that is not supposed to be illuminated. 
My approach however uses something called raytracing. In a nutshell, we imagine firing a ray in a certain direction to see if it hits anything on the way. I imagine this could get resource intensive really fast but I was too lay to figure out polygon clipping and I ways already familiarized with raytracing.
To learn how you can use raytracing to generate your mesh you can follow THIS awesome interactive article that shows you everything you need to know, step by step, with interactive animations!

At the end, we're left with something like this:

Kinda ugly, but it's the exact same lightmap you saw drawn in the video (except the light controlled by my cursor).

Notice that the lights have a maximum range after which they stop propagating.

To draw colored light all you have to do is draw the polygon a certain color. In a scene with multiple lights I recommend using the add blend mode when you're drawing the light meshes. You can see in the picture above that where 2 lights intersect the colors get blended rather than overlapping.

If you want, you can add a bit of ambient light by clearing the screen with something other than black when rendering the lightmap. If you leave it completely black only what's illuminated by your lights will be visible. Choosing a gray color will make the entire scene visible, but darker where the lights don't illuminate. Finally choosing a different color will give your scene a slight tint. You can use this to make a scene feel warmer or colder or whatever your creativity demands.

2. Adjust the intensity over distance

No light in the real world would look like that because the intensity (the color) of the mesh is the same everywhere. Naturally you would expect a light to be at maximum intensity at the source and then slowly lose intensity as it spreads out more and more into space. Since we're going to multiply the lightmap with the rest of the scene at the end, intensity is pretty much the same as color at this point.

To do this you can either set different color for each vertex in the mesh or you could use shaders and let your graphics card work a bit.
I chose to use a fragment shader just because it felt easier to write at the time. It's very simple and you can write it in less than 10 lines of code. Basically you have to slowly fade out the color of your light the further you get from the light source.

Just by doing this your lightmap should start looking like this:


It kinda looks a bit soft and fuzzy because of the way I coded it, this can be improved a lot.

3. Blur
Right now our light map has the right shape and colors but it still looks kinda rough so I blur them a bit with another shader that applies some gaussian blur.

Congratulations! Your lightmap texture is complete. Now all you have to do is draw it over your scene, using the multiply blending mode.


Penumbra
If you looked really carefully at the screenshots/video you may have noticed that the main light I'm controlling with my cursor produces penumbra. Usually a point light only produces an umbra (shadow) while something like the sun also produces penumbra. (if you have no ieda what the heck I'm rambling about, just google 'penumbra' and the first or second image should be self explanatory). I managed to get this effect by spawning a ring of point lights, this kinda approximates the way light would be emitted from the surface of an object.

Performance issues and considerations
The part that will stress your computer the most is generating the mesh for each light. To avoid this you can generate the mesh only when needed (when the light is created, moved, has changed colors, etc) and when some object that is in the rage of the light has changed (has moved, rotated, etc).
I would say updating lots of lights in the same frame is a bad idea with this method and should be avoided. One cheap way I got away with it was decreasing the number of light updates per frame and scheduling the rest of them for the next few frames.

In my experience, since my code is not really optimized and it's single threaded, updating 10 lights every frame in the scene you saw above caused a noticeable FPS dip and while restricting the number of light updates per frame helped a lot, I would say there's huge space for improvements.
I'm sure this is caused by my sloppy code and you can probably get away without any performance issues if you optimize it enough.

Multi threading also might solve this problem but I'll have to rewrite a bit of the code for that to work so maybe someday I'll do a followup.

The code
I'm not going to share the code I wrote because in my opinion it's not ready to be used. It's more or less just a demo I made to see what kind of effect I can get without much work or complicated maths. I'm not proud of the performance side either and I'm sure it can be improved a lot.

If you have any suggestions or any kid of comments about my approach feel free to leave a reply below and I'll check it out tomorrow.
« Last Edit: October 04, 2018, 01:03:54 pm by Antonio9227 »

FRex

  • Hero Member
  • *****
  • Posts: 1848
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: Some lighting stuff (+ how to implement it yourself)
« Reply #1 on: July 07, 2018, 05:39:57 am »
I'd like to see the code and the performance. I've made attempts at light in the past using sight and light tutorial (https://en.sfml-dev.org/forums/index.php?topic=22899.msg159728) and clipper lib ( https://en.sfml-dev.org/forums/index.php?topic=14234.msg99838 ) before as well but performance was so-so for many dynamic lights in complex scenes and I wonder if it's a problem with optimization or with the techniques themselves.
Back to C++ gamedev with SFML in May 2023

verdog

  • Newbie
  • *
  • Posts: 25
    • View Profile
    • Email
Re: Some lighting stuff (+ how to implement it yourself)
« Reply #2 on: July 11, 2018, 08:37:28 pm »
Great tutorial :)

I've spent some time on this myself. I originally followed the same ncase tutorial, but also noticed a performance hit after there was a lot of walls used in the calculation of the light mesh.
My current method is to actually render everything in full brightness, and then draw the light map by rendering shadows, not light. I feel like this is a more efficient approach than doing that heavy ray tracing math on every wall.

You can see what my version looks like here.

I'm not sure if there's actually a performance boost in doing it my way, because in my version you need a separate rendertexture for each light, and rendering to those separately might outweigh the benefit of not doing as heavy calculations. Not sure.

Antonio9227

  • Newbie
  • *
  • Posts: 25
    • View Profile
Re: Some lighting stuff (+ how to implement it yourself)
« Reply #3 on: July 14, 2018, 12:32:12 pm »
Something REALLY weird happened yesterday.

I had some free time so I decided to compile this for linux and I noticed it was running smoother than usual. So I looked over the FPS and for some reason it had HUGE improvements.

I use the same machine in a dual boot configuration and I compiled exactly the same code, yet somehow it somehow manages to work way faster.

Usually on windows this got an average of 1700FPS when no light updates occurred, which doesn't sound that bad but when updating all the lights in the scene it dropped to about 30FPS (I have around 12 lights). On linux on the other hand, the average FPS when not updating was around 3000FPS and even when updating all the lights in the same frame it still managed to only drop to 2300FPS (which would be expected when you only update such a small amount of lights)

It only dropped to 30FPS when updating 300 lights at the same time. This blows my mind and I have no clue what might have caused it.

Maybe it's in the way the code got compiled or something... Both debug and release versions improved their FPS.

Any idea why or how this might have happened?

verdog

  • Newbie
  • *
  • Posts: 25
    • View Profile
    • Email
Re: Some lighting stuff (+ how to implement it yourself)
« Reply #4 on: July 15, 2018, 03:43:51 am »
Did you use the same compiler for each version? Also, were compiler optimizations on? Compiler optimizations can do some incredible and magic things.