In this post we will be simulating the following effect, in which our pillars cast shadows that move as the player moves. This technique is one we used when creating the games Entombed and Brimstone.
In order to create this effect, it is enough to know how to create a single shadow from a single pillar. We will represent the pillar by (x,y) coordinates for each of its four corners, which we will assume is information that is available to the algorithm; we also presume that the algorithm is aware of the coordinates of the light source. To implement the algorithm, first we set up the points (x1,y1), (x2,y2), (x3,y3) and (x4,y4), which obey the following rules:
- (x2,y2) is the pillar corner which is furthest from the light source.
- If two of the corners are equidistant from the light source, then (x4,y4) is the pillar corner which is (tied to be) closest to the light source. Otherwise, (x4,y4) are both -1.
- (x1,y1) is the pillar corner which is second-closest to the light source.
- (x3,y3) is the pillar corner which is second-furthest from the light source.
We have successfully found the coordinates which determine the inner boundary of the shadow; now we need to find the coordinates which determine the outer boundary of the shadow. These points we will call (u1,v1), (u2,v2), (u3,v3) and (u4,v4). To find them, we define a global scale factor f which is the same for all pillars and determines how long the shadows are (a value of 0 gives no shadows, while a value of 1 gives infinitely long shadows). With f defined, we can find our boundary points as follows:
- Find the vector extending from the light source to the point (u1,v1).
- Find r1 and θ1, the radius and angle of this vector.
- Define u1 = x1 + f r1 cos(θ1)
- Let v1 = y1 + f r1 sin(θ1)
- Do the same for all of our (u,v) pairs.
With these pairs defined, we can draw our shadow by filling in the region described by the following path:
(x1,y1) -> (u1,v1) -> (u2,v2) -> (u3,v3) -> (u4,v4) -> (x4,y4) -> (x3,y3) -> (x2,y2) -> (x1,y1)
Note that if x4 and y4 is negative, exclude both (x4,y4) and (u4,v4) from the path above.
There is one further caveat to be noted. Usually when drawing a shadow you want it to be semitransparent, but the HTML5 canvas causes unrealistic-looking shadows when it layers one over another. This is because semitransparent textures add up to make a less transparent texture, rather than all the shadows being the same transparency like would be physical. To fix this, we drew our shadows on a separate canvas (hidden from view) in full black and then drew the results of this canvas onto the desired one using the drawImage property (which can except canvases as an argument) with a globalAlpha of our desired transparency. We found this technique to be quite effective.