In this article I will describe a simple approximate method how we can cull objects that are casting shadows.

Culling of objects that do not cast shadows is trivial. We just check if *object AABB* is inside or intersects camera frustum planes.

## 1. The problem

However, shadow casters have also their shadow volume, which goes outside the object AABB.

Culling only against *object AABB* will lead into *object shadow* popping into view. To solve this problem, we need to merge *object shadow* into the *object AABB*.

Calculating real shadow bounding volume is problematic and cannot be solved trivially, as shadow can be cast on different objects or terrain that is not simple plane, but has bumps, valleys etc. However, we can approximate the shadow bounding volume by using only **light direction**, **object AABB **and a **ground collider**.

## 2. Constructing light rays

At the first step, we will construct light rays. Their origins will be at the 4 top corners of the *object AABB* and direction will be the same as the sun light direction.

1 2 3 4 |
Ray p0 = new Ray (new Vector3 (aabb.min.x, aabb.max.y, aabb.min.z), lightDirection); Ray p1 = new Ray (new Vector3 (aabb.min.x, aabb.max.y, aabb.max.z), lightDirection); Ray p2 = new Ray (new Vector3 (aabb.max.x, aabb.max.y, aabb.min.z), lightDirection); Ray p3 = new Ray (aabb.max, lightDirection); |

After we have these 4 rays defined, we collide them with the terrain, or simply the bottom plane of the *object AABB*. After that we have 4 intersection points.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Plane groundPlane = new Plane (Vector3.one, aabb.min); Vector3 int0, int1, int2, int3; float dist = 0.0f; if (groundPlane.Raycast (p0, out dist)) int0 = p0.GetPoint (dist); if (groundPlane.Raycast (p1, out dist)) int1 = p0.GetPoint (dist); if (groundPlane.Raycast (p2, out dist)) int2 = p0.GetPoint (dist); if (groundPlane.Raycast (p3, out dist)) int3 = p0.GetPoint (dist); |

In this example, for simplicity, *ground* is defined as simple plane at the bottom of the object. However, we can improve the algorithm by *Raycasting* to the terrain collider.

## 3. Constructing combined bounding volume

Combined bounding volume is defined by merging *object AABB* and the *4 light ray* *intersection points*.

1 2 3 4 5 |
Bounds combinedBounds = objectBounds; combinedBounds.Encapsulate (int1); combinedBounds.Encapsulate (int2); combinedBounds.Encapsulate (int3); combinedBounds.Encapsulate (int4); |

After *combinedBounds* are computed, we can frustum cull against these Bounds using:

1 2 3 4 5 6 7 8 |
if( GeometryUtility.TestPlanesAABB(frustumPlanes, combinedBounds) ) { Debug.Log(object.name + " is visible!"); } else { Debug.Log(object.name + " is not visible"); } |

## Improvements

This solution works only for single directional light. However, we can compute combined bounding volume for each shadow casting light on a single object, and then Encapsulate them all.