This is the documentation for Enlighten.

Per pixel probe lighting


Per pixel probe lighting provides the highest possible effective indirect lighting resolution in areas close to the viewer, but automatically reduces the effective resolution in distant areas.

The technique uses a 3D virtual texture, implemented with an indirection clipmap texture which indexes into an atlas of tiles.

This technique provides accurate lighting across very large meshes. The runtime cost increases only with the number of probes updated, regardless of the number of objects that are lit by probes.

To use per pixel probe lighting:

  • The High Level Runtime must be configured to enable the Entire Probe Set Solver. 
  • Automatic probe placement must be used.
  • L1 SH probe output must be used.
  • floating point probe output must be used.

Use the High Level Runtime update manager to update the virtual texture with the results of the radiosity computation. To render indirect lighting, sample the probe SH coefficients from the virtual texture and evaluate SH lighting in the direction of the pixel normal.

Initial setup

At the same point in your pipeline that you process the Enlighten runtime data, call GetOctreePppiAtlasFootprint for each probe set in the world, and then call ComputePppiAtlasMaxima to obtain the maximum runtime memory footprint of per pixel probe lighting data. Use this footprint when you configure the High level runtime.

Use PppiConfiguration to configure the 3D virtual texture. To get started quickly, use the default constructor for reasonable defaults. When constructing the High level runtime update manager, assign your configuration to UpdateManagerProperties::m_PppiConfiguration.

	Enlighten::UpdateManagerProperties properties;
	properties.m_UseEntireProbeSetSolver = true;
	properties.m_PppiConfiguration = Enlighten::PppiConfiguration();

Call GetPppiRequiredOutputTextures with the same configuration to find the size and format of the required output textures. 

	Enlighten::PppiOutputTextureRequirements requirements = GetPppiRequiredOutputTextures(properties.m_PppiConfiguration);

Create rendering resources of the required size and format for each of the output textures.

Compute shader update

If compute shaders are available on the target platform, use the compute shader update path.

Set PppiConfiguration::m_UseComputeUpdate to true.

Configure the output textures to optimise for access only by the GPU.

To obtain the maximum required size of each input buffer for the compute shaders, call GetPppiMaximumClipmapUpdateRequirements and GetPppiMaximumAtlasUpdateRequirements with the result of ComputePppiAtlasMaxima obtained earlier. Create these input buffers at load time.

Implement IPppiComputeUpdateHandler::UpdateClipmap and PppiComputeUpdateHandler::UpdateAtlas to write the input buffers, bind the input buffers and output textures and dispatch the corresponding compute shaders GeoRuntime/Resources/PppiClipmapUpdate.hlsl and GeoRuntime/Resources/PppiAtlasUpdate.hlsl.

One PppiUpdateData object consists of multiple buffers that are to be combined into a single input buffer. In most scenarios we recommend to call PppiUpdateData::CopyCombined to do this, but you can also access the buffers directly if you prefer.

Provide a pointer to your implementation of IPppiComputeUpdateHandler when calling IUpdateManager::Update.

The Sample Runtime application includes a simplified example implementation of the compute shader update path within PppiDx11TextureUpdateHandler.

If your engine implements dynamically updated vertex buffers, you may be able to reuse the implementation for the compute shader input buffers.

We recommend to use shared GPU memory and write directly to the input buffer on the CPU, on platforms which allow this. Your implementation is responsible for ensuring correct synchronisation to prevent writes to a buffer still in use by the GPU.

Regular texture update

If compute shaders are not available on the target platform, use the regular texture update path.

We recommend to use textures in shared GPU memory, with linear untiled layout, on platforms which allow this. Enlighten can write directly to these textures on the CPU. If this option is not available, allocate a separate buffer large enough to hold a copy of the texture data.

Fill UpdateManagerProperties::m_PppiOutputWorkspace with the layout of the output textures in memory.

The update manager does not take ownership of these textures, and the caller must ensure that their lifetime is the same as the update manager. 

If your output textures cannot be written directly on the CPU, when each texture is updated, you must flush the modified part of this buffer to the GPU using your preferred graphics API. Provide an implementation of IPppiTextureUpdateHandler that does this when the output textures are written. 

Each time you call IUpdateManager::AllocateProbeSetIUpdateManager::EnqueueRemoveProbeSet or IUpdateManager::Update, the virtual texture is updated and output texture update notifications are produced. Because many notifications might be generated in a single frame, we recommend to batch multiple updates to minimize the number of graphics API calls required.

class PppiTextureUpdateHandler : public Enlighten::IPppiTextureUpdateHandler
{
	void UpdateAtlas(const Enlighten::VolumeTextureRegion& region)
	{
		// mark the specified regions of the atlas texture to be copied from CPU to GPU
	}

	void UpdateIndirection(const Enlighten::IndirectionTextureRegions& regions)
	{
		// mark the specified regions of the atlas texture to be copied from CPU to GPU
	}
};

Each frame

Before drawing meshes, call IUpdateManager::Update. Specify the view origin to obtain full resolution indirect lighting close to the viewer.

As an optional optimization, limit the LOD distance to trade lighting accuracy for faster updates. 

If you call IUpdateManager::Update for different views on alternating frames, you may incur some unnecessarily expensive indirection texture updates.

If both views are very similar, provide the average view origin to IUpdateManager::Update. If the views are very different, create one instance of the update manager for each view.

Render indirect lighting

To sample the virtual texture in a pixel shader, use the output textures and the shader parameters returned by IUpdateManager::Update with the SamplePppiVirtualTexture function in GeoRuntime/Resources/PppiCommon.cg.

Excerpt: PppiCommon.cg
struct PppiSample
{
	float4 R;
	float4 G;
	float4 B;
	float InverseValidity;
	float EnvironmentVisibility;
};

PppiSample SamplePppiVirtualTexture(float3 worldPosition, float3 viewOrigin)
{
	...
}

The worldPosition argument is the world space position of the pixel. The viewOrigin argument is the position of the camera, used to determine the level of detail.

PppiSample R, G and B contain the L1 SH coefficients for each colour channel. Use this to evaluate SH lighting in the direction of the pixel normal.

In areas close to invalid (culled) probes, the lighting result is darkened. To compensate for this, multiply the lighting result by InverseValidity.

Environment Visibility is not yet implemented for the compute shader update path.