ShadowMap
Shadows, from one directional light. The technique is standard and the fiddly parts are all here rather than in each program: render the scene from the light's point of view into a depth texture, then when drawing normally, ask that texture whether each fragment is further from the light than whatever the light could see. If it is, something is in the way. Use it with Shader->LitShadowed or LitTexturedShadowed, which declare the uniforms ApplyTo writes. ## What Cover is for, and why it is not automatic A directional light has no position, so its view of the scene has no natural extent -- something has to say how much world the map covers. Too little and shadows stop at an invisible line; too much and the map's resolution is spread so thin that shadow edges turn to staircases. There is no good default, because the right answer is the size of the thing being lit, so it is a parameter rather than a guess. ## Bias, and why there is any A depth map is a grid of samples, so a surface's own recorded depth is almost never exactly its depth when looked up again. Without a margin, half of every lit surface reads as being behind itself and the result is a fine dark stipple over everything -- "shadow acne". The bias is that margin. Too small brings the acne back; too large and shadows detach from what casts them, which is why the default is small and adjustable rather than generous.
Example
shadows := ShadowMap->New(1024);
shadows->SetLight(light);
shadows->Cover(Vector3->New(0.0, 0.0, 0.0), 14.0);
# per frame, before drawing anything else
shadows->Begin();
shadows->DrawCaster(floor_mesh, floor);
shadows->DrawCaster(ball_mesh, ball);
shadows->End(window);
# then the normal pass
shadowed->Use();
shadows->ApplyTo(shadowed);
# ... draw as usual ...Operations
- New
- ApplyTo
- Begin
- Capture
- Cover
- DrawCaster
- DrawCasterMatrix
- End
- Free
- GetError
- GetLightSpaceMatrix
- GetSpotOuterRadians
- GetTexture
- IsOk
- IsSpot
- RebuildSpot
- SetBias
- SetDirection
- SetLight
- SetSpotLight
- SetTextureUnit
ApplyTo #
Bind the map and tell a shader how to read it. Writes "light_space", "shadow_map", "shadow_bias" and "shadow_texel" -- skipping any the shader does not declare, so this can be handed an unshadowed program without filing a diagnostic.
method : public : ApplyTo(shader:Shader) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| shader | Shader | the program about to draw |
Begin #
Start the depth pass: draw into the map, from the light. Clears it, points the viewport at it, and makes the depth shader current. Everything drawn until End goes into the map. Culling is left exactly as the caller set it. Reversing it for this pass -- recording each object's far side rather than its near side -- is a known way to reduce self-shadowing without a large bias, but it only works for closed solids: a flat floor has no far side and would stop casting altogether. Since a floor is usually the first thing anyone wants to cast onto AND from, the bias is the safer lever, and SetBias is there for it.
method : public : Begin() ~ NilCapture #
Draw every box in a Scene into the map.
method : public : Capture(scene:Scene) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| scene | Scene | the scene whose boxes cast shadows |
Cover #
What area of the world the map covers.
method : public : Cover(center:Vector3, extent:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| center | Vector3 | the middle of the shadowed area |
| extent | Float | half its width, in world units |
DrawCaster #
Draw one caster into the map.
method : public : DrawCaster(mesh:Mesh, transform:Transform) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| mesh | Mesh | its geometry |
| transform | Transform | where it is |
DrawCasterMatrix #
Draw one caster with a model matrix rather than a Transform.
method : public : DrawCasterMatrix(mesh:Mesh, model:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| mesh | Mesh | its geometry |
| model | Float | its model matrix |
End #
End the depth pass and go back to drawing at the window.
method : public : End(window:GLWindow) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| window | GLWindow | the window to return to |
Free #
Release the depth target and the depth shader. Call it while the GL context is still current -- see the shutdown order on the GL class.
method : public : Free() ~ NilGetError #
Why it could not be set up.
method : public : GetError() ~ StringReturn
| Type | Description |
|---|---|
| String | the reason, or an empty string |
GetLightSpaceMatrix #
The light's projection times its view -- what a caster's position has to be multiplied by to find where it lands in the map.
method : public : GetLightSpaceMatrix() ~ Float[]Return
| Type | Description |
|---|---|
| Float | the matrix, 16 floats column-major |
GetSpotOuterRadians #
The spot's outer half-angle in radians, recovered from the cosine it is stored as.
method : private : GetSpotOuterRadians() ~ FloatGetTexture #
The depth map as a texture, for inspecting what is in it.
method : public : GetTexture() ~ Texture2DReturn
| Type | Description |
|---|---|
| Texture2D | the depth texture, or Nil when the map is not usable |
IsOk #
method : public : IsOk() ~ BoolReturn
| Type | Description |
|---|---|
| Bool | true when the depth target and its shader are both usable |
IsSpot #
method : public : IsSpot() ~ BoolReturn
| Type | Description |
|---|---|
| Bool | true when casting from a spot rather than a directional light |
New # constructor
A square shadow map.
New(size:Int)Parameters
| Name | Type | Description |
|---|---|---|
| size | Int | the map's width and height in pixels; 1024 is a reasonable start, 2048 for sharper edges over a large area |
RebuildSpot #
The light-space matrix for a spot: a perspective projection from where the spot is, along where it points, as wide as its outer cone.
method : private : RebuildSpot() ~ NilSetBias #
The depth margin that keeps a lit surface from shadowing itself. Default 0.0025.
method : public : SetBias(bias:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| bias | Float | the margin in light-space depth |
SetDirection #
method : public : SetDirection(direction:Vector3) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| direction | Vector3 | toward the light, matching Light's convention |
SetLight #
Take the direction from a Light, so the shadows and the shading agree. Worth doing rather than setting the direction twice: a shadow map pointing somewhere other than the light produces shadows on the wrong side of everything, which looks like a bug in the shadows and is really two numbers that were meant to be one.
method : public : SetLight(light:Light) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| light | Light | the light casting them |
SetSpotLight #
Cast from a spot light, using a perspective projection matched to its cone. The map covers exactly what the spot lights, so its resolution goes where the light goes -- which is why a spot shadow can look sharp at a size where a directional one covering the same scene would be blocky. Cover is ignored in this mode: the spot's own range and cone say what it reaches.
method : public : SetSpotLight(light:Light) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| light | Light | a spot light; anything else is treated as directional |