Description

This sample shows how OpenGL hardware tessellation support can be used to implement a highly-efficient terrain engine that supports high geometric detail.

Screenshot

APIs Used

  • GL_TESS_CONTROL_SHADER
  • GL_TESS_EVALUATION_SHADER
  • glGenProgramPipelines
  • glUseProgramStages
  • glPatchParameteri
  • GLSL 'textureGather'

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

This sample demonstrates how to render a procedural terrain using OpenGL tessellation shaders, including automatic level of detail and culling.

It is not intended to show the optimal way of rendering terrain, but more to serve as a testbed for experimenting with tessellation shaders and different terrain heightfield functions.

Rendering Process

The code renders the terrain as a grid of patches, each of which can generate a grid of up to 64 x 64 triangles (this is the limit for the current tessellation hardware). The patches are rendered using instancing, using the glDrawArraysInstanced() function with a dummy vertex buffer. This means the whole terrain is rendered with a single draw call. The actual patch origin positions are then calculated from the gl_InstanceID in the vertex shader.

Level of detail (determining the tessellation level) and view frustum culling are performed in the tessellation control shader. Level of detail is calculated based on the projected screen size of a sphere fitted to each patch edge (as described in NVIDIA's DirectX 11 terrain tessellation sample, see link below). Culling is performed by testing the bounding sphere of the patch against the view frustum planes.

The terrain height is calculated procedurally for each generated vertex in the tessellation evaluation shader. Procedural noise is calculated based on a small 2D texture containing random values. The GLSL textureGather() function is used to read the neighboring texels in a single pass, so that custom smooth interpolation can be performed. Using hardware linear interpolation does not look as good, and has stepping artifacts at low frequencies due to the limited hardware precision (9 bits sub-texel).

The noise function is used is based on an advanced perlin noise article by Inigo Quilez (see link below). It uses the derivatives of the noise function to weight the noise octaves.

Surface normals for the terrain can be calculated in two ways: either by evaluating the terrain function at two additional neighboring points in the tessellation evaluation shader (if the "smooth normals" option is enabled), or per-triangle using the geometry shader (if "smooth normals" is disabled). The geometry shader method is usually slower on current hardware, and only calculates a flat surface normal.

Finally, the terrain color is calculated in the fragment shader, based on the height and normal.

Controls

The Tweakbar has several options and sliders to control various parameters.

  • There are several different quality settings, which vary the number of terrain patches, and how large each patch is. Each quality level up doubles the resolution in each dimension (4 times the number of triangles).

  • If "auto LOD" mode is enabled, the tessellation level is calculated to try and maintain a constant screen-space triangle size, which is controlled by the "triangle size" slider.

  • If "auto LOD" is disabled, you can directly control the tessellation factors using the two sliders. This mode is typically much slower, since patches in the distance are typically very over-tessellated.

  • You can also control the frequency of the noise function, the terrain height, and how many noise octaves are used to generate the terrain.