This is the documentation for Enlighten.

Low level runtime walkthrough


We recommend to use the high level runtime, unless you need complete control over scheduling and resource handling.

The low level runtime API consists of several components, targets a range of platforms, and is flexible enough to allow a range of configurations. Depending on your requirements, there are many ways in which you could implement the runtime in your game.

This walkthrough presents one simple example of how you could choose to combine the various runtime components using the low level runtime API. It focuses on getting a simple runtime working, and defers issues such as scheduling, alternative albedo and input lighting options, different output formats and optional aspects of the Enlighten SDK.

Workflow

The process of generating textures for Enlighten systems is centred around a loop of API calls that alternately updates the lighting information and drives the 'solver'. All other runtime modules (such as albedo) are there to help drive this loop. The process for generating probe sets is very similar and shares the lighting information. Enlighten systems are discussed first.

The diagram illustrates the following steps, with the associated API calls:

  1. Write input lights to the input lighting buffer: Enlighten::DoWriteInputLightingTask
  2. Add GPU calculated duster values or cached input lighting buffers (optional): Enlighten::AddDusterValuesToInputWorkspace, Enlighten::AddCachedValuesToInputWorkspace
  3. Finalise input lighting buffer by applying bounce, albedo, and emissive and calculate the lighting difference for temporal solves: Enlighten::DoEndInputLightingTask
  4. Solve the radiosity: Enlighten::SolveIrradianceTask

DoWriteInputLightingTask

Enlighten::DoWriteInputLightingTask writes the contributions from conventional lights (Enlighten::InputLight) to the output lighting buffer. Any previous lighting in the buffer is erased. This implies that values added by the Enlighten::Add[Duster/Cached]ValuesToInputWorkspace utility functions will be overwritten if they are made before this call. Therefore, any customised lighting added with these functions should be added after the Enlighten::DoWriteInputLightingTask is executed.

On almost all platforms the task needs a small amount of working memory. The size of this buffer is calculated with Enlighten::CalcRequiredWorkspaceMemory.

Add[Duster/Cached]ValuesToInputWorkspace

This writes any customised input lighting (for example, GPU calculated duster values) to the output lighting buffer.

DoEndInputLightingTask

This step applies the surface albedo to the input lighting, adds the bounced and emissive lighting, and optionally calculates a lighting difference value that is used by the temporal optimisation during radiosity solves.

Temporal optimisation allows the lightmap solve tasks to skip processing when they detect that the lighting has not changed significantly from the last update. However, in order to skip lighting updates, there must be sufficient persistent data to reuse previous lighting and accumulate lighting error.

The TextureAlbedo approach uses a down-sampled albedo texture laid out and generated in Enlighten output UV space. This texture is easy to use and supports general materials, and is typically rendered with the GPU.

SolveIrradianceTask

To configure the temporal solve functionality, Enlighten::SolveIrradianceTask takes as input a temporal threshold value and temporal epsilon value. If the ratio of lighting change and current intensity is greater than the threshold, the output pixel does not need to be solved.

  • The threshold value is in the range [0;1]. A value of 0.01 generally produces good results with no perceptible differences from doing a complete solve (0.01 is the default setting demonstrated in GeoRadiosity). If you pass in a negative threshold value, the temporal optimisation will be disabled.

  • The epsilon value is a suitably small value used to avoid jitter and division by zero. It should be small compared to the range of output values. It can generally be left to its default value of 0.0001.

When using temporal solves the output textures must be persistent because the solve is not guaranteed to write to all the pixels. As mentioned before, the bounce data must also be persistent because it stores the running total of lighting difference values.

Buffer sizes

The size of the bounce data buffer is calculated with Enlighten::CalcRequiredBounceOutputSize and must be cleared to zero when allocated. If temporal solves are desired, the bounce data used with Enlighten::DoEndInputLightingTask must be persistent because the solve tasks store a running total of lighting difference values in that buffer.

When using temporal solves, Enlighten::DoEndInputLightingTask must also be given the lighting buffer for the previous frame in order to calculate difference values. Therefore, the lighting buffers must be double-buffered and persistent. If temporal solves are not needed, the previous lighting buffer pointer can be set to NULL.

Emissive environments and temporal optimisation

When using emissive environments with temporal solves, the SetEnvironmentLightValues function should be called exactly once per solve so that appropriate difference values are correctly computed for the environment. Failure to do so will result in inconsistent results when using animated or changing environments.

FreezeIrradianceTask

There are some update requirements that must be met when using temporal solves. This is due to the fact that the input lighting buffers store the lighting difference from the previous frame only and the radiosity solves rely upon this data to accumulate a difference value when deciding whether to skip a lighting update.

In particular, updating the input lighting for a system requires that you update all the radiosity systems that reference this input lighting before it is updated again. This is necessary because the optimisation relies on all dependent systems of any input lighting buffer having an opportunity to see the values it contains so that the running totals can be properly updated.

Failure to do so will cause some systems to miss lighting differences and therefore update too infrequently, causing potentially severe lighting artefacts. This is exactly the same requirement that is required for the environment (it is helpful to think of the environment as a special input lighting buffer).

However, you don't need to call Enlighten::SolveIrradianceTask on each system - you can instead call Enlighten::FreezeIrradianceTask on some subset of the systems. This function is much faster since it only performs the minimal housekeeping required to keep track of light changes, without computing output values. It is useful in the case where you want different systems to update their lighting at different rates, to ensure that "more important" systems (for instance, those close to the camera) update smoothly, while saving performance.

FreezeInputLightingBuffer

It is also possible to update input lighting buffers less frequently for some systems. If you do this, call Enlighten::FreezeInputLightingBuffer on the lighting buffer in place of computing input lighting. This ensures that any previous lighting changes aren't "double-counted". (This wouldn't cause visual artefacts in the output, but it would cause updated values to be computed more often than necessary, harming performance.)