Tricks for Efficient Shadow Effect in Pure JavaScript
By: Joana Borges Late
Tags: Graphics Programming, Lighting, Game Development, JavaScript, Browser

The second solution — in details

The source code of Lost In Maze for lighting is contained in two files of 170 JavaScript lines each. We will see a compact simpler version, easier to understand.

First we will see the code for shadowing when the avatar is standing. After we will see that side effect caused by the reduction of the shadow layer, that only happens when the avatar is “moving”.

Note: as we are seeing code snippets, “use strict” does not appear although its present at the top of the source code files.

Preparing

We create the shadow layer: a 78 x 64 pixels canvas. Each dimension is exactly 10 times smaller than the respective dimension of the Lost In Maze game canvas (780 x 640 pixels).

We store as global variables the shadow layer itself, it’s 2D context, its ImageData and its array of pixel values (ImageData.data).

Creating the shadow layer

We use the shadow of the torch to create the default state for the shadow layer. We only need the data (array of pixel values).

Creating the torch shadow data

The last preparation step is creating data of the reduced bonfire shadow. The original bonfire shadow is 540 x 540 pixels. The reduced bonfire shadow is 54 x 54 pixels.

Creating the bonfire shadow data

Running

If the current loop of the game is not in Daylight mode, the program calls the drawLighting function.

Note: the fact that we draw shadows to represent light effects is a private detail of the lighting module, the rest of the program needs not to be involved in such subject. That’s why the interface functions of the lighting module are called initLighting and drawLighting.

Drawing the lighting

We reset the shadow layer using the data of the torch shadow (that is always in the center of the game canvas — easy). Notice that we are not using the canvas method clearRect or drawImage, because we are not working exactly on the 2D context of the shadow layer. We are directly manipulating its array of pixel values.

Resetting the shadow layer

Drawing bonfire shadows implicates creating a list of the bonfires (and respective coordinates) that are in the scene or are near enough for lighting the scene. This part is a bit complicated and related to the Lost In Maze mechanics. We will skip the analysis of the function drawBonfireShadows and we will focus the analysis of the function pasteBonfireShadow, that is responsible for pasting a bonfire shadow on the shadow layer, object of our study.

Pasting a bonfire shadow on the shadow layer

We don’t need to blur the shadow layer. But blurring it results in a nicer light effect. Another irony: the less important procedure takes the biggest code snippet.

Using the canvas method “blur” is not acceptable, because it creates blank pixels near the edges.

Blurring the shadow layer

This blur algorithm is dirty, reckless. It should be constructed with two arrays. One with the original pixels, just for reading. And a blank one just for store the resulting blurred pixels.

I made a lot of attempts and chose this because it was faster and, for my surprise, produced better shadows.

The second solution — the side effect and the fix

As I said before, reducing the shadow layer has a side effect. Any reduction would cause this side effect, but the more radical the reduction is, greater is the side effect. The side effect only happens when the avatar is moving.

This part is not complicated but requires that we focus our attention.

We know that the shadow of a bonfire must match the bonfire. When the avatar “moves” one pixel to the left, on the game canvas, it is the environment that moves one pixel to the right. So, the shadow of the bonfire must move one pixel to the right (matching the new position of the bonfire). Easy, right?

Hence, all we need to do is place the shadow layer (the canvas where torch and bonfire shadows are composed) one pixel to the right on the game canvas, right?

Wrong. The shadow layer, when expanded, has the same size of the game canvas. It must match the game canvas. It is not supposed to be displaced!!

What should be displaced one pixel to the right are the bonfire shadows INSIDE the shadow layer. The torch shadow must keep centered inside the shadow layer, like the avatar is centered in the game canvas.

Good. So when the avatar moves one pixel to the left we place the torch shadow in the center of the shadow layer (as always) and we place each bonfire shadow one pixel to the right. No big deal. What is the problem? — you ask me.

The problem is this: we are (for reasons of efficiency) working with REDUCED images. We place a reduced bonfire shadow one pixel to the right on the reduced shadow layer. For now everything is fine. But when the shadow layer is expanded ten times each side to cover the game canvas, that one pixel to the right becomes ten pixels to the right!!! Therefore, each bonfire shadow will miss its correct position to the respective bonfire by nine (ten minus one) horizontal pixels.

Trying to fix that, we displace the reduced bonfire shadow one pixel to the right on the reduced shadow layer ONLY when the avatar walks ten pixels to the left (on the normal game canvas), keeping the proportion. OK. It is much better now, but it is not good yet: