Shadows undoubtedly make 3D scenes look more realistic. I’ve been striving to implement proper shadows since I got into WebGL. Turns out this is not as straightforward as using the box-shadow property in CSS!
My first attempt was to make Thibaut’s snake game look nice in 3D. You can play it here. The idea was to render the same object for the second time, but applying a special matrix transform, that smashes it down on the ground in the direction of light. This worked fine for the snake game, where the shadows only needed to be on the floor.
However, this method doesn’t work if you need to cast shadows on other objects — which was the situation that I faced when implementing examples for elm-physics. I still chose to go with shadows on the floor, because it looked better than no shadows at all.
My second attempt was to use shadow maps, which currently is the most widely used method. Shadow maps require multipass rendering: you first need to render the scene from the light’s point of view onto a temporary buffer, and then render the same scene again from the camera, while looking up the shadow pixels from this buffer. Unfortunately, this cannot be done using the current Elm WebGL. I spent a lot of time prototyping possible solutions, with the help of Michel van der Hulst. It turned out that such functionality cannot be properly implemented on top of the existing declarative API while at the same time being both performant and nice to use.
Does it mean we cannot render shadows in Elm? Not until now!
It took us just two sessions to implement a working solution, thanks to Ian’s extensive knowledge in 3D maths. A shadow volume is created from the original object by extending its silhouette in the direction of light. The scene is first rendered as if everything was in shadow. Secondly, the invisible shadow volumes are rendered in order to create a mask in the stencil buffer. Lastly, the scene is rendered again, but only the highlighted parts, masked by the stencil buffer.
The final prototype can be seen here. On desktop, you can orbit the scene by dragging with the mouse. If you’re interested in implementation details, check the source here. If you want a high level API to render realistically looking scenes in Elm, wait a few months until elm-conf, where Ian will present his 3D rendering engine!