2.15
Lighting: Projective

- 2.15.1. Overview
- 2.15.2. Algorithm
- 2.15.3. Back projection
- 2.15.4. Clamping
- 2.15.5. Attenuation
- 2.15.6. Application

2.15.1
Overview
1
A projective light in the r2 package is a light that projects a texture onto the scene from a given origin specified in eye space up to a given maximum radius. Projective lights are the only types of lights in the r2 package that are able to project shadows.
2.15.1.2 Projective lighting
2.15.1.3 Projective lighting (Texture)
2.15.2
Algorithm
1
At a basic level, a projective light performs the same operations that occur when an ordinary 3D position is projected onto the screen during rendering. During normal rendering, a point p given in world space is transformed to eye space given the current camera's view matrix, and is then transformed to clip space using the current camera's projection matrix. During rendering of a scene lit by a projective light, a given point q in the scene is transformed back to world space given the current camera's inverse view matrix, and is then transformed to eye space from the point of view of the light (subsequently referred to as light eye space) using the light's view matrix. Finally, q is transformed to clip space from the point of view of the light (subsequently referred to as light clip space) using the light's projection matrix. It should be noted (in order to indicate that there is nothing unusual about the light's view or projection matrices) that if the camera and light have the same position, orientation, scale, and projection, then the resulting transformed values of q and p are identical. The resulting transformed value of q is mapped from the range [(-1, -1, -1), (1, 1, 1)] to [(0, 0, 0), (1, 1, 1)], and the resulting coordinates are used to retrieve a texel from the 2D texture associated with the light.
2
Intuitively, an ordinary perspective projection will cause the light to appear to take the shape of a frustum:
2.15.2.3 Projective lighting (Frustum)
4
There are two issues with the projective lighting algorithm that also have to be solved: back projection and clamping.
2.15.3
Back projection
1
The algorithm described above will produce a so-called dual or back projection. In other words, the texture will be projected along the view direction of the camera, but will also be projected along the negative view direction [17]. The visual result is that it appears that there are two projective lights in the scene, oriented in opposite directions. As mentioned previously, given the typical projection matrix, the w component of a given clip space position is the negation of the eye space z component. Because it is assumed that the observer is looking towards the negative z direction, all positions that are in front of the observer must have positive w components. Therefore, if w is negative, then the position is behind the observer. The standard fix for this problem is to check to see if the w component of the light-clip space coordinate is negative, and simply return a pure black color (indicating no light contribution) rather than sampling from the projected texture.
2.15.4
Clamping
1
The algorithm described above takes an arbitrary point in the scene and projects it from the point of view of the light. There is no guarantee that the point actually falls within the light's view frustum (although this is mitigated slightly by the r2 package's use of light volumes for deferred rendering), and therefore the calculated texture coordinates used to sample from the projected texture are not guaranteed to be in the range [(0, 0), (1, 1)]. In order to get the intended visual effect, the texture used must be set to clamp-to-edge and have black pixels on all of the edges of the texture image, or clamp-to-border with a black border color. Failing to do this can result in strange visual anomalies, as the texture will be unexpectedly repeated or smeared across the area outside of the intersection between the light volume and the receiving surface:
2.15.4.2 Projective lighting (Correct, clamped)
2.15.4.3 Projective lighting (Incorrect, not clamped)
4
The r2 package will raise an exception if a non-clamped texture is assigned to a projective light.
2.15.5
Attenuation
1
The light supports attenuation using the maximum range taken from the projection.
2.15.6
Application
1
The final light applied to the surface is given by projective in Projective.hs, where sr, sg, sb are the red, green, and blue channels, respectively, of the surface being lit. The surface-to-light vector stl is calculated by normalizing the negation of the difference between the the current eye space surface_position and the eye space origin of the light.
2.15.6.2 Projective lighting (Application)

    module Projective where

    import qualified Attenuation
    import qualified Color3
    import qualified Color4
    import qualified Direction
    import qualified LightDiffuse
    import qualified LightSpecular
    import qualified LightProjective
    import qualified Normal
    import qualified Position3
    import qualified Specular
    import qualified Spaces
    import qualified Vector3f
    import qualified Vector4f

    projective :: Direction.T Spaces.Eye -> Normal.T -> Position3.T Spaces.Eye -> LightProjective.T -> Specular.T -> Float -> Color3.T -> Color4.T -> Vector3f.T
    projective view n surface_position light specular shadow texture (Vector4f.V4 sr sg sb _) =
      let
        position_diff   = Position3.sub3 surface_position (LightProjective.origin light)
        stl             = Vector3f.normalize (Vector3f.negation position_diff)
        distance        = Vector3f.magnitude (position_diff)
        attenuation_raw = Attenuation.attenuation (LightProjective.radius light) (LightProjective.falloff light) distance
        attenuation     = attenuation_raw * shadow
        light_color     = Vector3f.mult3 (LightProjective.color light) texture
        light_intensity = LightProjective.intensity light
        light_d         = LightDiffuse.diffuse stl n light_color light_intensity
        light_s         = LightSpecular.specularBlinnPhong stl view n light_color light_intensity specular
        light_da        = Vector3f.scale light_d attenuation
        light_sa        = Vector3f.scale light_s attenuation
        lit_d           = Vector3f.mult3 (Vector3f.V3 sr sg sb) light_da
        lit_s           = Vector3f.add3 lit_d light_sa
      in
        lit_s

3
The given shadow factor is a value in the range [0, 1], where 0 indicates that the lit point is fully in shadow for the current light, and 1 indicates that the lit point is not in shadow. This is calculated for variance shadows and is assumed to be 1 for lights without shadows. As can be seen, a value of 0 has the effect of fully attenuating the light.
4
The color denoted by texture is assumed to have been sampled from the projected texture. Assuming the eye space position being shaded p, the matrix to get from eye space to light-clip space is given by The final light applied to the surface is given by projective_matrix in ProjectiveMatrix.hs:
2.15.6.5 Projective matrix

    module ProjectiveMatrix where

    import qualified Matrix4f

    projective_matrix :: Matrix4f.T -> Matrix4f.T -> Matrix4f.T -> Matrix4f.T
    projective_matrix camera_view light_view light_projection =
      case Matrix4f.inverse camera_view of
        Just cv -> Matrix4f.mult (Matrix4f.mult light_projection light_view) cv
        Nothing -> undefined -- A view matrix is always invertible

[17]
The same issue occurs when performing ordinary rendering of points in a scene. The issue is solved there by clipping primitives based on their w component so that primitives that are "behind" the observer are not rendered.