With several features involving light, I wanted to explore the absence of light. One of the resources I’ve used as a reference when working on graphics projects is LearnOpenGL.com, and for this feature I studied Joey de Vries’ guide to directional shadow mapping.
Shadow Mapping for a directional light source
For directional shadow mapping the premise is to render the scene using an orthographic projection from a particular position, but instead of writing colour, writing the depth to the nearest visible fragment. The regular render pass is then performed, with each vertex being transformed by the light’s view projection. In the fragment shader, the depth texture is sampled, and the distance from the fragment to the light source is compared with the depth texture sample. If the depth texture’s sample is nearest, then the fragment must be in shadow, and the fragment’s colour value can be adjusted to reflect this.
I created a light depth shader for the first pass, and the changes necessary to the regular shaders, and the orchestration logic for sending the light’s position and projection to them, as well as a light depth texture in which to store the depth map. At this point, I’ll note that I got very lucky in terms of the values I chose for the position and distance of the light from the planet, as well as the size of the texture that I chose, because it came out looking exactly as I wanted it to. The planet cast a shadow over the rings, the rings cast a shadow over the planet, and the spaceship cast a shadow over both of them. Clearly I’m brilliant at this?
Shadow Mapping for a point light source
The directional source was great, but I’ve shaded the planet based on the idea that light hitting the planet comes from a local star. A star emits light in all directions, and while the shading is based on that idea, the shadow map is not. Before updating the shadow mapping to support point light sources, I wanted to add a visual representation of the local star so that I had a reference for where the light should be coming from. I added a plain white icosahedron which automatically glowed a little, owing to the bloom effect.
I then adapted the directional shadow map to use perspective projection and a 90° field of view. This meant rendering the scene 6 times from the star’s perspective, updating the camera to point in each of the cardinal directions and write to a different light depth texture for each pass. The shadows disappeared. Clearly I’m not brilliant at this…
To debug the shadow, I played with different values including the planet’s position from the local star. I noticed that when the planet was close to the star, then the shadow mapping worked, but the further it got, the more aliased the shadow became. I realised that since the point light shadows require perspective projection things that were very far away would take up a tiny amount of the depth map and therefore when sampled at a large distance there would be aliasing. One option would be to increase the size of the depth map, but how big? The further away from the light I wanted to draw things, the bigger the texture would need to be. An alternative approach could still be to use directional mapping, but I would have to work out which parts of the scene would be likely to be in shadow and then use multiple shadow maps to get enough resolution in given areas.
Before diving down this route, I looked for prior art to see if there were common approaches for this problem. One particular Stack Overflow question described my exact problem, even down to the fact that it related to a space scene. The top response suggested a similar approach to what I’d been thinking, recommending a shadow map for a specific area, but also detailed an alternative approach using ray-sphere intersections to calculate overlapping angular areas for spherical bodies. There were several new concepts here for me to digest but the approach was great. It has a limitation of only casting shadows for these large spherical occluding bodies, so the rings cast by the shadows and the spaceship were out. At this point I’m happy with this trade off to get large scale point light shadows. I’ll return to the smaller scale later on, maybe trying out my multiple shadow maps idea.
This entry is part of a series on writing a Metal Renderer in Swift Playgrounds.