I am trying to implement a lighting effect in an HTML5/JavaScript game using tile replacement. What I have now is kind of working, but the transitions do not look smooth/natural enough as the light source moves around. Here's where I am now:

  1. Right now I have a background map that has a light/shadow spectrum PNG tilesheet applied to it - going from darkest tile to completely transparent. By default the darkest tile is drawn across the entire level on launch, covering all other layers etc.
  2. I am using my predetermined tile sizes (40 x 40px) to calculate the position of each tile and store its x and y coordinates in an array.
  3. I am then spawning a transparent 40 x 40px "grid block" entity at each position in the array
  4. The engine I'm using (ImpactJS) then allows me to calculate the distance from my light source entity to every instance of this grid block entity.
  5. I can then replace the tile underneath each of those grid block tiles with a tile of the appropriate transparency.

Currently I'm doing the calculation like this in each instance of the grid block entity that is spawned on the map:

var dist = this.distanceTo( ig.game.player );
var percentage = 100 * dist / 960;

if (percentage < 2) {
    // Spawns tile 64 of the shadow spectrum tilesheet at the specified position
    ig.game.backgroundMaps[2].setTile( this.pos.x, this.pos.y, 64 ); 
else if (percentage < 4) {
    ig.game.backgroundMaps[2].setTile( this.pos.x, this.pos.y, 63 );
else if (percentage < 6) {
    ig.game.backgroundMaps[2].setTile( this.pos.x, this.pos.y, 62 );
// etc...

The problem is that like I said, this type of calculation does not make the light source look very natural. Tile switching looks too sharp whereas ideally they would fade in and out smoothly using the spectrum tilesheet (I copied the tilesheet from another game that manages to do this, so I know it's not a problem with the tile shades. I'm just not sure how the other game is doing it). I'm thinking that perhaps my method of using percentages to switch out tiles could be replaced with a better/more dynamic proximity forumla of some sort that would allow for smoother transitions? Might anyone have any ideas for what I can do to improve the visuals here, or a better way of calculating proximity with the information I'm collecting about each tile?

(PS: I'm reposting this from Stack Overflow at someone's suggestion, sorry about the duplicate!)

  • 1
    \$\begingroup\$ Can you post screen shots of what you have and what you want to have? \$\endgroup\$
    – House
    Commented Mar 27, 2012 at 21:31
  • \$\begingroup\$ Sure, I will post a screenshot tonight. \$\endgroup\$ Commented Mar 28, 2012 at 1:56
  • 1
    \$\begingroup\$ You forgot to add a screen shot... \$\endgroup\$
    – House
    Commented Mar 30, 2012 at 18:09

2 Answers 2


First of all, I'm sorry that I won't be able to help you with your specific platform, as I've never done this sort of work in HTML5. So I'll try to address this in a language agnostic way.

I don't think you'll be able to get smooth transitions if you're calculating light on a tile per tile basis. This is analogous to the differences between using flat shading, gouraud shading or phong shading:

enter image description here

What you're doing is similar to flat shading, i.e. you're assigning a different light per tile which results in the sharp transitions between tiles as demonstrated on the picture above.

In order to get better results you'll have to calculate your lights on a per vertex basis (i.e. gouraud). I don't know what are the capabilities of your engine, but the ideal case would be if each of your tiles was a quad and you had direct access to its vertices. Then you would be able to calculate the distance from the light for each vertex and store your shade color right inside the vertex. Finally, the rendering pipeline would automatically take care of interpolating the shades accross the tile's surfaces resulting in a smoother look (aka gouraud shading).

For even better results albeit a bit more expensive you could calculate the light on a per pixel (i.e. phong) basis inside the fragment shader. The basic approach works well if you have just a couple of lights to sample as seems to be the case from your example.

Finally, another alternative which might work very well is to render all of your lights (and your lights only) into a separate render target first, optionally blur the result, and blend it together with the rest of the scene at the end. Check this question for a few ideas on this.

  • \$\begingroup\$ The final alternative seems like a good fit for HTML5 Canvas. Your other ones are not exposed to Canvas, unless ImpactJS specifically implements shaders and vertices, which I highly doubt. \$\endgroup\$
    – DMan
    Commented Mar 27, 2012 at 21:14
  • \$\begingroup\$ @DMan You're right, if it's canvas based it probably doesn't support those changes. I was thinking perhaps it might have been a WebGL engine, in which case it would be simple. \$\endgroup\$ Commented Mar 27, 2012 at 21:16
  • \$\begingroup\$ Thanks very much for your detailed response. I will look into your last suggestion tonight along with some other ideas I'm trying. \$\endgroup\$ Commented Mar 28, 2012 at 1:52

You appear to be lighting with a linear attenuation (your light intensity is dropping off linearly). You may want to try using the inverse square law. Or a simple exponential fall off.

float maxLightDistance = 64;
float fallOff = 1.4f;
float intensity = Math.pow(distanceFromSource / maxLightDistance, fallOff);
int tileToUse = (int)intensity;
ig.game.backgroundMaps[2].setTile( this.pos.x, this.pos.y, tileToUse);

The above code also helps you get rid of the atrocious if else if thing you have going.

I use just a basic exponential fall off, and get results like this (I can post a better image later of a single light source, I just don't have one at the moment):

light from lava

I should also mention that a program like Excel is great for things like this. You can test out formula changes pretty quickly and either graph them or use conditional rules for changing the color of a cell.

  • \$\begingroup\$ I'd like to note that changing the attenuation formula won't make the light be interpolated smoothly over the surface of the tiles. Each tile will still have a single color which suddenly changes when reaching the next tile. Using very small variations in luminosity can help conceal the problem and make it look good, but it's still not a smooth transition. Also I argue that the choice of attenuation has a lot to do with aesthetics, as I've had projects where I preferred the look of a linear attenuation (with a maximum and minimum radius of effect) rather than the inverse square law. \$\endgroup\$ Commented Mar 27, 2012 at 21:27
  • \$\begingroup\$ My suggestion is based on what the OP has already. It's meant to be an easy solution to try without the need to re-create the rendering system. And it can make a difference if the OPs method is placing tiles that are more than one shade difference next to each other. \$\endgroup\$
    – House
    Commented Mar 27, 2012 at 21:32
  • \$\begingroup\$ @spectralbat But I definitively agree with getting rid of that if else if chain. Make a method that maps percentage to tile index and replace all the branches with a single call to setTile like in Byte56's example. \$\endgroup\$ Commented Mar 27, 2012 at 21:34
  • \$\begingroup\$ Sure, it's a good thing that you pointed it out, especially considering how simple of a change it is. It might look better than what the OP currently has. :) I'm just pointing out that I don't think it has anything to do with the actual problem of smooth transitions between tiles. But maybe that's exactly what the OP was asking for and I was the one who misunderstood. ;-) \$\endgroup\$ Commented Mar 27, 2012 at 21:37
  • \$\begingroup\$ Thanks! Even if this doesn't solve my original problem I will try anything at all to make it look better. I will give this and some other options I've been looking into a try tonight. \$\endgroup\$ Commented Mar 28, 2012 at 1:54

You must log in to answer this question.

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