The static lightmap generation system has been redesigned and reengineered
from the ground up to accommodate the growing need for better lighting
in Ritual’s games. These changes have required code changes to the
map compiling utility (q3map.exe), the map editor (q3radiant.exe), and
the core game engine itself. This document is meant to serve three purposes:
first, to extend the TDD (Technical Design Doc) as it relates to the specific
features of the lighting system; second, as a tutorial for level designers
and lighting specialists; third, as a canonical reference resource for
future lighters, engineers, and programmers alike.
In designing each aspect of the new lighting system, input was gathered
from a number of contributors both inside Ritual and out. The resulting
design was a consolidation of all of these inputs and, for the most part,
accommodates the desires of each (at least in spirit if not in specific
implementation details). Efforts have been made to create the system in
such a way that it is flexible enough to encompass different methods and
techniques into a generalized system that can service the needs of everyone
The most obvious philosophical change in lighting paradigms is that map
lighters now have a much greater amount of control over various aspects
of the light. A light’s brightness is now independent of its falloff
geometry, which in turn is broken up into several individually controllable
aspects. Each of these aspects will be covered in detail throughout the
course of this document.
Another important philosophical design decision of the new lighting system
can be summarized by the phrase: “realism is good; total control
is better”. Lighting engineers now have the ability to (reasonably)
reproduce realistic behavior in their lights; however, they also have
the ability to explicitly change any aspect of a light such that it will
behave in a less realistic but more desirable fashion.
The following is a step-by-step process of how lighting works in the
Step 1: Placement of light entities in Radiant
The lighting engineer uses the map editor to create, copy, and modify
light sources in a map. Lights are previewed visually in the editor, both
in the orthogonal (2d) and perspective (3d) views. OpenGL is used to render
spotlights as cones, point-lights as spheres, and so on. The lighting
engineer tweaks each of the lighting keys (discussed later) until (s)he
gets the desired light shape and properties. The map is then saved.
Step 2: Compilation of static lightmap data using q3map
The lighting engineer then generates the static lightmap data for the
map by running the map compiler on the level, using the command:
Or by running each stage of q3map as a separate task:
At this stage, the q3map tool calculates the lightmap data for the entire
level by applying the following steps:
- For each surface in the world, a sample point is generated for every
16 world units, forming a coarse grid overlaying the entire surface
area of the world.
- For each sample point, every light source in the map is checked to
see if it contributes any light to that sample point. If the sample
point is outside a point-light’s (or spotlight’s) maximum
radius (called “falloff_end_dist”), that light is not considered
for that sample point.
- For point-lights, the amount of light added to the lightmap at each
sample point is calculated using the following steps:
- a. If the sample point is inside the inner radius of the light
(called “falloff_start_dist”), the light is applied
to the sample point at the light’s maximum brightness.
- b. If the sample point is outside the inner radius of the light,
the amount of light is somewhere between 0 and maximum brightness;
the exact value depends on the distance the sample point is away
from the inner radius as well as the nature of the falloff curvature
(“falloff_curvature”), which can be anywhere from a
straight-linear drop-off to a steep, inverse-squared drop-off.
- For spotlights, the amount of light added to the lightmap at each
sample point is calculated using the following steps:
- a. If the sample point is inside the area of the inner cone (“angle_hotspot”),
it is lit normally as if the light were a normal point light.
- b. If the sample point is outside the inner cone but still within
the outer cone (“angle_penumbra”), it is partially lit
in proportion to its closeness to the inner cone.
- c. If the sample point lies outside the outer cone, it is not
lit by the spotlight at all (unless the “spherical_ambient”
key is nonzero for that light).
- For sunlight, the amount of light added to the lightmap at each sample
point is calculated using the following steps:
- a. A ray is traced from the sample point backward along the direction
of the sunlight vector. If the ray collides with a sky brush or
sky portal before any other solid object, that sample point is fully
lit by the sunlight source.
Step 3: The map is rendered by the engine using textures and lightmap
When drawing a surface of the world, the engine generally makes (at least)
two rendering passes. The first pass is the opaque application of the
surface’s texture. The second pass is the application of the lightmap
data that was precalculated during the q3map –light stage. The lighting
pass is done using the lightmap’s RGB value as a color filter against
pixels in the texture at each pixel on the surface; each of the lightmap’s
RGB components is multiplied by the pixel’s matching component to
produce the net resulting color component. Thus, a lightmap of (1.0, 0.5,
0.0) on a medium-grey texel (0.6, 0.6, 0.6) will produce a resulting pixel
value of (0.6, 0.3, 0.0).
There are several different types of light sources available to lighting
engineers. All types are represented by a single “light” entity;
however, some types require additional objects in order to be properly
Type 1: Point Lights
A point light is the most common type of light. It is the simplest to
define, the easiest to visualize in the editor, and the fastest to calculate
during the q3map –light phase of map precompilation.
Point lights emit light outward spherically from their origin, affecting
all surfaces within their outermost radius (or “falloff_end_dist”,
discussed below). They cast static shadows onto the lightmap when a line
of sight ray between the point light source and the surface sample point
is obstructed by another object.
Type 2: Spotlights
A spotlight is the next most common type of light. It bears all the properties
of a point light; however, a spotlight confines its area of affect into
a cone along the direction of the spotlight. This direction is determined
by associating the light with a target – usually an info_null –
that has a “targetname” key matching the light’s own
“target” key. In order to turn a point light into a spotlight,
simply associate it to a target entity in this manner.
Type 3: Sunlights
Sunlights are essentially point lights that are infinitely far away. They
are constructed in a manner similar to a spotlight (i.e. using an info_null
as a target entity). To make a spotlight into a sunlight, simply check
the box in the Light Properties dialog labeled “Directional (parallel)
Once a light becomes a sunlight, its actual position becomes completely
meaningless. The light now simply serves as a prototype for defining a
light source infinitely far away (such as the sun or moon). All sky brushes
and sky portals in the level will now act as parallel emitters for this
sunlight; in other words, light of the type specified will be cast in
parallel lines along the vector specified by the light’s target
entity from every point on the surface of every sky brush or sky portal.
In the map editor, a light source is represented as an entity. Each
entity has a number of key-value pairs (or epairs) which store the various
properties of that light. The following is a description of each of the
newly-added epair keys:
Called Overall Brightness in the Light Properties dialog, this determines
the maximum brightness multiplier for the light at its brightest point
(i.e. its origin). If “brightness” is 1.0, the light makes
any surface inside its inner radius appear fullbright (assuming the light
is pure white). If “brightness” is 0.5, the light’s
brightest spot is only 50% as bright as a normal light at any given point.
Note, however, that its geometry – including inner and outer radius,
falloff curvature, etc. – will be completely unchanged.
Note also that if “brightness” is negative, the light is
subtractive rather than additive; i.e. it actually removes light from
surrounding surfaces rather than adding light to them.
The “brightness” key may hold any value between –1.0
Called Dot Product Effect in the Light Properties dialog, this determines
the degree to which the dot product (angle of incidence) will affect a
light’s effect on a surface. When “dot_product_weight”
is 0.0, the angle of the surface being lit is not taken into account at
all (other than the fact that surfaces facing completely away from the
light are never lit). When “dot_product_weight” is 1.0, the
amount of light received on a given surface is proportional to how perpendicular
/ straight-on that surface is to the source of the light. A value of 0.0
produces unrealistic (but easy-to-control) quake-like lighting; a value
of 1.0 produces realistic lighting with more complex subtleties. In general,
a value somewhere in-between these two extremes is desirable. The default
value is 0.5.
The color of the light emitted by a light source. This key has a 3-float
vector for its value, such as “1.0 0.5 0.0” (an orange color).
The “_color” key is set by choosing a color in the entity
color-picker (default “K” in Radiant), although it may also
be edited by hand. Clicking on the color sample in the Light Properties
dialog also opens the color picker for all currently selected objects.
Note that lights may never have a color that is not component-saturated;
in other words, all light colors must have at least one component (either
Red, Green, or Blue) that is set to 1.0. Non-saturated colors are automatically
saturated by Radiant (if set through the color picker) and by q3map at
Called Falloff Curvature in the Light Properties dialog, this determines
the steepness of the falloff in intensity as it decreases from maximum
at the inner radius (“falloff_start_dist”) to the zero at
the outer radius (“falloff_end_dist”). A “falloff_curvature”
of 0.0 represents a gentle linear falloff; a value of 1.0 represents a
steep, rapidly-decreasing (inverse-squared) falloff. As with “dot_product_weight”,
“falloff_curvature” is easiest to control (and least realistic)
at 0.0 and is most realistic (but more subtly complex to control) at 1.0.
The default value is 0.5.
Called Falloff Start Distance in the Light Properties dialog. This is
the inner radius; that is, the distance – in world units –
away from the light source within which any surface is fully lit by the
light. Note that although this means “fullbright” in some
cases, other factors are considered as well: the color of the light, the
dot product of the light’s angle hitting the surface (if enabled),
and so on. The “falloff_start_dist” value of a currently selected
light is represented graphically in both 2d and 3d views in Radiant as
translucent spheres of the (normalized) color of the light. (Note: make
sure “Show actual colors in XY” is checked ON in Edit->Preferences.)
For spotlights, this represents the height of the inner cone, as is consistent
with spherical lights (considering the cone as a segment of a sphere).
The hotkeys to quickly adjust “falloff_start_dist” for all
selected lights is (by default) : ALT + CTRL + [ to decrease, ALT + CTRL
+ ] to increase.
Called Falloff End Distance in the Light Properties dialog. This is the
outer radius; that is, the distance – in world units – away
from the light source outside of which any surface is totally unaffected
by the light. The “falloff_end_dist” value of a currently
selected light is represented graphically in both 2d and 3d views in Radiant
as translucent spheres of the (normalized) color of the light. (Note:
make sure “Show actual colors in XY” is checked ON in Edit->Preferences.)
For spotlights, this represents the height of the outer cone, as is consistent
with spherical lights (considering the cone as a segment of a sphere).
The hotkeys to quickly adjust “falloff_end_dist” for all
selected lights is (by default) : ALT + SHIFT + [ to decrease, ALT + SHIFT
+ ] to increase.
“angle_hotspot” (spotlights only)
Called Hotspot Angle in the Light Properties dialog, this field is used
only by spotlights. The “angle_hotspot” value of the light
determines the total angle, in degrees, of the inner cone of the spotlight.
Any point within this inner cone is lit as though the light source were
a normal spherical point light. Likewise, any point within this inner
cone AND within the inner radius (as determined by “falloff_start_dist”)
is fully lit by the light.
The hotkeys to quickly adjust “angle_hotspot” for all selected
lights is (by default) : ALT + CTRL + < to decrease, ALT + CTRL + >
“angle_penumbra” (spotlights only)
Called Penumbra Angle in the Light Properties dialog, this field is used
only by spotlights. The “angle_penumbra” value of the light
determines the total angle, in degrees, of the outer cone of the spotlight.
Any point outside this outer cone is completely unlit / unaffected by
the spotlight (unless “spherical_ambient” is nonzero –
see below). Points that fall outside the inner cone (“angle_hotspot”)
but inside the outer cone (“angle_penumbra”) are partially
lit by the spotlight, with a lateral linear falloff from normal brightness
(at the edge of the inner cone) to total darkness (at the edge of the
The hotkeys to quickly adjust “angle_penumbra” for all selected
lights is (by default) : ALT + SHIFT + < to decrease, ALT + SHIFT +
> to increase.
“spherical_ambient” (spotlights only)
Called Spherical Ambient in the Light Properties dialog, this field is
used only by spotlights. The “spherical_ambient” value of
the light determines the fractional amount of the spotlight’s intensity
that is conveyed outside the outer (“angle_penumbra”) cone.
A value of 0.0 (default) indicates that the spotlight emits no light outside
its outer cone. A value of 0.5 indicates that surfaces outside the spotlight’s
cone are lit as if by a normal, spherical point light of half the intensity
of the spotlight (but with the same basic geometry, in terms of “falloff_start_dist”,
“falloff_end_dist”, and “falloff_curvature”).
A value of 1.0 (maximum) makes the spotlight behave exactly like a normal
spherical point light, since points outside the cone are lit with an equal
amount of intensity as the points inside the cone.
- Sunlights ignore the following keys: falloff_curvature, falloff_start_dist,
falloff_end_dist, hotspot_angle, hotspot_penumbra, spherical_ambient
- Point lights ignore the following keys: hotspot_angle, hotspot_penumbra,
- falloff_start_dist can never be greater than falloff_end_dist; similarly,
falloff_end_dist can never be less than falloff_start_dist. (Note that
the distinction between these two statements refers to two different
types of clamps, depending on which value is being changed to exceed
the other.) The Light Properties dialog will automatically stretch the
other field to accommodate the one being moved in excess of these limits.
- Likewise, angle_hotspot can never be greater than angle_penumbra,
and vice-versa. Also enforced by the Light Properties dialog.
- The keys light, style, falloff, minlight, radius, angles, spot_angle,
spot_dir, and other legacy quake-style lighting epair keys are now obsolete
and are completely ignored by the compiler.
- Sunlights automatically enforce a dot_product_weight of 1.0.