PointShadow
Shadows from a point light, in every direction at once. A point light shines everywhere, so no single flat map can hold its shadows. The scene is rendered SIX times, once down each axis, into the six faces of a cube map -- and then sampled by direction rather than by a projected coordinate. Use it with Shader->LitTexturedPointShadowed. ## What it costs Six passes over the casters, against one for a directional or spot light. That is the price of omnidirectional shadows and there is no way around it at this level -- the cheaper techniques (rendering all six faces in one pass with a geometry shader, or dual-paraboloid maps) are a different design rather than an optimisation of this one. So: use a spot where a spot will do. A lamp in a room a viewer only sees one side of does not need six faces. ## Distance, not depth The faces store the DISTANCE from the light in world units, scaled by the light's range -- not the usual projected depth. That is the trick that makes this tractable: the six faces do not share a projection, so a comparison in projected depth would have to know which face a lookup landed on and undo that face's projection. A distance means the same thing on every face. It also disposes of the bias problem spot shadows have. Projected depth is non-linear so a constant bias means different things at different distances; a world-unit distance is linear, so one bias is right everywhere.
Example
shadows := PointShadow->New(512);
shadows->SetLight(lamp);
# once a frame
shadows->Capture(scene);
# then the visible pass
lit->Use();
rig->ApplyTo(lit);
shadows->ApplyTo(lit);Operations
- New
- ApplyTo
- BeginFace
- BindCubeTo
- BuildFace
- Capture
- DrawCaster
- DrawCasterMatrix
- End
- Free
- GetError
- IsOk
- SetBias
- SetLight
- SetLightAt
- SetTextureUnit
ApplyTo #
Bind the cube and tell a shader how to read it. Writes "point_light_position", "point_light_range", "point_bias" and "shadow_cube", skipping any the shader does not declare.
method : public : ApplyTo(shader:Shader) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| shader | Shader | the program about to draw |
BeginFace #
Start drawing into ONE face. Faces run 0..5 in GL's order: +X, -X, +Y, -Y, +Z, -Z. Everything drawn until the next BeginFace or End goes into that face. Call Capture instead unless the casters are not in a Scene.
method : public : BeginFace(face:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| face | Int | 0..5 |
BindCubeTo #
Bind the cube for sampling on a chosen unit, without touching a shader. For inspecting what the six faces hold -- which is the only way to tell a rotated face from a correct one, since a symmetric scene looks the same either way on screen.
method : public : BindCubeTo(unit:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| unit | Int | the texture unit |
BuildFace #
The six views from the light, one per face. The up vectors are not a free choice: GL specifies how each cube face is oriented, and a face rendered with a different up is stored rotated. The lookup would then read the right face and the wrong part of it -- shadows that are present, plausible, and in the wrong place on two faces out of six.
method : private : BuildFace() ~ NilParameters
| Name | Type | Description |
|---|---|---|
Capture #
Render a whole Scene into all six faces, then go back to the window. The one call that does the entire depth stage.
method : public : Capture(scene:Scene, window:GLWindow) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| scene | Scene | the casters |
| window | GLWindow | the window to return to |
DrawCaster #
Draw one caster into the face that BeginFace selected.
method : public : DrawCaster(mesh:Mesh, transform:Transform) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| mesh | Mesh | its geometry |
| transform | Transform | where it is |
DrawCasterMatrix #
Draw a 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 #
Finish the depth stage 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 cube map and its shader. Call it while the GL context is still current -- see the shutdown order on the GL class.
method : public : Free() ~ NilGetError #
method : public : GetError() ~ StringReturn
| Type | Description |
|---|---|
| String | why it could not be set up, or an empty string |
IsOk #
method : public : IsOk() ~ BoolReturn
| Type | Description |
|---|---|
| Bool | true when the cube map and its shader are both usable |
New # constructor
A cube shadow map.
New(size:Int)Parameters
| Name | Type | Description |
|---|---|---|
| size | Int | the width and height of EACH of the six faces; 512 is a reasonable start, and remember there are six of them |
SetBias #
The depth margin, in WORLD UNITS -- unlike ShadowMap's, which is in projected depth. Default 0.06.
method : public : SetBias(bias:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| bias | Float | the margin |
SetLight #
Take the position and range from a point light. Worth doing rather than setting them twice: a cube rendered from somewhere other than the light puts every shadow in the wrong place, and the receiving shader matches the cube to a light BY POSITION, so a mismatch means the shadow is silently applied to no light at all.
method : public : SetLight(light:Light) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| light | Light | a point light |