8
\$\begingroup\$

I have been working on a 2D tile game and I have implemented hard-edged lighting:

enter image description here

I want it to be smoothed out a bit. I don't need shadows or anything, just simple lighting. I'd like it to look more like this:

enter image description here

My current system uses light levels for each tile in the world and they are recalculated, when a tile is placed or removed. I'm using batch.setColor(...) to shade the tiles. What is a good way to achieve this smooth lighting?

I don't want to use the light map overlay method, I tried that already and I was not happy with the result. I want to be able to set how much light is able to pass through a block. For examble a dirt block should absorb some of the light, but a glass block should not block any light. This was not really possible with the light map overlay method. UPDATE: I missunderstood what this method actually is. I understand now. I was thinking the wrong way. Sorry!

\$\endgroup\$
2
  • \$\begingroup\$ To be honest, it looks more stylish with hard-edged lighting. \$\endgroup\$ Jul 27, 2016 at 16:35
  • \$\begingroup\$ I don't like it that much, also its a bit hard to differentiate background tile and foreground tiles with this style. \$\endgroup\$
    – ProRed
    Jul 27, 2016 at 16:55

5 Answers 5

15
+50
\$\begingroup\$

A simple way to achieve smooth lighting in a tile based game, is to draw a "lightmap" to a render target, and then drawing this render target over top your scene while alpha blending it.

Your light map render target would be the size of your tile map, but in pixels. Each pixel would represent the light color of its corresponding tile. This render texture would look something like this (top left texture in black and white):

enter image description here

Once you have this light map texture, you can then draw it as an overlay over your game world (stretched out). You are going to have to clamp it properly, so that it stretches perfectly overtop your tiles in the game world.

Then it is a matter of alpha blending this texture onto your game world in libGDX. In order to blend in LibGDX (I personally don't know how), you may have to look at the Gdx.gl.glBlendFunc function.

\$\endgroup\$
9
  • 2
    \$\begingroup\$ You can control the lighting the way you want with this system. Not sure what you are referring to when you say you can't block light. Also, not entirely sure how you plan to smooth out your current system, as you are literally just coloring your tiles. \$\endgroup\$
    – jgallant
    Jul 25, 2016 at 14:09
  • 4
    \$\begingroup\$ By the way, this system is the way Starbound does it. \$\endgroup\$
    – Qqwy
    Jul 27, 2016 at 9:38
  • 2
    \$\begingroup\$ This is a great approach, and allows flexibility where it is needed. I use this in my tile based game where I don't have access to shaders, and it works great! \$\endgroup\$
    – user35344
    Jul 27, 2016 at 16:21
  • 1
    \$\begingroup\$ UPDATE: This seems the way to go, I missunderstood this method and I understand now fully! I will try to implement this method now and I will mark this as the best answer and give you the bounty! \$\endgroup\$
    – ProRed
    Jul 27, 2016 at 18:28
  • 2
    \$\begingroup\$ If you have further questions about how to do it, feel free to ask. I have implemented this, and @Tyyppi_77 has as well recently. I've done it in XNA/Monogame and Unity. Tyyppi has done it in SDL. \$\endgroup\$
    – jgallant
    Jul 27, 2016 at 18:30
4
\$\begingroup\$

an engine agnostic way to do it is to use average light mapping. First, you need to generate a black and white map as a 2D array of booleans that is the size of the world where the blocks are True and empty is False.

Like This(1 is black, 0 is white): map

Then you need to create a new 2D array that is the same size as the first array but is an array of floats. Then you go from black(True) block to black block in the array and average the nearby sampled value(explained below)

This image will help: averaged

This image represents the sampling(where + means false and | means true). what we do is treat True values as 1 and False values as 0. Then you average the 9 numbers and put it into the correct part of the array. NOTE: Make sure that when you are sampling edges you use treat outer values as context sensitive. For example: underground the map outside the world might be 1 and the sky should be 0.

It should looks like this(taken from other post):end

Then all you have to do is tint each block in the array with

rgba(tint,tint,tint,1)

(tint = the current averaged block value in the second array and alpha doesn't matter). Note: this assumes that the pseudo-function rgba takes floats from 0-1 instead of 0-255, if it does, just do this:

rgba(tint*255,tint*255,tint*255,255)

Hope This Made a little sense :) If you want code, please ask!

\$\endgroup\$
1
  • \$\begingroup\$ Fist off thanks for your answer, but the thing is I already have hard-edge lighting in my game (here). I asked how I could change this to look more smooth (like this) \$\endgroup\$
    – ProRed
    Jul 26, 2016 at 21:59
1
\$\begingroup\$

Since you are talking about things blocking light, I'm assuming you have some model for how light should spread. For example everything with no blocks above it is completely lit, light spreads to all neighbouring cells but loses a certain amount of brightness.

You should be able to implement those rules fairly easily on the software side at least the smoothing part is then just a matter of throwing some shader at the light map you have generated.

The methods others have suggested with overlaying a light map is something you should do anyway. But you can change how you generate this light map. You can initially fill it with just the block information, but then you could scale it up and smoothen it out in a shader for example. An added benefit of the light map approach is that you can simply draw dynamic lights into your light map as well to highlight explosions or what have you and it will naturally blend with the rest of your world.

Additive blend stuff into your light map then multiplicative blend your lightmap with the game world.

I did this a while ago which uses the same principles. This system does not support having light fall of differently for different types of terrain but at least it should illustrate the usefulness of overlaying a light map.

\$\endgroup\$
2
  • \$\begingroup\$ I see, so I would render the light levels in form of rectangles that are exactly the size of the corresponding tile to a second render target? And how would I go about smoothing it out? \$\endgroup\$
    – ProRed
    Jul 27, 2016 at 18:19
  • \$\begingroup\$ You can let the GPU handle it by downsampling it, since when rendering it will just linearly interpolate for you. If that's not good enough you can run some sort of blur over it in a shader. You can also consider to render in finer granularity than tile resolution if you want to draw it differently depending on adjacent tiles. Basically the hard part is coming up with the specific rules you want for what area should be lit how much. If you write those rules down you will usually find a pretty straightforward way to do it. \$\endgroup\$ Jul 27, 2016 at 18:21
1
\$\begingroup\$

It's pretty easy for a torch : using a custom pixel shader you calculate the distance between each fragment and the torch ; you can then calculate where the first dense tile that blocks light coming from the torch. You then calculate the distance where the light isn't blocked, multiply that by something < 1 so the torch doesn't light the sun, substract that from total distance, and repeat for the rest of the distance (where light was blocked by at least one tile) with a multiply value way smaller... I know how to do this math and it's not too complicated to implement in opengl.

EDIT : for a simple light source with a specific center point, my method is extremely simple. However mine does not work with sunlight, but while the alpha map works really well for sunlight, it would NOT be a good idea for a torch, given that it is potentially a moving object, and making a new map every frame is not pleasant a life experience. The best solution in my opinion is a mix : distance calculating for specific lights that are actually objects in the game, a separate shader that you just throw on top of the alpha map you procedurally generated by using the technique mentionned above : a texture of all the tiles alphas from your hard shading, as lots of squares, and mess around with techniques to smooth it out.

\$\endgroup\$
0
\$\begingroup\$

Does your rendering use vertex colors (or can you change it) ?

Assuming each block is a 4-vertex square, that would give you a couple of options, for example:

If your current lighting algorithm gives you a light value per block, you could change it to give you a value per vertex . Alternately, at each vertex you could average the light color of the current block plus its three neighbors. Either solution would be inexpensive.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .