GLSL Terrain Clipmap Continuous LOD Blending

Rendering large-scale terrain without visual popping requires a combination of geometry clipmaps and continuous level-of-detail (LOD) blending, known as geomorphing. In GLSL, this algorithm calculates a transition parameter per vertex based on the distance from the clipmap ring's center, morphing the vertex's spatial position and heightmap sampling coordinates between the current level and the next coarser level. This technique preserves continuous surface topology, prevents T-junction cracks, and completely eliminates popping artifacts between nested terrain rings.

The Geometry Clipmap Algorithm

Geometry clipmaps organize terrain into a set of \(N\) concentric, nested grid rings centered on the viewer. Each ring shares an identical vertex resolution (typically \(255 \times 255\) or \(511 \times 511\)) but doubles the grid spacing of the inner ring:

\[\text{spacing}_L = \text{spacing}_0 \times 2^L\]

Where \(L\) is the clipmap level index. Because the outer edge of level \(L\) has twice the vertex density of the inner edge of level \(L+1\), stitching them directly causes cracks and temporal popping as the camera moves.

To solve this, the transition algorithm applies vertex geomorphing across a defined blend region (typically the outer 20% to 30% of each ring).

Geomorphing Transition Parameter

Inside the vertex shader, each vertex evaluates its distance from the ring center. Because clipmap rings are square, the algorithm uses a Chebyshev metric (maximum norm) rather than Euclidean distance to match the square boundary of the clipmap ring:

// Calculate normalized distance in the range [0, 1] relative to the ring's extent
vec2 coordFromCenter = abs(vertexGridPos - ringCenterGridPos);
float maxDist = max(coordFromCenter.x, coordFromCenter.y);

// Define transition boundaries (e.g., transition occurs between 70% and 100% of ring radius)
float blendStart = ringRadius * 0.70;
float blendEnd   = ringRadius;

// Morph factor: 0.0 = fully current LOD, 1.0 = fully morphed to next coarser LOD
float morphFactor = clamp((maxDist - blendStart) / (blendEnd - blendStart), 0.0, 1.0);

Vertex Snapping and Position Morphing

Before sampling the heightmap, the horizontal coordinates of vertices with odd grid indices are shifted toward their even neighbors. At \(\text{morphFactor} = 1.0\), the vertex position perfectly coincides with the coarser grid of level \(L+1\):

// Identify odd vertices along both axes
vec2 gridStep = vec2(currentGridSpacing);
vec2 coarserStep = gridStep * 2.0;

// Determine target coarser grid position
vec2 morphedWorldXZ = floor((worldPos.xz + gridStep * 0.5) / coarserStep) * coarserStep;

// Linearly blend the planar coordinates
worldPos.xz = mix(worldPos.xz, morphedWorldXZ, morphFactor);

Height Sampling and Blending

To prevent vertical popping, height values are blended alongside horizontal coordinates. Two height samples are taken from the heightmap texture: one using the current level's UV coordinates and one corresponding to the coarser level's sampling footprint.

// Current level height sample
float hFine = textureLod(uHeightMap, uvFine, 0.0).r;

// Coarser level height sample (mimics filtered lower-resolution representation)
float hCoarse = textureLod(uHeightMap, uvCoarse, 0.0).r;

// Blend the final elevation
worldPos.y = mix(hFine, hCoarse, morphFactor) * uTerrainHeightScale;

Alternatively, if a single high-resolution texture or virtual texture is used, sampling \(h_{\text{coarse}}\) can be performed by snapping the texture coordinate to the coarse texel footprint:

vec2 texelSize = 1.0 / uTextureResolution;
vec2 coarseUV = floor(uvFine / (texelSize * 2.0)) * (texelSize * 2.0) + texelSize;
float hCoarse = textureLod(uHeightMap, coarseUV, 0.0).r;
worldPos.y = mix(hFine, hCoarse, morphFactor) * uTerrainHeightScale;

Eliminating Cracks and T-Junctions

By applying this continuous interpolation, every vertex along the outer border reaches \(\text{morphFactor} = 1.0\) precisely where it meets the inner boundary of ring \(L+1\). The geometric positions and elevations of the finer mesh align identically with the vertices of the coarser mesh, eliminating T-junction cracks entirely in screen-space without requiring dynamic mesh stitching or degenerated triangles.