A 2D Line of Sight shader using raycasting

This was a small project where I created a LoS shader with penumbras on shadows and a no shadows some depth into occluders. You can play around with a shadertoy of the setup here.

I made this after playing a lot of barotrauma by FakeFish studios, a 2D game about submarining in space. :) To block vision between rooms, it uses a Line of Sight shader that draws shadows from occluders.

This LoS shading was done by iterating over all colliders, and using a spritebatch to draw a shadow and penumbra texture behind the object. This way of drawing shadows with penumbras made the LoS effect look natural, but also allow looking into a collider some distance, making the sprite of walls themselves visible while hiding geometry behind it. However, because it used rectangle colliders for the shadow rather than the actual sprite of the shape, it would often not be aligned properly and leave gaps in walls that you could see through. It would also behave oddly at times if colliders overlapped (note that this is a particularly bad example and lighting is turned off, it looks better in game):

los example

I wanted to see if it was possible to do this in a shader to make use of the GPU and using the actual sprite shape.

The following design was what I made in the end:

los example

Geometry is first rendered to a separate occlusion map. This was already done as part of the original rendering pipeline. A 1D texture is then filled using a shader that steps from a center viewpoint out in all directions while sampling the occlusion map until it lands on an occluder. The resulting texture then has the distance to occluders in all directions. To calculate the penumbra, a Penumbra map is pre-calculated by determining how deep in the shadow a point at a certain distance from an occluder would be.

This texture is then used to determine how much the shadow from adjacent rays (sampled in the horizontal direction) should add to the shadow from the current ray. By converting the texture back to carthesian coordinates we can draw the final textures on screen.

The resulting shadows looked quite a bit nicer, although it had some noticable jitter over time depending on the limited amount number of rays or ray steps. When profiling using RenderDoc it was found the shader took more time to render, but still only a fraction of the total render time.

los example