Terrain |
This topic describes how to create a heightfield-based terrain.
This topic contains the following sections:
DigitalRune Graphics uses following types to define terrain:
A TerrainNode is a SceneNode that represents a terrain in the scene graph. TerrainNodes are rendered by the TerrainRenderer.
The terrain data is defined by the Terrain class. Terrain instances can be shared, i.e., multiple TerrainNodes can reference the same Terrain instance.
The terrain node is rendered using a Material (similar to a normal Mesh). If no custom material is set, a default material is used. The default material supports the render passes "GBuffer" and "Material" (which are used in the deferred lighting samples).
The default material renders the terrain using height and material information stored in clipmaps. These clipmaps are explained below.
Terrain nodes can be rendered into the shadow maps. The scene node property CastsShadows is true by default. Please note: When using the default ShadowCasterQuery, terrain nodes are only rendered into the shadow maps of DirectionalLights.
The class Terrain defines a heightfield-based terrain.
A terrain is split into one or more TerrainTiles. The terrain tiles define the geometry and materials of the terrain.
A TerrainTile defines the geometry (height, normals, holes) and materials of a rectangular terrain region.
The geometry of the terrain is defined using textures. The HeightTexture defines height values (absolute heights in world space) of the terrain. The NormalTexture contains the terrain normal vectors. This texture encodes the normal vectors like a standard "green-up" normal map. (I.e., the world space +x component of the normal is stored in the red channel. The world space up (+y) component is stored in the blue channel. The negative world space z component is stored in the green channel.) HoleTexture defines holes in the terrain. The texture is used like an alpha mask texture. If the alpha channel contains 0, then there is a hole in the terrain.
All textures should contain mipmaps. Ideally, the mipmaps are generated using 3 x 3 downsampling instead of the usual 2 x 2 downsampling. - Suitable textures can be created using the TerrainHelper class.
Each tile has a set of material layers (class TerrainLayer) that define the appearance (dirt, grass, decals, roads). The material layers are applied (blended) one after the other, which means that a layer can override previous layers.
OriginX and OriginZ define the tile origin in world space - which corresponds to center of the first texel of the textures. CellSize defines the horizontal distance between two height values. The texture coordinate u is aligned with the positive x-axis. The texture coordinate v is aligned with the positive z-axis. This means, if the cell/texel size is 1 world space unit and the texture is 1025 x 513 texels large, then the terrain tile covers the area between (OriginX, *, OriginZ) and (OriginX + 1024, *, OriginZ + 512). This also means that two neighboring TerrainTile should overlap by one texel to avoid gaps!
OriginX and OriginZ should always be an integer multiple of the cell size. For example, if the cell size is 0.5, valid origin values are -0.5, 0, 0.5, 1, etc.
TerrainLayers define the materials (detail textures, decals, roads, etc.) of a terrain tile. The material layers are applied (blended) one after the other, which means that a layer can override previous layers.
For example: The first layer draws a dirt texture that covers the whole tile. The second layer draws a grass texture, which covers only parts of the tile defined by a blend map. Additional layers add roads and decals like dirt, leaves, sewer grates, etc.
Each terrain tile can have its own set of terrain layers, but they can also share the same terrain layer instances.
There are several default terrain layer types:
This section explains how to render TerrainNodes.
The TerrainClipmapRenderer is a SceneNodeRenderer that handles TerrainNodes. It renders into the two clipmaps stored in the TerrainNode: BaseClipmap and DetailClipmap.
Terrain clipmaps are usually generated at the start of the Draw-method, before the scene is rendered:
// Update terrain clipmaps. terrainClipmapRenderer.Render(terrainNodes, context); // Render scene...
The TerrainClipmapRenderer uses the geometry and material information defined by the Terrain, combines the information (e.g. blends the material detail textures) and caches the information in the clipmaps. The renderer updates the clipmaps incrementally when the camera moves.
This renderer uses the Materials of the TerrainTiles and the TerrainLayers. The materials need a render pass called "Base" to render into the base clipmap and a render pass called "Detail" to render into the detail clipmap. (A material can have render passes for both, "Base" and "Detail".)
The BaseClipmap stores information at the terrain vertex level. It usually provides height, normal and hole information which define the terrain geometry. When the default terrain materials are used, the terrain clipmap renderer uses the materials of the TerrainTiles to render into the base clipmap which uses the surface format HalfVector4. The base clipmap stores:
terainNode.BaseClipmap.Textures[0]: (absolute terrain height, world space normal x, world space normal z, hole flag)
The DetailClipmap stores more detailed information which is used to shade the terrain. It usually stores detail normals (for normal mapping), diffuse colors, specular colors, heights (for parallax occlusion mapping) and other material information. When the default terrain materials are used, the terrain clipmap renderer uses the materials of the TerrainLayers to render into the detail clipmap which consists of several clipmap hierarchies that use the surface format Color. The detail clipmap stores:
terainNode.DetailClipmap.Textures[0]: (world space detail normal x, world space detail normal z, specular power, hole flag) terainNode.DetailClipmap.Textures[1]: (diffuse R, diffuse B, diffuse B, not used) terainNode.DetailClipmap.Textures[2]: (specular intensity, height, not used, not used)
The content of the clipmaps can be changed by using custom materials for the TerrainTiles and TerrainLayers. This way, it is possible to store different information, for example, an "emissive intensity" could be added for glowing terrain.
The TerrainRenderer is a SceneNodeRenderer that handles TerrainNodes.
The terrain is usually rendered before or after the opaque objects of the scene. For example:
// Render terrain. terrainRenderer.Render(terrainNodes, context); // Render opaque objects...
The renderer draws the terrain using a single mesh (one draw call) which represents a geometry clipmap. This mesh is represented by the TerrainRendererMesh class. It is not necessary to create a TerrainRendererMesh manually - it is created automatically by the terrain renderer. However, the creation of the mesh can take a short moment. Therefore, it is also possible to control when the mesh is created or to load it from a file. TerrainRendererMesh can be loaded from file and saved to file using Load and Save. A manually created mesh can be assigned to the terrain renderer using SetMesh.
The terrain renderer renders terrain nodes using the Material of the node. It is possible to use a custom material to control how the terrain is rendered. The default material uses clipmaps (BaseClipmap and DetailClipmap). The clipmaps are created and updated by the TerrainClipmapRenderer - not by the TerrainRenderer.
When the terrain is rendered, the terrain mesh and texture resolution depends on the distance from the camera. When the TerrainRenderer renders the terrain, it uses the LodCameraNode of the RenderContext. (If no LodCameraNode is set, the normal CameraNode of the render context is used.)
A terrain node should only be rendered for a single camera node because the renderer might cache camera-dependent LOD data. If a scene contains two camera nodes (e.g. for 2 player split screen rendering), the LodCameraNode should be one of these two cameras. It could also be a "virtual" camera, which is e.g. between both player cameras. Switching the cameras within one frame would be inefficient.
Alternatively, each camera could use a separate TerrainNode instance. When the image of a camera is rendered only one terrain node should be rendered. Several terrain nodes can reference the same Terrain instance.
DigitalRune Graphics contains several predefined effects which can be used in the terrain materials. (See also Pre-Built Content). Following effects can be used in the material of the terrain node:
Effect | Description |
---|---|
DigitalRune/Terrain/TerrainGBuffer | Renders the terrain in the G-buffer pass. Holes in the effect are rendered by culling triangles. |
DigitalRune/Terrain/TerrainGBufferHoles | Renders the terrain in the G-buffer pass. Holes in the effect are created using texkill/clip in the pixel shader. |
DigitalRune/Terrain/TerrainGBufferPom | Renders the terrain in the G-buffer pass. Holes in the effect are rendered by culling triangles. The effect supports parallax occlusion mapping. |
DigitalRune/Terrain/TerrainGBufferHolesPom | Renders the terrain in the G-buffer pass. Holes in the effect are created using texkill/clip in the pixel shader. The effect supports parallax occlusion mapping. |
DigitalRune/Terrain/TerrainMaterial | Renders the terrain in the Material pass. Holes in the effect are rendered by culling triangles. |
DigitalRune/Terrain/TerrainMaterialHoles | Renders the terrain in the Material pass. Holes in the effect are created using texkill/clip in the pixel shader. |
DigitalRune/Terrain/TerrainMaterialPom | Renders the terrain in the Material pass. Holes in the effect are rendered by culling triangles. The effect supports parallax occlusion mapping. |
DigitalRune/Terrain/TerrainMaterialHolesPom | Renders the terrain in the Material pass. Holes in the effect are created using texkill/clip in the pixel shader. The effect supports parallax occlusion mapping. |
DigitalRune/Terrain/TerrainShadowMap | Renders the terrain into the shadow map. Holes in the effect are rendered by culling triangles. |
DigitalRune/Terrain/TerrainShadowMapHoles | Renders the terrain into the shadow map. Holes in the effect are created using texkill/clip in the pixel shader. |
You can use following code to create a material for a terrain node:
var shadowMapEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap"); var gBufferEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer"); var materialEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainMaterial"); var material = new Material { { "ShadowMap", new EffectBinding(_graphicsService, shadowMapEffect, null, EffectParameterHint.Material) }, { "GBuffer", new EffectBinding(_graphicsService, gBufferEffect, null, EffectParameterHint.Material) }, { "Material", new EffectBinding(_graphicsService, materialEffect, null, EffectParameterHint.Material) } }; TerrainNode = new TerrainNode(terrain, material);
Have a look at the terrain samples (see Samples) for more details.
The class TerrainHelper contains various methods which help to create and manipulate height maps and other terrain textures.
The terrain renderers provide several effect parameter bindings for effect parameters which can be used in the materials of terrain nodes, terrain tiles and terrain layers. A list of supported effect parameter semantics can be found in the class TerrainEffectParameterSemantics.
The rendering system caches terrain information internally to improve performance. The cache is invalidated automatically when an important property of a terrain class is changed. However, in some cases it is not possible to automatically detect these changes, e.g. if the data of a height texture is changed. In this case it is necessary to manually call the Invalidate method of the appropriate terrain class.