Tile by tile lighting - Shader/alpha values/lightmap?

Hi guys. I know this sort of question has been asked over and over but I can’t seem to find any information on what i’m specifically looking for.

I want to create a lighting system where the lighting is not exactly smooth. Each tile around the player will be lit with a slightly different alpha value and give a very block-y feel to the lights. Much like the image below:

http://soapquest.googlecode.com/svn/trunk/screenshots/SoapQuest%201024%201.jpg

I have a few ideas in mind but i’m not sure if they are the correct way of doing things. My first idea was to simply change the alpha value on blocks around the player but this seems expensive on the CPU as it will have to update every frame. My second idea was some kind of lightmap, where I blend the image over the player using OpenGL and a shader but I don’t know if this will give me my block-y effect.

Anyone every done any lighting like this before? :slight_smile:

Here’s (maybe) a good way of thinking about the problem. If you want to figure out how bright to make some part of your image, it depends totally on how far that part is from the light source. So, you’re going to be doing a distance calculation like: distance = sqrt(xDistance^2 + yDistance^2) and then you use that distance to figure out how much to darken or lighten things. When you calculate xDistance and yDistance, though you probably have something like: x2 - x1 and y2 - y1, and those x’s and y’s refer to pixels on your screen, so you would get a smooth gradient in lighting as distance varies. But what you want is for all of the pixels in a given rectangle to always have the same ‘distance’ value—to be treated kinda of like giant pixels themselves. If you just base your distance calculation on your ‘grid coordinates’ instead of pixel coordinates, you’ll get the desired effect. This means your xDistance and yDistance are calculated with gridX2 - gridX1 and gridY2 - gridY1 instead. You can convert from pixel coordinates to ‘grid coordinates’ pretty easily — think about using division to do it.

To do it in a shader, you could use standard Phong or Gouraud shading (you can find examples on Google), but you’d have to modify it to base its distance calculation on ‘grid coordinates’ like above.

Unless you’re really wanting to experiment with shader’s though, I’d just do it on the CPU. If your game feels slow, then profile it to see what’s actually slowing you down (it’s easy for our intuition to be off on this stuff); if you find that this algorithm really is slowing you down, you might move it to the GPU at that point.

My guess at what would be slow with the cpu version (where you modify an alpha value and blend with a black background), is in doing the blending calculations for every pixel on the GPU—but that’s also probably not really a problem, and again it’s best to profile it and find out.

This could work…
In pseudocode;


for (current tiles visible on screen) {

float d = distanceFormula(lightSourceX, currentTileCenterX, lightSourceY, currentTileCenterY);
if (d < A_CONSTANT) {
tile.adjustBrightness(BRIGHTER);
}
//add more if statements for more gradiented light.
//if smooth gradients are in need, you need to use linear interpolation.
}

Thanks guys, this is what I was looking for.

Surely I’d have to create a black texture with the same dimensions as 1 tile, and lay it over all my tiles. Then adjust the alpha value depending on the light distance, correct? I know this likely isn’t efficient though

Guess this depends on how you are doing it, are you using opengl? Could be simple to fit into a shader , just need some uniforms handed through.

I’m using LibGDX :slight_smile: I have very limited knowledge of OpenGL/shaders

Thats what I was thinking , libGDX . Doesn’t it have a built in lighting system anyway? Sure you could tweak it.

As far as the efficiency part of your question goes, that shouldn’t really affect performance in a meaningful way as long as you’re isolating the problem to what’s in view and preferably only in the area light affects, rather than everything in your game. There’s a couple extra things you could do though.

Firstly, you don’t need to use the distance formula, you only need to know what the lighting value of blocks are at X distance from you. So either use the formula once when loading the game, and create a lookup table, or just manually write a lookup table. When you move, simply set the tiles around you to their approriate lighting value, and clear the lighting of anything outside of the new area.

You can avoid blending by just not requiring it, and you’re pixel/fragment shader would just be a simple pass through of a lighting scalar. In a game like the one you’ve shown, you really only need to redraw what changes, when something changes, because it’s easy to know, unlike more complicated games. I’m not sure if dirty rectangles are still a thing, but that’s probably going too far. Of course, you should benchmark any approach, and get the application working first.

Or you could create a screen size light map, which you create once and just render it over the top of your game. With the right blending functions you should be able to achieve the same effect.

That’s another interesting idea, Husk.The lightmap would require a shader, correct? And as you said I’d have to find some way of blending that would actually create the block effect (Google should help with that) It’s good to know that there are different ways of getting the same effect :point:

@icass doesn’t LibGDX only support Box2D lighting out of the box?

If you’re doing modern OpenGL, which you probably should be, shaders it is. You don’t have to do it by blending, you could calculate the final pixel colour in the shader. I’m not sure which way would perform better, you’ll have to benchmark that. Depending on how your game works, you might even be able to do the calculation in the vertex shader and let the GPU interpolate, though I’m not sure since I’ve never done it, but I like to think about it. :stuck_out_tongue:

It’d simply be a matter of uploading a texture like this to the GPU:

Then either, use the shader to blend the tile and lightmap together, and output the final colour, or render the lightmap (using a simple passthrough shader) after you’ve rendered the tiles as their default colour and enable blending (you’ll have to find the right blending function). Each tile only needs to correspond to one pixel in the lightmap.

All approaches should be sufficiently fast even on low spec computers, it’s up to you on how you want to balance performance/development complexity. My guess is passing alpha values once to the GPU and using those will be fastest, but there is a reason we do benchmarks and testing. :stuck_out_tongue:

You could have a 1x1 texture generated at runtime (or even stored if you wanted) and draw black tiles (with different alpha) on top of each block. This would also allow you to “light” entities.

I know you did want a blockier look, but you could even use gradients to smooth it out:

I know that your game is top down, but if you’re going for a “gravity-down” (I can’t remember the term for it right now, but a game like Terraria) game, you will need to use a shader to mask the lighting onto the world terrain so the “shadows” you drew don’t interfere with the background (this guy had that problem in this thread).

Performance wise, this isn’t all that bad if you only render the blocks around you. In your image, you are rendering 20x13 (ish) blocks. I’m rendering a lot more and it still does quite decent even on old machines.

So I just got done on my first try of implementing lighting! Sadly it only works for the player right now as I designed it around shadows where the player isn’t rather than where the light is but I will change this. This method follows what Chrislo, Archive and Weston said

I will also need to learn how to only render the tiles on the screen as I had 45 fps with an i5 and GTX 970 :persecutioncomplex: but this was rendering a 2000x1900 world or so

I have a video! :slight_smile: what do you guys think? (sorry about my coding music ::))

t0yXq7LCpaA

Still you should be getting a few thousand fps with that. Something is definately wrong here , are you rendering the entire scene?

I’ve manged to get it to hit 60 easily but I can’t tell how much more it is due to LibGDX not allowing me to turn off VSync O.o

Nevermind, fixed. 2000 fps

Just one more final question :persecutioncomplex:

If I want to have lights such as fires using this method, wouldn’t this overwrite the previous alpha values and then change them.

For example, I make all the tiles that are more than 8 blocks away from the player very dark, this means that when the fire is setting the alpha values then they will instantly get overwritten by the player and the light will never show

Factor all light sources into your calculation (set the tile’s light value to the highest computed value from all light sources).

I’d have to use a comparator for that, right?. I could test if the alpha value is below 0.8 (that must mean that the tile is lit) and if it is, I could then set the tile to that value

Something along the lines of:

float tileLight = 0;
for (LightSource source : sources) {
    tileLight = Math.max(tileLight, source.getLight(tilePosition);
}
float darknessFactor = 1f - tileLight; // if you darken tiles
// use 'tileLight'

My lighting engine is in these two classes here: https://github.com/chrislo27/ProjectMP/tree/master/core/src/projectmp/client/lighting

It has “diamond lighting” so it’s not exactly the same as yours (yours is more “globular”).

The meat and potaters of my lighting engine are in the recursion method:


private void recursiveLight(int x, int y, byte bright, int color, boolean source) {
		lightingUpdateMethodCalls++;
		
		if (bright <= 0) {
			return;
		}
		if (getBrightness(x, y) >= bright && !source) {
			return;
		}
		if (x < 0 || y < 0 || x >= sizex || y >= sizey) {
			return;
		}

		setBrightness(bright, x, y);

		bright = (byte) MathUtils.clamp(bright
				- (world.getBlock(x, y).lightSubtraction(world, x, y) * 127), 0, 127);

		if (bright <= 0) return;

		mixColors(x - 1, y, color);
		mixColors(x, y - 1, color);
		mixColors(x, y + 1, color);
		mixColors(x + 1, y, color);
		
		if ((x - 1 >= 0) && !(getBrightness(x - 1, y) >= bright)) {
			recursiveLight(x - 1, y, bright, color, false);
		}
		if ((y - 1 >= 0) && !(getBrightness(x, y - 1) >= bright)) {
			recursiveLight(x, y - 1, bright, color, false);
		}
		if ((x + 1 < sizex) && !(getBrightness(x + 1, y) >= bright)) {
			recursiveLight(x + 1, y, bright, color, false);
		}
		if ((y + 1 < sizey) && !(getBrightness(x, y + 1) >= bright)) {
			recursiveLight(x, y + 1, bright, color, false);
		}
	}

Unlike how CopyableCougar4 said to “add” the lights together, I simply take the highest one (that is, when the method is called at a block, if the light at the block is already brighter to skip setting the light value). I also have checks if the brightness where the recursion will occur is already bright enough, if so don’t call it (because calling a method is much much slower than an if statement).

There’s also a lot of boring code such as copying the lighting data to another array because I found something where as I was looping through the array, light sources that finished recurring would re-do their recursion again as the for loop went through. That ended up with 200k method calls and took 500 milliseconds to process. Now it only takes around 2-10 ms to process. I also only update the lighting around the player. As the player moves near to the edge of the lit area it will re-light the surroundings. I also have coloured light support.

Data wise, lights are stored in bytes (I actually used signed bytes even though I don’t use negative numbers :slight_smile: and the higher the number, the more light it is. When I render, it flips the number around (127 - lightValue) to convert it into an alpha which I can use to draw the “shadows”.

I hope this helps!

cough My code examples don’t add; they take the highest one.

Just curious, why did you choose recursion?

I also don’t think that SauronWatchesYou needs such an intensive use case with recursion and mixing colors. I think that a simple snippet like the one I provided run every frame where movements or updates occur would suffice.