6.3.3 Rendering shadows

Build a vector PiL in world space from a vertex or fragment, to the light or lights, and fetch the cubemap shadow using this vector.

Before fetching each texel, you must apply local correction to the PiL vector. ARM recommends you make the local correction in the fragment shader to obtain more accurate shadows.
To compute the local correction, you must calculate an intersection point of the fragment-to-light vector with the bounding box of the environment. Use this intersection point to build another vector from the cubemap origin position C to the intersection point P. This gives you the final vector CP that you use to fetch the texel.
You require the following input parameters to calculate the local correction:
Compute the output value CP. This is the corrected fragment-to-light vector that you use to fetch a texel from the shadow cubemap.
The following figure shows the local correction of the fragment-to-light vector.
Figure 6-27 Local correction of the fragment-to-light vector

The following example code shows how to calculate the correct CP vector:
// Working in World Coordinate System.
vec3 intersectMaxPointPlanes = (_BBoxMax - Pi) / PiL;
vec3 intersectMinPointPlanes = (_BBoxMin - Pi) / PiL;

// Looking only for intersections in the forward direction of the ray.	
vec3 largestRayParams = max(intersectMaxPointPlanes, intersectMinPointPlanes);

// Smallest value of the ray parameters gives us the intersection.
float dist = min(min(largestRayParams.x, largestRayParams.y), largestRayParams.z);

// Find the position of the intersection point.
vec3 intersectPositionWS = Pi + PiL * dist;

// Get the local corrected vector.
CP = intersectPositionWS - _EnviCubeMapPos;		
Use the CP vector to fetch a texel from the cubemap. The alpha channel of the texel provides information about how much light or shadow you must apply to a fragment:
float shadow = texCUBE(cubemap, CP).a;
The following figure shows a chess room with hard shadows:
Figure 6-28 Chess room with hard shadows

This technique generates working shadows in your scene, but you can improve the quality of the shadows with two more steps:

Back faces in shadow

The cubemap shadow technique does not use depth information to apply shadows. This means that some faces are incorrectly lit when they are meant to be in shadow.

The problem only occurs when a surface is facing in the opposite direction to the light. To fix this problem, check the angle between the normal vector and the fragment-to-light vector, PiL. If the angle, in degrees, is outside of the range -90 to 90, the surface is in shadow.
The following code snippet does this check:
if (dot(PiL,N) < 0)
shadow = 0.0;
The previous code causes each triangle into a hard switch from light to shade. For a smooth transition use the following formula:
shadow *= max(dot(PiL, N), 0.0);
  • shadow is the alpha value fetched from the shadow cubemap.
  • PiL is the normalized fragment-to-light vector in world space.
  • N is the normal vector of the surface in world space.
The following figure shows a chess room with back faces in shadow:
Figure 6-29 Chess room with back faces in shadow


This shadow technique can provide realistic soft shadows in your scene.

  1. Generate mipmaps and set trilinear filtering for the cubemap texture.
  2. Measure the length of a fragment-to-intersection-point vector.
  3. Multiply the length by a coefficient.
The coefficient is a normalizer of a maximum distance in your environment to the number of mipmap levels. You can calculate this automatically against bounding volume and mipmap levels. You must customize the coefficient to your scene. This enables you to tweak the settings to suit the environment, improving visual quality. For example, the coefficient used in the Ice Cave project is 0.08.
You can reuse the results of calculations that you did for the local correction. Reuse dist from the code snippet for local correction as the length of the segment from the fragment position to the intersection point of the fragment-to-light vector with the bounding box:
float texLod = dist;
Multiply texLod by the distance coefficient:
texLod *= distanceCoefficient; 
To implement softness, fetch the correct mipmap level of the texture using the Cg function texCUBElod() or the GLSL function textureLod().
Construct a vec4 where XYZ represents a direction vector and the W component represents the LOD.
CP.w = texLod;
shadow = texCUBElod(cubemap, CP).a;
This technique provides high-quality, smooth shadows for your scene.
The following figure shows a chess room with smooth shadows:
Figure 6-30 Smooth shadows

Non-ConfidentialPDF file icon PDF versionARM 100140_0201_00_en
Copyright © 2014, 2015 ARM. All rights reserved.