← blog
Nov 6, 2025

Piecing Together the Tessellation Pipeline

I had been staring at OpenGL's tessellation shaders for hours, trying to make sense of patches, layouts, and all those mysterious gl_TessLevel variables. Here is a summary of it all.

What's a "patch"?

A patch functions as the fundamental input unit to the tessellation pipeline, comparable to how vertices feed the vertex shader or fragments feed the fragment shader. It represents a group of vertices that defines a piece of a surface to be subdivided.

To specify patch vertex count:

glPatchParameteri(GL_PATCH_VERTICES, 4);

When drawing patches:

glDrawArrays(GL_PATCHES, 0, 4);

The Tessellation Control Shader (TCS) executes once per vertex in the input patch.

What happens in the Tessellation Control Shader?

The TCS accesses patch vertices through the built-in array gl_in[]. With layout(vertices = 4) out; declared, four control points exist per patch. Each TCS invocation is identified by gl_InvocationID and writes to gl_out[gl_InvocationID].

Basic forwarding:

gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;

This passes control point positions to the Tessellation Evaluation Shader (TES) — a crucial step for downstream processing.

What about the tessellation levels?

Within the TCS, tessellation granularity is specified:

gl_TessLevelOuter[0..3] = ...
gl_TessLevelInner[0..1] = ...

Level counts depend on domain type:

Outer levels govern edge subdivision; inner levels control interior density.

How does the Tessellation Evaluation Shader fit in?

The TES interprets patch structure through layout declarations:

layout(quads) in;

This specifies quad domain interpretation using (u, v) coordinates between 0 and 1 for vertex interpolation across the surface. Alignment requirements: TCS vertex count must match TES domain:

Mismatched counts result in OpenGL ignoring excess data.

What are Tessellation Coordinates (gl_TessCoord)?

The tessellator generates new vertices with built-in variable gl_TessCoord, indicating position within the tessellated patch.

Domaingl_TessCoord contentsDescription
quadsvec3(x, y, z) where z = 0(x, y) in [0,1] across the quad
trianglesvec3(u, v, w) with u+v+w=1Barycentric coordinates
isolinesvec3(u, v, w) where u,v∈[0,1]u = position along line, v = line index
float u = gl_TessCoord.x;
float v = gl_TessCoord.y;

For quads: u = 0 is left, u = 1 is right, v = 0 is bottom, v = 1 is top. Bilinear interpolation between four control points:

vec3 P = mix(mix(P0, P1, u), mix(P3, P2, u), v);

Why is the MVP multiplied in the TES?

Applying the Model-View-Projection transformation in the vertex shader before tessellation produces incorrect results. Projection via the MVP is not linear, and tessellation interpolates linearly. Tessellating in clip space distorts edges and produces curved artifacts.

Correct approach: maintain object or world space coordinates during tessellation, then apply MVP in TES:

gl_Position = uMVPMatrix * vec4(P, 1.0);

This ensures all generated vertices receive proper clip-space transformation.

How does everything connect?

StageOperates onSpaceWhat it does
Vertex Shader1 vertexObjectPasses raw vertex data
Tessellation ControlPatch (N vertices)ObjectSets tessellation levels
TessellatorObjectSubdivides patch
Tessellation EvaluationGenerated verticesObject → ClipInterpolates + applies MVP
Fragment ShaderFragmentsScreenFinal color

Quick Recap