Hubbry Logo
Shadow mappingShadow mappingMain
Open search
Shadow mapping
Community hub
Shadow mapping
logo
8 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Contribute something
Shadow mapping
Shadow mapping
from Wikipedia
Scene with shadow mapping
Scene with no shadows

Shadow mapping or shadowing projection is a process by which shadows are added to 3D computer graphics. This concept was introduced by Lance Williams in 1978, in a paper entitled "Casting curved shadows on curved surfaces."[1] Since then, it has been used both in pre-rendered and realtime scenes in many console and PC games.

Shadows are created by testing whether a pixel is visible from the light source, by comparing the pixel to a z-buffer[2] or depth image of the light source's view, stored in the form of a texture.

Principle of a shadow and a shadow map

[edit]

If you looked out from a source of light, all the objects you can see would appear in light. Anything behind those objects, however, would be in shadow. This is the basic principle used to create a shadow map. The light's view is rendered, storing the depth of every surface it sees (the shadow map). Next, the regular scene is rendered comparing the depth of every point drawn (as if it were being seen by the light, rather than the eye) to this depth map.

This technique is less accurate than shadow volumes, but the shadow map can be a faster alternative depending on how much fill time is required for either technique in a particular application and therefore may be more suitable to real-time applications. In addition, shadow maps do not require the use of an additional stencil buffer and can be modified to produce shadows with a soft edge. Unlike shadow volumes, however, the accuracy of a shadow map is limited by its resolution.

Algorithm overview

[edit]

Rendering a shadowed scene involves two major drawing steps. The first produces the shadow map itself, and the second applies it to the scene. Depending on the implementation (and the number of lights), this may require two or more drawing passes.

Creating the shadow map

[edit]
Scene rendered from the light view
Scene from the light view, depth map

The first step renders the scene from the light's point of view. For a point light source, the view should be a perspective projection as wide as its desired angle of effect (it will be a sort of square spotlight). For directional light (e.g., that from the Sun), an orthographic projection should be used.

From this rendering, the depth buffer is extracted and saved. Because only the depth information is relevant, it is common to avoid updating the color buffers and disable all lighting and texture calculations for this rendering, to save drawing time. This depth map is often stored as a texture in graphics memory.

This depth map must be updated any time there are changes to either the light or the objects in the scene, but can be reused in other situations, such as those where only the viewing camera moves. (If there are multiple lights, a separate depth map must be used for each light.)

In many implementations, it is practical to render only a subset of the objects in the scene to the shadow map to save some of the time it takes to redraw the map. Also, a depth offset which shifts the objects away from the light may be applied to the shadow map rendering in an attempt to resolve stitching problems where the depth map value is close to the depth of a surface being drawn (i.e., the shadow-casting surface) in the next step. Alternatively, culling front faces and only rendering the back of objects to the shadow map is sometimes used for a similar result.

Shading the scene

[edit]

The second step is to draw the scene from the usual camera viewpoint, applying the shadow map. This process has three major components. The first step is to find the coordinates of the object as seen from the light, as a 3D object only uses 2D coordinates with axis X and Y to represent its geometric shape on screen, these vertex coordinates will match up with the corresponding edges of the shadow parts within the shadow map (depth map) itself. The second step is the depth test which compares the object z values against the z values from the depth map, and finally, once accomplished, the object must be drawn either in shadow or in light.

Light space coordinates

[edit]
Visualization of the depth map projected onto the scene

To test a point against the depth map, its position in the scene coordinates must be transformed into the equivalent position as seen by the light. This is accomplished by a matrix multiplication. The location of the object on the screen is determined by the usual coordinate transformation, but a second set of coordinates must be generated to locate the object in light space.

The matrix used to transform the world coordinates into the light's viewing coordinates is the same as the one used to render the shadow map in the first step (under OpenGL this is the product of the model, view and projection matrices). This will produce a set of homogeneous coordinates that need a perspective division (see 3D projection) to become normalized device coordinates, in which each component (x, y, or z) falls between −1 and 1 (if it is visible from the light view). Many implementations (such as OpenGL and Direct3D) require an additional scale and bias matrix multiplication to map those −1 to 1 values to 0 to 1, which are more usual coordinates for depth map (texture map) lookup. This scaling can be done before the perspective division, and is easily folded into the previous transformation calculation by multiplying that matrix with the following:

If done with a shader, or other graphics hardware extension, this transformation is usually applied at the vertex level, and the generated value is interpolated between other vertices and passed to the fragment level.

Depth map test

[edit]
Depth map test failures

Once the light-space coordinates are found, the x and y values usually correspond to a location in the depth map texture, and the z value corresponds to its associated depth, which can now be tested against the depth map.

If the z value is greater than the value stored in the depth map at the appropriate (x,y) location, the object is considered to be behind an occluding object and should be marked as a failure, to be drawn in shadow by the drawing process. Otherwise, it should be drawn lit.

If the (x,y) location falls outside the depth map, the programmer must either decide that the surface should be lit or shadowed by default (usually lit).

In a shader implementation, this test would be done at the fragment level. Also, care needs to be taken when selecting the type of texture map storage to be used by the hardware: if interpolation cannot be done, the shadow will appear to have a sharp, jagged edge (an effect that can be reduced with greater shadow map resolution).

It is possible to modify the depth map test to produce shadows with a soft edge by using a range of values (based on the proximity to the edge of the shadow) rather than simply pass or fail.

The shadow mapping technique can also be modified to draw a texture onto the lit regions, simulating the effect of a projector. The picture above captioned "visualization of the depth map projected onto the scene" is an example of such a process.

Drawing the scene

[edit]
Final scene, rendered with ambient shadows

Drawing the scene with shadows can be done in several different ways. If programmable shaders are available, the depth map test may be performed by a fragment shader which simply draws the object in shadow or lighted depending on the result, drawing the scene in a single pass (after an initial earlier pass to generate the shadow map).

If shaders are not available, performing the depth map test must usually be implemented by some hardware extension (such as GL_ARB_shadow), which usually does not allow a choice between two lighting models (lit and shadowed), and necessitate more rendering passes:

  1. Render the entire scene in shadow. For the most common lighting models (see Phong reflection model) this should technically be done using only the ambient component of the light, but this is usually adjusted to also include a dim diffuse light to prevent curved surfaces from appearing flat in shadow.
  2. Enable the depth map test and render the scene lit. Areas where the depth map test fails will not be overwritten and will remain shadowed.
  3. An additional pass may be used for each additional light, using additive blending to combine their effect with the lights already drawn. (Each of these passes requires an additional previous pass to generate the associated shadow map.)

The example pictures in this article used the OpenGL extension GL_ARB_shadow_ambient to accomplish the shadow map process in two passes.

Shadow map real-time implementations

[edit]

One of the key disadvantages of real-time shadow mapping is that the size and depth of the shadow map determine the quality of the final shadows. This is usually visible as aliasing or shadow continuity glitches. A simple way to overcome this limitation is to increase the shadow map size, but due to memory, computational or hardware constraints, it is not always possible. Commonly used techniques for real-time shadow mapping have been developed to circumvent this limitation. These include Cascaded Shadow Maps,[3] Trapezoidal Shadow Maps,[4] Light Space Perspective Shadow maps,[5] or Parallel-Split Shadow maps.[6]

Also notable is that generated shadows, even if aliasing free, have hard edges, which is not always desirable. In order to emulate real world soft shadows, several solutions have been developed, either by doing several lookups on the shadow map, generating geometry meant to emulate the soft edge or creating non-standard depth shadow maps. Notable examples of these are Percentage Closer Filtering,[7] Smoothies,[8] and Variance Shadow maps.[9]

Shadow mapping techniques

[edit]

Simple

[edit]
  • SSM "Simple"

Splitting

[edit]

Warping

[edit]

Smoothing

[edit]

Filtering

[edit]

Soft Shadows

[edit]

Assorted

[edit]

Miscellaneous

[edit]
  • Shadow Depth Maps (SDM)[10]
  • Perspective shadow maps (PSMs)
  • Light Space Perspective Shadow Maps (LSPSMs)
  • Cascaded Shadow Maps (CSMs)[11]
  • Variance Shadow Maps (VSMs)[12]

See also

[edit]

Further reading

[edit]

References

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
Shadow mapping is a technique for rendering shadows in three-dimensional scenes, introduced by Lance Williams in 1978 as a method to cast curved shadows onto curved surfaces using depth buffering. The approach involves two primary rendering passes: first, generating a (or shadow map) from the perspective of the light source by rendering the scene and storing the depth values of visible surfaces; second, during the main scene render from the viewer's perspective, transforming fragment coordinates into light space and comparing their depths against the shadow map to determine if they are occluded and thus shadowed. This image-based method offers significant advantages for interactive applications, such as video games and simulations, due to its linear computational cost relative to scene complexity—approximately twice that of standard rendering—and its compatibility with via graphics APIs like and . It supports dynamic shadows for both static and moving objects without requiring additional geometric primitives, making it suitable for large-scale environments. However, shadow mapping is prone to artifacts, including from resolution limitations, self-shadowing "acne" on surfaces due to floating-point precision errors, and perspective where shadow resolution varies unevenly across the scene; these issues are commonly mitigated through techniques like depth bias, percentage-closer filtering, and offsetting. Over time, variants have addressed these limitations to enhance quality and performance. Cascaded shadow maps divide the view frustum into multiple depth ranges, allocating higher resolution to nearer cascades for improved detail in foreground shadows. Variance shadow maps store depth variance in the map to enable soft shadows via statistical sampling, reducing without multiple samples per fragment. Other improvements include adaptive resolution adjustments and integration with modern GPU features for handling complex , such as point lights via cube-mapped shadow maps. These evolutions have made shadow mapping a foundational technique in real-time rendering pipelines, including those in engines like .

Fundamentals

Definition and History

Shadow mapping is a rasterization-based technique used to approximate hard in rendered scenes by generating a , known as the shadow map, from the viewpoint of a light source and then comparing depths during the primary scene rendering to determine shadowed regions. This image-space method leverages depth buffering to efficiently handle occlusions without explicit ray tracing, making it suitable for both static and dynamic scenes. The technique was invented by Lance Williams in 1978, detailed in his seminal paper "Casting Curved Shadows on Curved Surfaces," which introduced the core idea of projecting depth information from a light's perspective to cast shadows onto arbitrary surfaces, including curved ones. Initially, shadow mapping found application in offline rendering for pre-computed animations and , particularly in the 1980s as computational power allowed for more complex scene illumination in . For instance, researchers extended the method in 1987 to handle antialiased shadows using depth maps for area light sources, enabling higher-quality results in ray-traced environments like those in early computer-animated films. Shadow mapping transitioned to real-time rendering in the late 1990s and early , driven by advancements in graphics hardware that supported programmable shaders and depth textures. The GeForce 3 GPU, released in 2001, provided hardware acceleration for shadow maps via 8 and extensions, allowing efficient implementation in interactive applications. This milestone facilitated its adoption in video games, marking one of the earliest uses of real-time shadow mapping for dynamic shadows. By the mid-, integration into standard rendering pipelines in and enabled widespread use for handling multiple dynamic lights in real-time scenarios, evolving from its offline origins to a cornerstone of modern graphics engines.

Principles of Shadows and Shadow Maps

Shadows in optical physics arise from the occlusion of by intervening , preventing direct illumination from reaching certain surfaces. When an opaque object blocks rays from a source to a receiver, it casts a shadow consisting of two distinct regions: the umbra, where the light source is completely obstructed and no direct reaches the surface, and the penumbra, where partial occlusion occurs, allowing some light rays to graze the edges of the occluder and create a transitional zone of reduced intensity. This formation depends on the relative positions of the , occluder, and receiver, with the umbra being the darkest core and the penumbra providing a softer boundary. The nature of shadows—hard or soft—fundamentally stems from the size and distance of the light source relative to the occluder. A point light source, idealized as having zero extent, produces sharp, hard shadows with no penumbra because all rays are either fully blocked or fully transmitted, resulting in binary occlusion. In contrast, extended light sources, such as area lights with non-negligible size comparable to the occluder distance, generate soft shadows featuring prominent penumbrae, as varying portions of the source remain visible around the occluder's edges, blending the transition from full shadow to illumination. Larger source sizes or closer occluder distances amplify the penumbra width, enhancing realism but increasing computational complexity in simulation. In , shadow maps digitally represent these occlusion principles as a 2D texture capturing the minimum depth from the source to visible surfaces within its view , serving as a proxy for determining shadowed regions during rendering. This encodes, for each ( in texture ), the closest distance along rays emanating from the , effectively approximating the umbra and penumbra boundaries by comparing scene depths against stored values. The technique relies on rasterization pipelines that employ to transform world coordinates into the light's view via view-projection matrices, which define the as a perspective volume bounding the illuminated scene. Depth buffering, a core prerequisite in this rasterization process, maintains a per-pixel buffer storing the minimum depth value encountered during scene traversal, resolving visibility by discarding fragments farther from the viewpoint (or , in shadow map generation). ensures accurate mapping by applying homogeneous transformations—combining view matrices (positioning the as camera) and projection matrices (perspective or orthographic)—to clip and normalize coordinates within the , enabling the shadow map to align seamlessly with the 's optical projection. This foundation allows shadow maps to efficiently proxy real-world occlusion without explicit ray tracing of every path.

Core Algorithm

Generating the Shadow Map

The generation of the shadow map constitutes the first pass of the shadow mapping algorithm, where the scene is rendered solely from the perspective of the light source to capture depth information about occluding geometry. This process utilizes the light's viewpoint to determine visible surfaces, storing their distances in a depth texture that serves as the shadow map. Introduced by Williams in 1978, this depth-only rendering leverages Z-buffer techniques to efficiently compute the nearest surface depth for each pixel in the light's view . To initiate the generation, the view matrix for the light is established by positioning a virtual camera at the light source and orienting it along the light's direction, transforming world-space coordinates into light-view space. The is then configured based on the light type: an for directional lights to model parallel rays emanating from an infinite distance, and a perspective projection for spot lights to simulate the conical illuminated by the source with a defined and angle. For point lights, which emit in all directions, a perspective projection is applied across multiple faces of a cubemap to encompass the full 360-degree surroundings, though basic implementations often limit this to simpler cases. The scene is subsequently rendered using these matrices, employing a fragment or render state that discards color output and writes only the depth values to the attached depth buffer. These depths are stored in a 2D texture, typically at a resolution like 1024×1024 pixels, which provides a balance between shadow detail and rendering overhead. During rendering, the depth value zlightz_{\text{light}} for each fragment is derived from the light-space position of the world vertex, computed as
zlight=projectionlightviewlightposworld,z_{\text{light}} = \text{projection}_{\text{light}} \cdot \text{view}_{\text{light}} \cdot \mathbf{pos}_{\text{world}},
and then normalized and clamped to the [0,1] range suitable for texture storage, representing the relative distance from the light to the surface. This value records the minimum depth (closest occluder) per texel via depth testing, ensuring the shadow map encodes only the frontmost geometry visible to the light.
For scenes with multiple light sources, shadow maps are generated sequentially for each active light, producing distinct depth textures that can later be sampled independently during scene rendering. This per-light approach accommodates varying projection types and positions but scales the computational cost with the number of shadow-casting lights, often necessitating optimizations like limiting shadows to key sources in real-time applications.

Rendering the Scene with Shadows

In the rendering pass from the camera's viewpoint, the scene is drawn normally, but with additional computations to incorporate shadows using the previously generated shadow map. For each fragment, its world-space position is transformed into the light's view space by applying the light's view-projection matrix, yielding texture coordinates and a depth value in light space. These coordinates are used to sample the corresponding depth from the shadow map, and the fragment's light-space depth is compared to this sampled value: if the fragment's depth exceeds the sampled depth, the fragment is deemed to be in shadow and receives reduced illumination from that light source. This process effectively projects the shadow map onto the scene geometry to identify shadowed regions. To mitigate self-shadowing artifacts, known as shadow acne, where surfaces incorrectly shadow themselves due to precision limitations in depth comparisons, a offset is applied to the fragment's depth value before the comparison. In the original formulation, this is a small constant subtracted from the transformed depth to push the surface slightly closer to the light, preventing erroneous shadowing while potentially introducing minor edge discrepancies. Modern implementations often employ a -scale depth , which dynamically adjusts the offset based on the surface's relative to the light direction—steeper slopes receive larger biases to better handle grazing angles and reduce acne without excessive detachment of shadows from casters. The result of the depth comparison yields a binary shadow factor (0 for shadowed, 1 for lit), which is multiplied by the light's contribution in the shading equation to attenuate illumination in shadowed areas. For instance, the shadowed light intensity can be computed as min(1,compare(depthmap,depthfragmentbias))\min(1, \text{compare}(\text{depth}_\text{map}, \text{depth}_\text{fragment} - \text{bias})), where the compare function returns 1 if the fragment is visible to the light and 0 otherwise; this factor scales the diffuse, specular, or other terms from that light in the final fragment color. This integration occurs in the fragment shader, allowing shadows to be seamlessly blended with the rest of the model without altering the core rendering significantly.

Implementation Challenges

Coordinate Transformations

In shadow mapping, coordinate transformations are essential to project scene geometry from world space into the light's view for depth comparison during rendering. The process begins by transforming a world-space position pw=(xw,yw,zw,1)T\mathbf{p}_w = (x_w, y_w, z_w, 1)^T into light clip space using the light's view matrix VL\mathbf{V}_L and PL\mathbf{P}_L, resulting in pc=PLVLpw\mathbf{p}_c = \mathbf{P}_L \mathbf{V}_L \mathbf{p}_w. This operation positions the geometry relative to the light source, analogous to the camera's view-projection in standard rendering. The homogeneous clip-space coordinates pc=(xc,yc,zc,wc)T\mathbf{p}_c = (x_c, y_c, z_c, w_c)^T then undergo a perspective divide to obtain normalized device coordinates (NDC): pn=(xc/wc,yc/wc,zc/wc,1)T\mathbf{p}_n = (x_c / w_c, y_c / w_c, z_c / w_c, 1)^T, where the NDC range for x and y is [-1, 1] across major graphics APIs (OpenGL and Direct3D), while the z range depends on the API: [-1, 1] in OpenGL and [0, 1] in Direct3D. To map these to texture coordinates in the [0, 1] range suitable for shadow map sampling, a scale-and-bias operation is applied, which varies by API. In OpenGL, t=0.5pn+0.5\mathbf{t} = 0.5 \cdot \mathbf{p}_n + 0.5, yielding tx=0.5(xc/wc)+0.5t_x = 0.5 \cdot (x_c / w_c) + 0.5, ty=0.5(yc/wc)+0.5t_y = 0.5 \cdot (y_c / w_c) + 0.5, and tz=0.5(zc/wc)+0.5t_z = 0.5 \cdot (z_c / w_c) + 0.5. In Direct3D, the x and y components use tx=0.5(xc/wc)+0.5t_x = 0.5 \cdot (x_c / w_c) + 0.5 and ty=0.5(yc/wc)+0.5t_y = -0.5 \cdot (y_c / w_c) + 0.5 (accounting for the inverted y-axis), while tz=zc/wct_z = z_c / w_c (no scaling, as z_NDC is already [0, 1]). This can be expressed compactly in the OpenGL convention as: t=PLVLpwwc0.5+0.5\mathbf{t} = \frac{\mathbf{P}_L \mathbf{V}_L \mathbf{p}_w}{w_c} \cdot 0.5 + 0.5 The operations are typically implemented using an API-specific bias matrix multiplied by the clip-space position before the divide. For directional lights, which model parallel rays like , an matrix PL\mathbf{P}_L is used instead of perspective, simplifying the transformation since wc=1w_c = 1 for all points, eliminating the perspective divide's nonlinear effects on depth. This results in linear z-depth distribution in NDC, aiding uniform sampling across the shadow map. A crop matrix may further align the light's to the camera's view , defined as: C=(Sx00Ox0Sy0Oy00100001),\mathbf{C} = \begin{pmatrix} S_x & 0 & 0 & O_x \\ 0 & S_y & 0 & O_y \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix},
Add your contribution
Related Hubs
Contribute something
User Avatar
No comments yet.