Skip to content

Instantly share code, notes, and snippets.

@ianmackenzie
Last active April 21, 2020 13:50
Show Gist options
  • Save ianmackenzie/92835d03673173751f116202edef6ac0 to your computer and use it in GitHub Desktop.
Save ianmackenzie/92835d03673173751f116202edef6ac0 to your computer and use it in GitHub Desktop.
elm-3d-scene shadow internals

Transcribed from Slack:

  • Shadows in elm-3d-scene are implemented using shadow volumes: the basic idea is that you take the faces pointing away from the light and 'push' them away from the light, keep the faces pointing toward the light in the same place, and add connecting faces between the two (via some magic in the vertex shader)
  • This shape is then a 'shadow volume', and you can check whether a spot on a surface is in shadow by checking whether it is inside that volume (via some magic with the stencil buffer)
  • Initially I was just using a huge default offset value of 1.0e9 to push back the 'back' faces, but it turns out that's too big for the default precision float value used on iOS
  • So the first fix (that I'd been planning to do at some point anyways) was to figure out a more reasonable offset value by calculating a bounding box for the entire scene, and using the diameter of that bounding box as the offset value
  • Adding that logic in meant that I could also do something else that I'd been planning to do - more aggressively compute near and far plane values to more tightly bound the scene, which should improve depth buffer accuracy
  • This was good, because just changing the offset wasn't enough - it was better, but there were still artifacts
  • Unfortunately, more aggressive clip planes led to problems where the shadow volumes themselves were being clipped (since they extend outside the actual scene geometry)
  • Which meant I had to do something else that I'd been planning to do: switch from the more-intuitive "Z-pass" variant of shadow volumes to the conceptually weirder but more robust "Z-fail" version
  • This involved a bunch of work itself since the Z-fail version requires all shadow volumes to have proper end caps (in Z-pass you can get away with just having the sides)
  • (Z-fail also requires meshes to themselves be properly closed, which is why I switched from the monkey to the duck - the monkey has a bunch of open edges which mess things up)
  • OK so now we have reasonable shadow offset value, proper Z-fail shadow volumes and a more accurate depth buffer
  • Still not enough though...on iOS there were still lots of cases where meshes would shadow themselves (you'd see patchy shadows on the side of an object facing a light, where those faces were seen as shadowing themselves)
  • The main fix here was switching from precision mediump float to precision highp float everywhere to explicitly request high-precision floating point math in shaders (including the implicit depth calculations, I think)
  • That mostly fixed things but there were still occasional artifacts, so I have a couple other slightly hacky tricks in there currently like using polygon offset to push back shadow volumes slightly, and also offsetting light-facing triangles very slightly away from the light (into the object) when constructing the shadow volume initially
    • Had to be fairly conservative with polygon offset - using more aggressive values there led to visible shadow artifacts, like sphere shadows with a sharp corner
  • precision highp float should be supported on almost all devices these days, but strictly speaking it's only guaranteed to have support in the vertex shader
  • So I've started work on using different precision values in different shaders as appropriate - if you want lighting then you might miss out on 1% of devices, but if you stick to unlit materials (constant color, color texture) then things should work anywhere that WebGL is supported
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment