This is the documentation for Enlighten.

Terrain LOD

Overview


Terrain Mountains from Unreal Engine 4.8 Samples

Enlighten may be used in games with large open world levels, in which the player can freely move around the world all the way to the horizon. A common feature of this type of scene is heightfield terrain. As of 3.03, Enlighten supports variable Level of Detail for terrain that enables efficient real time Enlighten updates across the entire world.

Terrain Input

Enlighten requires the terrain geometry be provided as a collection of square patches arranged in a regular grid. A single patch in a typical scene is highlighted below. Large terrain will consist of hundreds of patches.


Terrain Mountains from Unreal Engine 4.8 Samples

Each terrain patch is provided to Enlighten as a single terrain input geometry. Terrain patches typically have similar world space dimensions and may consists of multiple meshes.

The many patches that make up a large terrain must be grouped into a set of Enlighten systems. One or more terrain patches can be assigned to each Enlighten terrain system.

Terrain Output

Terrain patches have a natural UV parameterisation, defined by projection onto the horizontal plane. This is the parameterisation that Enlighten will use to produce lightmap UVs.

For each terrain system in the input scene, the Enlighten precompute pipeline will produce a set of RadSystemCore objects (and corresponding Radiosity Normal Textures). Each RadSystemCore object represents a different level of detail in the output Lightmap, with the resolution roughly halving at each successive level of detail. Reduced resolution means fewer Enlighten pixels to solve (faster run-time) and less data to store in memory (form-factors per pixel, etc).

By default, the precompute will add a half-pixel of padding to terrain instance UVs. This padding produces a different Enlighten UV transform for each LOD. The Enlighten UVs in the PackedGeometry can be re-used for all of the LODs by using these UV transforms that contain a simple scale and translation. Since the Enlighten clusters are the same for all the terrain LODs there is no need to change any code dealing with per-cluster buffers (e.g. input lighting, bounce, etc).

Details

Here follows a detailed description of how terrain system LOD data is generated and propagated throughout the precompute pipeline.

Definitions

  • Terrain System - an Enlighten system representing a terrain patch
  • Terrain Geometry - an Enlighten geometry representing some part of the Terrain System. Given terrain system may contain multiple terrain geometries.
  • Terrain Mesh - an Enlighten mesh representing a part of terrain geometry. Terrain geometry may contain multiple terrain meshes.
  • Terrain Geometry Instance - an instance of a terrain geometry.
  • Horizontal plane - the plane by which the terrain heightfield is defined.
  • TerrainU - a direction (unit vector) that defines the U direction of the horizontal plane
  • TerrainV - a direction (unit vector) that defines the V direction of the horizontal plane
  • TerrainUp - a direction (unit vector) that is defined to be TerrainU x TerrainV and represents the up direction of the terrain. It is important to make sure that TerrainU and TerrainV have values that give the right TerrainUp direction (and not -TerrainUp direction).
  • Terrain Geometry UVs - the UVs resulting from the geometry packing stage.
  • Instance UVs - the UVs stored in IPrecompPackedInstance, those UVs are used to read from the lightmap. Instance UVs can be obtained by transforming the geometry UVs by the instance UV transform (also stored in IPrecompPackedInstance).
  • Lightmap LODs - Levels Of Detail. LOD0 has the highest resolution, LOD1 has a quarter of the number of pixels of LOD0, etc...

Geometry Packing

During this stage the geometry UVs (together with Enlighten charts and other data) are generated. For geometry to undergo special terrain packing (which is necessary if this geometry belongs to a Terrain system and we want to generate lightmap LODs for it) it needs to be marked up:

  • In HLBS this is triggered by setting isTerrain="true" on the geometry xml attribute. If isTerrain is set to true then terrainU and terrainV also need to be provided so that the ground plane and up direction for this geometry are defined. Valid terrain geometry HLBS markup is given below:
Terrain Geometry HLBS markup
<geom name="LandscapeComponent_3_0" version="3" isTerrain="true" terrainU="1.000000 0.000000 0.000000" terrainV="0.000000 1.000000 0.000000">
	<mesh [mesh 1 attributes go here] />
	<mesh [mesh 2 attributes go here] />
	<mesh [mesh ... attributes go here] />
</geom>
  • Both the isTerrain property and the terrainU and terrainV directions can be accessed via the low level API (on the IPrecompInputGeometry interface):
Low Level Terrain Geometry markup
virtual void					SetIsTerrain(bool isTerrain) = 0;
virtual bool					IsTerrain() const = 0;
virtual void					SetTerrainU(Geo::v128 const& terrainU) = 0;
virtual void					SetTerrainV(Geo::v128 const& terrainV) = 0;
virtual Geo::v128 const&		        GetTerrainU() const = 0;
virtual Geo::v128 const&		        GetTerrainV() const = 0;

Note that if the isTerrain attribute is set then both terrainU and terrainV attributes must be set as well. Moreover terrainU and terrainV must be orthonormal.

Once geometry has the terrain markup a special terrain geometry packing logic will be invoked during the GeometryPacking precompute stage. This special packing assures that:

  • Only one Enlighten chart is created with all the geometry meshes belonging to it.
  • The lightmap UVs are generated by projecting all the vertices onto the terrainU and terrainV directions
  • The lightmap UVs are renormalised to be in (0,1) range (in both U and V directions)
  • Note that all the AutoUV parameters, vertex linking parameters etc... have no effect on Terrain geometry.

System Packing

Similarly to geometry packing, special system packing needs to be triggered for terrain systems. For a system to undergo the special terrain packing necessary for lightmap LOD generation, it needs to be marked up:

  • In HLBS this is triggered by setting requiresTerrainLODs="true" as an attribute of a parameterSet that the system in question uses. A valid paramaterSet definition that will trigger terrain packing and LOD generation for any system that uses it is given below:
Terrain ParamSet markup
<parameterSet name="HighTerrain" id="0"
    outputPixelSize="1.0"
    clusterSize="2.0"
    irradBudget="64"
    samplesPerCluster="32"
    environmentResolution="16"
    requiresTerrainLODs="true"/>
  • The requiresTerrainLODs property can also be accessed via Low Level API (on IPrecompBuildParameters):
Low Level API to trigger Terrain System packing and LOD generation
virtual void SetRequiresTerrainLODs(bool b) = 0;
virtual bool RequiresTerrainLODs() const = 0;

Note that once given system is set to be a Terrain System (by making it use a parameterSet marked as above), than some restrictions apply:

  • All the (instances of) geometries in that system have to be marked up to be terrain geometries (i.e. they all need to have isTerrain="true" and they all need terrainU and terrainV directions set)
  • terrainU and terrainV direction of all geometries in that system have to be the same
  • pixelSize of all geometries in that system have to be the same
  • blockSize of all geometries in that system have to be the same

Chart LOD chain generation

Recall that for terrain geometries there is only one chart (per geometry). Each of those charts has a lightmap (atlas) size, defined by the extend of its (world space) projection onto terrainU and terrainV directions and its pixelSize. This is its base (LOD0) resolution. Higher LODs (1, 2, ...) are generated for each chart by doubling the pixelSize each time (and rounding to make sure that the chart lightmap size is always multiple of 2 in both x and y direction). The LOD chain generation stops when all Charts are reduced to 2x2.

For example if our system contains 6 (instances of) geometries, and those geometries have different world space dimensions (aligned with terrainU and terrainV directions for simplicity):

  • Geom1 (200m, 150m)
  • Geom2 (100m, 100m)
  • Geom3 (100m, 50m)

Pixel size is set to be 1m. We will have the following Chart LODs:

  • LOD0: Geom1(200x150), Geom2(100x100), Geom3(100x50)m pixelSize = 1
  • LOD1: Geom1(100x76), Geom2(50x50), Geom3(50x25), pixelSize = 2
  • LOD2: Geom1(50x38), Geom2(26x26), Geom3(26x14), pixelSize = 4
  • LOD3: Geom1(26x20), Geom2(14x14), Geom3(14x8), pixelSize = 8
  • LOD4: Geom1(14x10), Geom2(8x8), Geom3(8x4), pixelSize = 16
  • LOD5: Geom1(8x6), Geom2(4x4), Geom3(4x2), pixelSize = 32
  • LOD6: Geom1(4x4), Geom2(2x2), Geom3(2x2), pixelSize = 64
  • LOD7: Geom1(2x2), Geom2(2x2), Geom3(2x2), pixelSize = 128

System Packing and System LOD generation

For every Chart LOD the charts (one per Geometry) are assembled into an atlas. IPrecompPackedInstance objects are created (one from each chart) and their UVs are set to point into the final coordinates in the system lightmap (atlas). An UV transform is also set on each of the IPrecompPackedInstance objects to allow a transformation from the raw packed Geometry UVs (stored in IPrecompPackedGeometry) to the final lightmap UVs. That is, one can access the lightmap pixel by reading an UV from IPrecompPackedGeometry and transforming it with the UV transform stored in the IPrecompPackedInstance representing that geometry. Finally the IPrecompPackedSystem is created, which contains the atlas texture, all the packed instances and additional data.

Unless explicitly disabled, a half-pixel border will be generated around the final packed charts. To disable it:

  • HLBS: requiresTerrainPackingPadding="false" on the ParamSet of the system
  • Low Level API: IPrecompBuildParameters::SetRequiresTerrainPackingPadding(false)

Enlighten will then pack all instances in the system based on their relative world-space positions. This is useful when all instances in a system are the same size and arranged in a uniform grid, because the Enlighten lightmaps can then be incorporated into larger atlases with no seams between adjacent Enlighten terrain systems.

You can also request that a specific number of LODs be generated. This is done by:

  • HLBS: setting numTerrainLODLevels="X" on the ParamSet of the system, where X is a positive integer.
  • Low Level API: IPrecompBuildParameters::SetNumTerrainLODLevels(Geo::s32)

This can be used in combination with requiresTerrainPackingPadding="false" to produce a specified number of LODs with identical Enlighten UV streams, enabling their use in larger mip-mapped atlases.

The process of creating IPrecompPackedSystem is repeated for every LOD level. This results in packed systems of an atlas of smaller and smaller resolution. Note that by using the raw UV from the packed terrain geometries and the per instance UV transforms reported in IPrecompPackedInstances one can access the lightmaps (atlases) at any LOD.

IPrecompPackedSystems representing different LODs are than assembled into one IPrecompPackedSystem object (which by itself represents LOD0). Other LODs can be accessed by calling:

virtual Geo::s32				GetNumLods() const = 0;
virtual const IPrecompPackedSystem*		GetLod(Geo::s32 lodIndex) const = 0;

For any packed system calling GetLod(0) will return itself. Other LODs can be accessed by passing the appropriate index to GetLod(s32 index) method.

In the example above the precompute system packing stage will return one IPrecompPackedSystem (lets call it mainSystem). Now:

  • mainSystem->GetNumLods() will return 8
  • mainSystem->GetLod(0) will return a pointer to mainSystem
  • mainSystem has an atlas with three charts (200x150, 100x100, 100x50). That atlas can be accessed by using UVs from packed geometries and UV transforms of appropriate IPrecompPackedInstance}}s (from {{mainSystem).
  • mainSystem represents the main LOD0 version of the packed system. This is the version of the system that is used by all the precompute stages that do not need to take LOD-ing into account (for example pre-clustering, clustering)
  • We can access LOD3 (for example) by calling: lod3System = mainSystem->GetLod(3).
  • lod3System has an atlas with three charts (26x20, 14x14, 14x8). That atlas can be accessed by using UVs from packed geometries and UV transforms of appropriate IPrecompPackedInstance}}s (from {{lod3System).

The results of terrain system packing for the terrain patch seen in the picture in the terminology paragraph are presented below. This system contains six (instances of) terrain geometries. Each of them ends up being one chart in the final atlas. At the base LOD(0) they have a resolution of about 20x20 each. The reduction in resolution can be seen in the consecutive screenshots, all the way until each chart is only 2x2. Also note that each chart is surrounded by a half pixel border (on each LOD). Using LOD4 instead of LOD0 reduces the number of lightmap pixels that need to be solved from around 2600 to just 24.

Precompute outputs

After terrain system and geometry markup, the different LODs will be generated for that system and propagated throughout the precompute pipeline. This will result in multiple rad core files (and radiosity normal texture files) begin generated for the terrain system. Each rad core (and .rnt file) represents a different LOD of that system. If the name of the terrain system is "TerrainPatch01" and it has LODs as pictured above (LOD0 ... LOD4), then the HLBS will generate following radcore (and rnt) files (for SSE solver target):

  • TerrainPatch01.rc.see
  • TerrainPatch01.rc_LOD_1.see
  • TerrainPatch01.rc_LOD_2.see
  • TerrainPatch01.rc_LOD_3.see
  • TerrainPatch01.rc_LOD_4.see
  • TerrainPatch01.rnt
  • TerrainPatch01_LOD_1.rnt
  • TerrainPatch01_LOD_2.rnt
  • TerrainPatch01_LOD_3.rnt
  • TerrainPatch01_LOD_4.rnt

Note that LOD0 is represented by the rad core with the same name as if the LOD-ing system was not being used.
Those rad cores and rnt textures can now be used by the run time APIs (described below) to solve light bouncing.
If the HLBS is not being used than the LOD rad cores are accessible via IPrecompSystemRadiosity:

virtual Geo::s32					GetNumLods() const = 0;
virtual const RadSystemCore*		                GetLodRadCore(Geo::s32 lodIndex) const = 0;

Precompute API summary

IPrecompInputGeometry markup
virtual void					SetIsTerrain(bool isTerrain) = 0;
virtual bool					IsTerrain() const = 0;
virtual void					SetTerrainU(Geo::v128 const& terrainU) = 0;
virtual void					SetTerrainV(Geo::v128 const& terrainV) = 0;
virtual Geo::v128 const&		        GetTerrainU() const = 0;
virtual Geo::v128 const&		        GetTerrainV() const = 0;
IPrecompBuildParameters markup (system markup)
virtual void 		SetRequiresTerrainLODs(bool b) = 0;
virtual bool 		RequiresTerrainLODs() const = 0;
virtual void 		SetRequiresTerrainPackingPadding(bool b) = 0;
virtual bool 		RequiresTerrainPackingPadding() const = 0;
virtual void		SetNumTerrainLODLevels(Geo::s32 v) = 0;
virtual Geo::s32	GetNumTerrainLODLevels() const = 0;
IPrecompPackedSystem: access number of LODs, UV transforms per LOD
virtual Geo::s32				GetNumLods() const = 0;
virtual const IPrecompPackedSystem*		GetLod(Geo::s32 lodIndex) const = 0;
IPrecompSystemRadiosity: rad core access
virtual Geo::s32				GetNumLods() const = 0;
virtual const RadSystemCore*			GetLodRadCore(Geo::s32 lodIndex) const = 0;
ILightTransportOutput: debug output of Light Transport
virtual Geo::s32				GetNumLODs() const = 0;
virtual const ILightTransportOutput*		GetLOD(Geo::s32 lodIndex) const = 0;

Runtime

For terrain LOD to work, Enlighten needs to be able to solve the same system at different resolutions. This is handled in the precompute by generating multiple radiosity cores for terrain systems where each RadSystemCore pertains to a particular LOD. The integration will need to determine, based on the camera position, which LODs need to be solved. This will, in most cases, be a small subset of the full LOD set since most terrain is expected to be far away. Once the desired LOD subset has been solved, the Enlighten::ResampleBounce() stage must be executed using only the highest quality LOD out of the solved set as input. This ensures we propagate bounce at the highest possible quality level.

Since only a small subset of LODs may need solving at a given time, it is recommended that the integration only keep resident in memory the {{RadSystemCore}}s that require imminent solving. This will greatly reduce the burden on streaming subsystems and the memory requirements for large worlds.

Low-Level Runtime

From the Enlighten low-level API, there are no new functions to call. Users of the low-level API will need to make sure the integration can solve multiple {{RadSystemCore}}s per system and then resample the bounce from the highest quality solution only. Some care must be taken to ensure that lightmap textures are not used by the rendering before their contents have been updated. This is more important now as switching to a new LOD may result in a texture having stale data in it during the time period between scheduling the solve and the data being visible in the texture resource. Using the texture before it is ready will lead to popping artefacts as LODs are switched in and out of the solution.

High-Level Runtime

For users of the HLRT, there are a few changes to be aware of in order to correctly handle terrain LOD. Prior to Enlighten 3.03, there was a 1-to-1 mapping between Enlighten::System}}s and {{Enlighten::RadSystemCore}}s. As of Enlighten 3.03 however, there can now be many {{RadSystemCores per Enlighten::System. Since there is additional supporting data and memory required per RadSystemCore, this data has been wrapped up into a new object type called the SystemSolutionSpace whose interface is exposed via the Enlighten::ISystemSolutionSpace class.

For more details please refer to the High level runtime section.

GeoRadiosity Support

If a terrain system has its LOD generated by the precompute pipeline (i.e. there are multiple radcore and rnt files generated for it), then GeoRadiosity will let you switch those rad cores at run time for the selected system. After the rad core has been switched it is used to calculate the lighting for the system. The change in resolution of the light map can also be seen in the Chart->RayOrigins view (but NOT in the Chart->Chart Texture view!). Note that not all views in GeoRadiosity work with LOD switching. At the moment the following modes work:

  • All rendering modes under "Renderer: Scene"
  • Chart->Ray Origins