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 inIPrecompPackedInstance
). - 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. IfisTerrain
is set totrue
thenterrainU
andterrainV
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:
<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 theterrainU
andterrainV
directions can be accessed via the low level API (on theIPrecompInputGeometry
interface):
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
andterrainV
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:
<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 (onIPrecompBuildParameters
):
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 needterrainU
andterrainV
directions set) terrainU
andterrainV
direction of all geometries in that system have to be the samepixelSize
of all geometries in that system have to be the sameblockSize
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 8mainSystem->GetLod(0)
will return a pointer tomainSystem
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 appropriateIPrecompPackedInstance}}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 appropriateIPrecompPackedInstance}}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
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;
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;
virtual Geo::s32 GetNumLods() const = 0; virtual const IPrecompPackedSystem* GetLod(Geo::s32 lodIndex) const = 0;
virtual Geo::s32 GetNumLods() const = 0; virtual const RadSystemCore* GetLodRadCore(Geo::s32 lodIndex) const = 0;
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