1. Post #1
    xAustechx's Avatar
    July 2009
    584 Posts
    How to Implement Dynamic Lighting in Your 2D Project
    A (hopefully easy) Tutorial
    By: Austin Shindlecker (Austech)

    Sections (in order)
    Recommended Knowlege
    Recommended Sites
    What we'll cover and how
    What makes a light dynamic
    SFML Objects You Should be Aware of
    Coding The Lights and Blocks
    Making The Engine



    Recommended Knowledge Needed:

    Some understanding of the Trigonomic Functions (cos, sin, tan)
    Pythagorean Theorem
    Well enough experience in the language you plan on using (duh)
    ----------------------------------------------
    Reccomended Sites if you don't want to read this thread:
    http://www.gamedev.net/reference/pro.../2dsoftshadow/
    ----------------------------------------------
    What We'll Cover and how
    The code you will see is made in C++ and using the SFML library, but I'm pretty sure that you will be able to get the basic idea of what to do\

    This tutorial will only be discussing what is called "hard shadows". The shadows do not blend or fade. But they are dynamic
    -----------------------------------------------

    What makes a light dynamic
    So what makes a light dyamic? First off dynamic, when compared to static, is something that changes. While static is something that doesn't. For those who are still confused here's an example. In my game, Zombie Outrage 2, the lighting system I made has a boolean for whether or not a light is dynamic.

    Static Light:


    Dynamic Light:



    Notice how with dynamic the light stops when it hits the zombies or player, creating a field of darkness behind them. Yet with the static it goes through. With dynamic the light changes.
    ------------------------------------------------

    SFML Objects You Should be Aware of
    As I said, I'm going to be using SFML (Simple, Fast, Multimedia Library), and I will be using some objects.

    sf::Vector2f - Vector holding 2 floats (x,y)
    The structure looks something like:
    struct Vector2f
    {
    float x, y;
    };
    
    Though Vector2f in reality is a derived class from sf::Vector, using floats in the template, this is pretty much how the structure would look like.

    sf::FloatRect - Rectangle object holding 4 floats. (Left, Top, Width, Height).
    The structure looks something like:

    struct FloatRect
    {
    float Left, Top, Width, Height;
    };
    
    Like the Vector2f, FloatRect in reality is just a derved of sf::Rect, using floats in the template. But this is how the structure would look like.

    sf::Color - Object holding values for Red, Green, Blue, and Alpha. This is used only for rendering, nothing based on the actual lighting system
    The structure looks something like:
    struct Color
    {
    int r, g, b, a;
    };
    
    I beleive that it's UINT instead of int. But I'm not 100%. But you should get the general idea.
    -------------------------------------------

    Coding the Lights and Blocks
    So now that the intro is done and some things are explained. Let's get into the code!

    We will be making 3 classes:

    Light - Info for lighting (color, radius, position, spread)
    Block - Areas where the light can't go through (rect, allowblock)
    LightEngine - This is the class that does what is needed with the Lights and Blocks.


    Let's get the first 2 classes out of the way since they are the easiest. Since it would be a pain to write a tutorial will .cpp and .hpp, I'm going to just right all the bodies in the .hpp file

    //Light.hpp
    #pragma once //don't allow this header to be included more than once
    #include <SFML/Graphics/Color.hpp>
    #include <SFML/System/Vector2.hpp>
    
    class Light
    {
    public:
    	Light()
    	{
    		color = sf::Color(255, 255, 255);
    		radius = 0;
    		angleSpread = 0;
    		position = sf::Vector2f(0,0);
    		angle = 0;
    		dynamic = true;
    	}
    	/*If you prefer, you can do the constructor like so:
    	
    	Light()
    	: color(255, 255, 255), radius(0), angleSpread(0), position(0,0), angle(0), dynamic(true)
    	{
    	}
    	*/
    
    	sf::Vector2f position; //position of the light
    	sf::Color color; //light color
    	float radius; //how far the light will shine to
    	float angleSpread; //spread of the light, creating an arc
    	float angle; //where the light's pointing at
    	bool dynamic; //can this change?
    };
    

    //Block.hpp
    #pragma once//don't allow this header to be included more than once
    #include <SFML/Graphics/Rect.hpp>
    
    class Block
    {
    public:
    	Block()
    	{
    		allowBlock = true;
    		fRect = sf::FloatRect(0,0,0,0);
    	}
    	/* Like the light class, you can do this in an initializer list, whatever works for you
    	
    	Block()
    	: fRect(0,0,0,0), allowBlock(false)
    	{
    	}
    	*/
    
    	sf::FloatRect fRect; //Area that will be blocking light
    	bool allowBlock; //Sometimes we want to keep a block, but don't want it to actually block until a certain point
    };
    

    These are pretty easy and straight forward. I added comments in places to make it even more understandable. Unfortunately, this is the easy part. Now we are going to make the engine. Again there will be comments.
    -----------------------------------------
    Making the engine

    The engine will be taking these 2 objects we made, and actually do things with them.

    The light engine should have some sort of container for holding multiple lights and blocks. I'll be using std::vector.
    The light engine should have a private Light() function to do things with a light.
    The light engine should have some sort of step function to light each light in a vector.
    A useful function to get the distance of two points
    A function to check if a light hits a block
    Maybe some useful functions to make long equations return an answer neater than typing it call out.

    My class looks like this:

    //LightEngine.hpp
    
    #pragma once//don't allow this header to be included more than once
    #include "Light.hpp"
    #include "Block.hpp"
    #include <vector>
    #include <SFML/Graphics/RenderTarget.hpp> //Place to draw on
    #include <SFML/Graphics/Shape.hpp> //SFML programmable Shapes
    
    class LightEngine
    {
    public:	
    	void Step(sf::RenderTarget &rt);
    
    	std::vector <Light> Lights; //Container for Lights
    	
    	std::vector <Block> Blocks; //Container for Blocks
    private:
    	
    	void ShineLight(Light &lig, sf::RenderTarget &rt);
    		
    	static const float Distance(const sf::Vector2f &p1, const sf::Vector2f &p2);
    	static const sf::Vector2f GetCenter(const sf::FloatRect &fr); //Get the center of a rectangle
    	
    	
    	struct FindDistance //if a light's radius manages to intersect multiple blocks, we need to find the sortest distance to shorten the light
    	{
    		FindDistance();
    		float shortest;
    		bool LightHitsBlock(Light &l, Block &b, const float cur_ang, float &reflength);
    		bool start; //to get the first distance to refer off of
    	};
    
    	FindDistance findDis;
    };
    

    Step - Only function exposed publicly. Calls "ShinLight" for each light in the container
    struct FindDistance Structure containing info for calculating how much the light ray should be shortened.
    FindDistance::LightHitsBlock - One of the main functions in LightEngine. Checks if a light hit's the block. This changes the findDis info.
    ShineLight - Another main functions for ray casting a light
    Distance - Static functionto find the distance between 2 points. This doesn't HAVE to be in the LighEngine class. It can be anywhere. I will use it for cleaner code and it's easier than typing the calculations over and over.
    GetCenter - Get Center of a Rectangle


    Now that you have a basic understanding of what each function and structure does. Let's get into the bodies of the structures. Like before, we'll do the easiest to hardest.

    
    LightEngine::FindDistance::FindDistance()
    {
    	start = false;
    	shortest = 0;
    }
    
    const sf::Vector2f LightEngine::GetCenter(const sf::FloatRect &fr)
    {
    	return sf::Vector2f(fr.Left + (fr.Width / 2), fr.Top + (fr.Height / 2));
    }
    
    const float LightEngine::Distance(const sf::Vector2f &p1, const sf::Vector2f &p2)
    {
    	//We need to look at this as a triangle
    
    	/*
    	         /|p1.y
    	        / |
    	       /  |
    	      /   |
    	     /    |
    	    /     |b
    	   /      |
    	  /       |
    	 /        |
    	/         |
    	-----------
    	     a     p2.y
    p1.x           p2.x
    
    	*/
    
    	float a = p2.x - p1.x;  //width length
    	float b = p2.y - p1.y; //height length
    	float c = sqrt((a * a) + (b * b)); //Pythagorean Theorem. (c = a + b). c = squareroot(a + b)
    
    	return c;
    } 
    

    These really should be self explainable with the comments.

    Step Function:

    void LightEngine::Step(sf::RenderTarget &rt)
    {
    	for(unsigned i = 0; i < Lights.size(); i++)
    	{
    		ShineLight(Lights[i], rt); //Shine all lights
    	}
    }
    

    Now we have 2 functions left, ShineLight and LightHitsBlock. These both aren't really too hard. Let's start with the ShineLight function:

    void LightEngine::ShineLight(Light &l, sf::RenderTarget &rt)
    {
    	/*
    	remember back in the Light class, we had something called 'angleSpread' ?
    	that's to create a tunnel, or arc shaped light. Like this:
    	       /)
    	     /  )
    	   /    )
    	 /      )
    	<       )
    	 \      )
    	   \    )
    	     \  )
    		   \)
    
    	Obviously it'll look better than an ascii drawing
    	*/
    
    	float current_angle = l.angle - (l.angleSpread / 2); //This will rotate the angle back far enough to get a desired arc
    
    	/*
    	Lights Angle (if it was at 0):
    	
    	-------------
    
    	Current_Angle:
    	    /
          /
    	/
    	(slanted)
    
    	*/
    
    	float dyn_len = l.radius; //dynamic length of the light. This will be changed in the function LightHitsBlock()
    
    	float addto = 1.f / l.radius;
    	for(current_angle; current_angle < l.angle + (l.angleSpread / 2); current_angle += addto * (180.f/3.14f)) //we need to add to the current angle, until it reaches the end of the arc. we divide 1.f by radius for a more solid shape. Otherwize you could see lines seperating
    	{
    		dyn_len = l.radius; //Reset the length
    		findDis.start = true; //Start of finding a light, we need to reset
    		findDis.shortest = 0; //Reset the shortest.
    
    		
    		
    		if(l.dynamic) //can this change?
    		{
    			for(unsigned i = 0; i < Blocks.size(); i++)
    			{
    				findDis.LightHitsBlock(l, Blocks[i], current_angle, dyn_len);
    			}
    		}
    		
    		
    		float radians = current_angle * (3.14f / 180); //Convert to radians for trig functions
    		
    		sf::Vector2f end = l.position;
    		end.x += cos(radians) * dyn_len;
    		end.y += sin(radians) * dyn_len;
    		rt.Draw(sf::Shape(sf::Shape::Line(l.position,  end, 1, l.color)));
    	}
    }
    

    We use another float, current_angle, because it's based off of the light's angle. At the very beginning we subtract half of the angle spread. This rotates that current_angle back. We use a for loop and increase the current_angle until it reaches the lights angle PLUS half the angle spread.

    current_angle is the direction of where the light ray will go. One light can equal many light rays. Hundreds of them.

    Since this function is using the LightHitsBlock function, we're going to go to that and come back.


    bool LightEngine::FindDistance::LightHitsBlock(Light &l, Block &b, float cur_ang, float &refleng)
    {
    	if(b.allowBlock) //can this even block?
    	{
    		float distance = Distance(l.position, GetCenter(b.fRect));
    	
    		if(l.radius >= distance) //check if it's radius is even long enough to hit a block
    		{
    			float radians = cur_ang * (3.14f / 180); //convert cur_ang to radians for trig functions
    			sf::Vector2f pointpos = l.position;
    
    			pointpos.x += cos(radians) * distance;
    			pointpos.y += sin(radians) * distance;
    			//By doing this, we check if the angle is in the direciton of the block.
    
    			if(b.fRect.Contains(pointpos)) //If it was, than the point would be intersecting the rectangle of the block
    			{
    				if(start || distance < shortest) //If this is the first block, or it has a shorter distance
    				{
    					start = false; //definately not the start so other blocks can't automatically set the distance
    					shortest = distance; //shortest is set to this
    					refleng = distance; //This is where the dynamic comes in, it changes the length of the reference towhere it's going to stop after it hits the distance from the point to the block
    				}
    				return true;
    			}
    		}
    	}
    	return false;
    }
    

    The comments should explain a lot of this. This function is basically doing this:
    Check if the Block can block light from passing through
    Check if the light's radius can reach to the block
    Check if the light's angle direction is in the direction of the block
    if it is, then check if the block is the closest to the light
    if it is, then change the ray's length to it's distance.




    Now back to the ShineLight function:
    void LightEngine::ShineLight(Light &l, sf::RenderTarget &rt)
    {
    	/*
    	remember back in the Light class, we had something called 'angleSpread' ?
    	that's to create a tunnel, or arc shaped light. Like this:
    	       /)
    	     /  )
    	   /    )
    	 /      )
    	<       )
    	 \      )
    	   \    )
    	     \  )
    		   \)
    
    	Obviously it'll look better than an ascii drawing
    	*/
    
    	float current_angle = l.angle - (l.angleSpread / 2); //This will rotate the angle back far enough to get a desired arc
    
    	/*
    	Lights Angle (if it was at 0):
    	
    	-------------
    
    	Current_Angle:
    	    /
          /
    	/
    	(slanted)
    
    	*/
    
    	float dyn_len = l.radius; //dynamic length of the light. This will be changed in the function LightHitsBlock()
    
    	float addto = 1.f / l.radius;
    	for(current_angle; current_angle < l.angle + (l.angleSpread / 2); current_angle += addto * (180.f/3.14f)) //we need to add to the current angle, until it reaches the end of the arc. we divide 1.f by radius for a more solid shape. Otherwize you could see lines seperating
    	{
    		dyn_len = l.radius; //Reset the length
    		findDis.start = true; //Start of finding a light, we need to reset
    		findDis.shortest = 0; //Reset the shortest.
    
    		
    		
    		if(l.dynamic) //can this change?
    		{
    			for(unsigned i = 0; i < Blocks.size(); i++)
    			{
    				findDis.LightHitsBlock(l, Blocks[i], current_angle, dyn_len);
    			}
    		}
    		
    		
    		float radians = current_angle * (3.14f / 180); //Convert to radians for trig functions
    		
    		sf::Vector2f end = l.position;
    		end.x += cos(radians) * dyn_len;
    		end.y += sin(radians) * dyn_len;
    		rt.Draw(sf::Shape(sf::Shape::Line(l.position,  end, 1, l.color)));
    	}
    }
    

    Notice how we loop through all blocks and call the same function. This is because it's changing variables and checking variables from the findDis object. As you can see before we loop through the blocks, we reset the values.

    We pass dyn_len to the LighHitsBlock function as a reference, so the function can change the value of it based off the smallest distance.

    After all that, we can finally draw it. We create a line from the lights original position, to the end which is the lights position plus the cos and sin of the dyn_len.




    Now we can test it, I set up this simple example to do so:

    #include <SFML/Graphics.hpp>
    #include <iostream>
    #include "LightEngine.hpp"
    
    int main()
    {
    	sf::RenderWindow win(sf::VideoMode(800, 600), "Light Tutorial");
    	sf::Event event;
    
    	LightEngine le;
    
    	Light light;
    	light.radius = 600;
    	light.angleSpread = 100;
    	light.position = sf::Vector2f(100,150);
    	le.Lights.push_back(light);
    
    	Block block;
    	block.fRect = sf::FloatRect(0,0,50,50);
    	le.Blocks.push_back(block);
    
    	
    	while(win.IsOpened())
    	{
    		while(win.GetEvent(event))
    		{
    			if(event.Type == sf::Event::Closed) win.Close();
    		}
    		std::cout<<1 / win.GetFrameTime()<<"\n"; //output the framerate
    		win.Clear();
    		le.Blocks[0].fRect.Left = win.GetInput().GetMouseX();
    		le.Blocks[0].fRect.Top = win.GetInput().GetMouseY();
    		win.Draw(sf::Shape::Rectangle(le.Blocks[0].fRect, sf::Color(255,0,0)));
    		le.Step(win);
    		win.Display();
    	}
    }
    

    I added a light, and a block to the light engine. In the while loop I step the engine, and I also draw the rectangle of the first block I added to the container. I also output the framerate in the console. All in all, the program should look something like this:



    Testing multiple blocks:


    Little more

    ================================================== ==================

    I really hope this helps some people out. I knew that some people wanted to know how my game did it and a way for SFML. Sorry if this wasn't clear enough or easy to read. This is my second tutorial at the moment and first time making a thread like this on facepunch.

    Thanks for reading and feel free to ask questions or whatever if you're still lost on something.

    Also, feel free to give me advice on what I could have done better or how you would have explained. I'd like any sort of feedback.

    Thanks again. :)
    Reply With Quote Edit / Delete Reply United States Show Events Informative x 26Programming King x 17Friendly x 5Dumb x 3Disagree x 1Useful x 1 (list)

  2. Post #2
    Richy19's Avatar
    May 2010
    5,382 Posts
    Wow really useful
    i have a question although its not to do with the lighting.
    Why do you use
    Code:
    #pragma once
    and not just
    Code:
    #ifndef header
    #define header
    #endif
    Reply With Quote Edit / Delete Reply United Kingdom Show Events Programming King Programming King x 8Disagree Disagree x 3 (list)

  3. Post #3
    www.bff-hab.de
    Dennab
    February 2009
    7,832 Posts
    That tutorial is awesomely helpful!

  4. Post #4
    Gold Member
    ZeekyHBomb's Avatar
    June 2006
    3,577 Posts
    The screenshots show dynamic lights in both, just the first one has no shadows :|

    codewise:
    Tracing edges and building a 'shadow volume' is probably more efficient than tracing lots of rays.
    An approach is explained here: http://www.gamedev.net/reference/pro...adow/page3.asp

    Another speedup could be gained by ordering the block in a quadtree or kd-tree.

    And using initializer lists is not a thing of mere preference. Some things can only be done via initializer lists (such as assigning references or constants). They are also slightly, probably not notably, faster, since the call to the default constructor for non-POD types can be skipped and no need to copy an object via the assignment operator.

    Returning a const copy is senseless. It's just a copy so it can be copied into a non-const object. Returning something as const only makes sense for pointers to const objects and const references.

    It's not necessarily important, but still good practice to usually keep member-variables private and provide public or protected accessors.

    Maybe you should use linear algebra instead of trigonometry. I'm not sure about the speed and memory differences, but usually it's easier to work with.

    My rant is complete. I hope I'm not looking like a complete ass now.
    Reply With Quote Edit / Delete Reply Germany Show Events Agree Agree x 3 (list)

  5. Post #5
    Gold Member
    Xera's Avatar
    November 2006
    3,097 Posts
    Wow really useful
    i have a question although its not to do with the lighting.
    Why do you use
    Code:
    #pragma once
    and not just
    Code:
    #ifndef header
    #define header
    #endif
    http://en.wikipedia.org/wiki/Pragma_once
    Reply With Quote Edit / Delete Reply United Kingdom Show Events Dumb Dumb x 3Agree Agree x 1 (list)

  6. Post #6
    Gold Member
    Darwin226's Avatar
    January 2009
    4,072 Posts
    Why are there always people rating dumb on threads like this?
    IMO it's above awesome if someone takes their time to write something like this with the sole purpose of helping someone else.

    Awesome tutorial.
    Reply With Quote Edit / Delete Reply Croatia Show Events Agree Agree x 4 (list)

  7. Post #7
    Gold Member
    r4nk_'s Avatar
    April 2005
    2,019 Posts
    Why are there always people rating dumb on threads like this?
    IMO it's above awesome if someone takes their time to write something like this with the sole purpose of helping someone else.

    Awesome tutorial.
    Exactly this, well done austech.

  8. Post #8
    Gold Member
    jA_cOp's Avatar
    May 2006
    2,685 Posts
    Returning a const copy is senseless. It's just a copy so it can be copied into a non-const object. Returning something as const only makes sense for pointers to const objects and const references.
    It's actually not completely without purpose. It doesn't affect good code, but it stops you from accidentally doing meaningless stuff like:
    e.GetCenter(fr) = sf::Vector2f(1, 2); // ???
    

    However, this is only for structs and classes. He's also returning stuff like "const float", which is meaningless.

    I strongly agree with the rest of your post.

    edit:

    And the change to initializer lists can make a huge difference if the contained type has a complex default constructor or destructor. It's also nice to be able to seamlessly separate member initialization code from more complicated object construction code.

  9. Post #9
    Richy19's Avatar
    May 2010
    5,382 Posts
    Thats weird
    i tried this code in MS VS 2k8 and apart from only having 10FPS it worked ok
    However the same code on the same setup in Code::blocks didnt

  10. Post #10
    open.gl
    Overv's Avatar
    February 2007
    7,431 Posts
    Why are there always people rating dumb on threads like this?
    IMO it's above awesome if someone takes their time to write something like this with the sole purpose of helping someone else.

    Awesome tutorial.
    Because you can find a much better way to do the shadows on that website linked by ZeekyHBomb.
    Reply With Quote Edit / Delete Reply Netherlands Show Events Agree Agree x 4Dumb Dumb x 1 (list)

  11. Post #11
    Gold Member
    Darwin226's Avatar
    January 2009
    4,072 Posts
    So that makes his effort to help and give his perspective dumb?
    So what is someone did it better, it's not like everything that people do on this forum is original and better than anything of that sort.

  12. Post #12
    open.gl
    Overv's Avatar
    February 2007
    7,431 Posts
    So that makes his effort to help and give his perspective dumb?
    So what is someone did it better, it's not like everything that people do on this forum is original and better than anything of that sort.
    Because it isn't just better, tracing hundreds of lines like that is simply a horrible way to do it.
    Reply With Quote Edit / Delete Reply Netherlands Show Events Agree Agree x 4Dumb Dumb x 1 (list)

  13. Post #13
    Richy19's Avatar
    May 2010
    5,382 Posts
    Because it isn't just better, tracing hundreds of lines like that is simply a horrible way to do it.
    The gamedev example might ave better ways of doing it but i at least couldt understand it.
    this one is easy to understand and i get the whole process
    now maybe i can go on gamedev and see if that one makes any more sence

    At the end of the day if you dont like it then dont read it
    but its nice to see a GOOD tutorial every now and then

  14. Post #14
    open.gl
    Overv's Avatar
    February 2007
    7,431 Posts
    I'm sorry for being such an ass, but it's just a bad idea if people are actually going to do it this way. With the immense performance loss this causes, there's not much room left for other effects.
    Reply With Quote Edit / Delete Reply Netherlands Show Events Agree Agree x 3Dumb Dumb x 1 (list)

  15. Post #15
    Interesting that you say that. Friend.
    NorthernGate's Avatar
    August 2007
    3,378 Posts
    Thanks for taking the time to write this Austech, I know a lot of people were asking you how you did it. It was really helpful.

  16. Post #16
    Gold Member
    thomasfn's Avatar
    July 2008
    2,959 Posts
    I'm with overv on this. We all appreciate good tutorials, and it seems lots of people found this helpful, but brute-force raytracing is not the way to go for a realtime engine.

  17. Post #17
    Gold Member
    efeX's Avatar
    April 2009
    2,332 Posts
    -snip-

  18. Post #18
    Gold Member
    BlkDucky's Avatar
    May 2008
    6,484 Posts
    Oh God yes. So reading this when I have the time. Bookmarked.

  19. Post #19
    xAustechx's Avatar
    July 2009
    584 Posts
    Just a quick note, I am aware of that this may not be the best way of doing dynamic lighting. But it is a way non the less. But thanks for the feedback. I'm currently working on my lighting system again and I'm trying to improve it.

    But yeah, thanks for all the feedback.

    Also ZeekyHBomb thanks for all that info in the reply.


    Thanks again everyone. :)

  20. Post #20
    Gold Member

    March 2005
    3,028 Posts
    Your lighting is all wrong. Objects in shadow should not be more visible than illumunated objects.
    You're overlaying a translucent white filter on "bright" areas, which reduces contrast and actually makes things more difficult to see. Instead, you should darken the area in shadow.

  21. Post #21
    xAustechx's Avatar
    July 2009
    584 Posts
    Your lighting is all wrong. Objects in shadow should not be more visible than illumunated objects.
    You're overlaying a translucent white filter on "bright" areas, which reduces contrast and actually makes things more difficult to see. Instead, you should darken the area in shadow.
    This just shows how to do the lighting portion with rays and shortening the lights based on things blocking it. I didn't add portions of blending because that's more library dependent since I'm sure there's various ways to do it.

    For example, in SFML I use a RenderImage to draw over the world. Set the light ray's blending to "none". And draw the rays to the render image. Giving me a result like this:



    Or this

  22. Post #22
    Gold Member
    Jallen's Avatar
    December 2007
    7,538 Posts
    Cool tutorial, although spamming rays is probably not the best idea :P
    Still, it looks good!

    As previously stated, your definition of dynamic and static lighting is incorrect.

    Also change [cpp] to [code]

    Your lighting is all wrong. Objects in shadow should not be more visible than illumunated objects.
    You're overlaying a translucent white filter on "bright" areas, which reduces contrast and actually makes things more difficult to see. Instead, you should darken the area in shadow.
    Yeah, the way I'd do it is have a black buffer the size of the render window, then for each light i would subtract from the alpha and if neccessary based on the colour of the lights then change some of the colour values too.

  23. Post #23
    Gold Member

    March 2005
    3,028 Posts
    Yeah, the way I'd do it is have a black buffer the size of the render window, then for each light i would subtract from the alpha and if neccessary based on the colour of the lights then change some of the colour values too.
    Or render out a proper lightmap and use multiplicative blending, which is really how diffuse lighting works. The final color of any given pixel is its initial unshaded color multiplied by the sum of the light intensities.

    Render all the lights additively on a black background, multiply with fullbright scene.

    Light intensity should also decay with distance (proportional to 1/distance^2), although I don't know if this is really feasible in SFML.