Lighting Bloom on a Tilemap

Lighting Bloom

Bloom for lighting can be an involved topic also, but we'll be looking at it from a simple perspective. If your lights are in an atmosphere, you can expect some degree of bloom. This is where light is scattered as it passes through air, smoke, noxious gasses - whatever makes your game world "atmospheric" (hoho).

This is also to emulate light bouncing off surfaces, and the other ways in which light moves about in the real world. It goes around corners, to some degree, in reality, so that's what we're looking at here.

View example

We'll add a new global setting that determines how much light we'll keep from a tiles light intensity to the tiles it blooms to. For this example, I'm using the value 0.75, or 75% of the lights intensity will be used on the tiles it blooms to.

var bloom = 0.75;

Most of our code is the same as our previous Raytracing Lighting on Tilemaps tutorial, but we'll expand the calculate method. We start with the complete code from the previous tutorial:

Light.prototype.calculate = function()
{
	this.area = {};
	var edges = pointsOnCircumference(this.position[0], this.position[1],
		this.radius);
	
	for(var e in edges)
	{
		var line = bresenhamsLine(this.position[0], this.position[1],
			edges[e][0], edges[e][1]);
		var mult = this.intensity / line.length;
		
		for(var l in line)
		{
			if(line[0] < 0 || line[0]>=mapW ||
				line[1] < 0 || line[1]>=mapH) { break; }
				
			var idx = ((line[l][1]*mapW)+line[l][0]);
			var strength = mult * (line.length - l);
			
			if(!(idx in this.area) || this.area[idx]<strength)
			{
				this.area[idx] = (strength > 1 ? 1 : strength);
			}
			
			if(mapData[idx]==0) { break; }
		}
	}

But before we close this function, we're adding our bloom loops. We'll create an empty array called openList, and add all of the points in the area array touched by this light which are not solid (mapData value of 0 for solid, 1 for not solid in our example) to the list.

	var openList = [];
	
	for(var a in this.area)
	{
		if(mapData[a]==1) { openList.push(a); }
	}

Now we begin our main bloom loop. While we've got entries in the openList array, we'll pop the value off the end of the array and store it in a variable called idx - this will be an index in the mapData array.

	while(openList.length > 0)
	{
		var idx = parseInt(openList.pop());

We use parseInt() to ensure the browsers Javascript engine treats this value as an Integer.

The bloom that scatters from this tile is the intensity from the area property of this light at the idx position, multiplied by our global bloom value (0.75):

		var bloomAmt = this.area[idx] * bloom;

If the amount of bloom generated by this tile is less than the baseLighting for our map, there's no point in spreading it any further. If your baseLighting is very low, I recommend you replace this with a bottom-level cut-off, so your light doesn't bloom over the whole map (replace the reference to baseLighting with 0.2, for example); while there's no problem with this per se, it can add a lot of processing demand on big maps:

		if(bloomAmt <= baseLighting) { continue; }

We now create a list of neighbours - tiles that are next to the current tile in each of the cardinal directions. We only add neighbours that fall within map bounds.

		var neighbours = [];
		if((idx - mapW) > 0) 		{ neighbours.push(idx - mapW); }
		if((idx + mapW) < (mapW * mapH)) { neighbours.push(idx + mapW); }
		if((idx % mapW) > 0) 		{ neighbours.push(idx - 1); }
		if((idx % mapW) < (mapW - 1)) 	{ neighbours.push(idx + 1); }

We now loop through the neighbouring tiles that were within map bounds...

		for(var n in neighbours)
		{

If this tile is either not already in the lights area, or the current illumination value for this tile in the area is less than bloomAmt, and the tile is of a type that light can pass through (a value of 1 in our mapData example array), we add it to the openList:

			if((!(neighbours[n] in this.area) ||
				this.area[neighbours[n]] < bloomAmt) &&
				mapData[neighbours[n]]==1)
			{
				openList.push(neighbours[n]);
			}

If the neighbour is already in the area lit by this light and the current illumination strength on this tile is less than the bloomAmt we've calculated for these tiles, or this tile is not yet in the illumination area at all, we set the illumination value for this tile:

			if((neighbours[n] in this.area &&
				this.area[neighbours[n]] < bloomAmt) ||
				!(neighbours[n] in this.area))
			{
				this.area[neighbours[n]] = bloomAmt;
			}

...and we can close our loops and this function is done!

		}
	}
};
Page loaded in 0.009 second(s).