Description

This sample demonstrates how to use a Texture Array to render a terrain with visually-complex texturing at high performance. The texture array is similar to a 3D texture, allowing for multiple 'slices' or sub-textures bound to a single texture ID, but arrays are much higher performance due to disallowing trivial filtering across the slices. We get great terrain rendering performance by eliminating the need to perform multiple, blended passes over the geometry, and improved quality by avoiding boundary issues that would occur with 2D atlas texturing.

Screenshot

APIs Used

  • GL_NV_texture_array
  • glTexImage3D
  • glTexSubImage3D
  • GL_NV_map_buffer_range
  • glMapBufferRange
  • glUnmapBuffer

Shared User Interface

The Graphics samples all share a common app framework and certain user interface elements, centered around the "Tweakbar" panel on the left side of the screen which lets you interactively control certain variables in each sample.

To show and hide the Tweakbar, simply click or touch the triangular button positioned in the top-left of the view.

Technical Details

The sample loads multiple terrain texture images into a single texture array. Multiple images are loaded from individual texture files. The texture array is populated with calls to glTexImage3D and glTexSubImage3D.

The vertex program computes the texture co-ordinates. It computes a texture array slice based on the height of the terrain. It also computes a factor that represents a cliff texture on steeper slopes.

The fragment program samples the texture array twice to fetch two adjacent array slices and linearly interpolates between them. It also fetches the cliff texture slice and interpolates between the cliff and the previous result.

Closeup

Figure 1: Close-up of Texture Array Blending
(Click to enlarge)

Figure 1 illustrates the blending, most obviously in the foreground at the right hand edge. There is a blend between three layers based on height: green grass, brown dirt and white snow; the snow doesn't blend in fully in the foreground and the result is mid-grey. The dark grey cliff texture is also visible at the top-right of the image where the terrain angle is more vertical.

Future Improvement

The terrain rendering could be further optimized in several ways:

  • Normals and heights could be stored as 16-bit GL_HALF_FLOAT_OES values. There would probably be no loss of visual fidelity for a significant reduction in bandwidth.
  • The indexing order is currently sub-optimal and could be improved with a preprocess step.

Terrain Height Field Generation

There is a significant amount of sample code that exists to generate a terrain height field. This is not strictly necessary to enable the texture array technique. However, a plausibly realistic height field is required. We have not made strenuous efforts for true realism.

The height field and normal vertex attributes are technically dynamic because they change in response to changes in the GUI sliders. If this code were being deployed in a game terrain engine, the terrain would be strictly static (in the absence of terrain deformation which we are not attempting to demonstrate). Hence, we do not attempt to optimize for the case of dynamic terrain updates beyond keeping the app interactive. Optimal dynamic VBO updates would be double-buffered so that the graphics driver does not stall waiting for the CPU to finish updating.

The TerrainSim class is responsible for generating the height field, using fBm and ridge noise. The TerrainSimRenderer class owns vertex buffer objects (VBOs) and the corresponding indices. The TerrainSimThread class manages multiple threads, taking advantage of Tegra's multiple CPU cores to accelerate the height field generation in response to adjustment's on the GUI sliders.

See Also

  • F. Kenton Musgrave, D. S. (2002). Texturing and Modeling, Third Edition: A Procedural Approach. Morgan Kaufmann.