Understanding the GLSL Provoking Vertex Rule
The provoking vertex rule governs how OpenGL and GLSL select a single
vertex to provide attribute data for an entire primitive when using flat
shading. In graphics pipelines, attributes qualified with the
flat keyword bypass the standard perspective-correct
interpolation across a primitive's surface. Instead, the rasterizer
identifies one specific vertex—the provoking vertex—and uses its output
values for every fragment generated by that primitive. This article
explains the mechanics behind the provoking vertex, the conventions used
to determine it across various primitive types, and how it impacts GLSL
shader execution.
The Role of Flat Shading in GLSL
When passing varying data from a vertex shader to a fragment shader
in GLSL, the pipeline normally interpolates the values across the face
of the primitive using linear or perspective-correct algorithms. Adding
the flat interpolation qualifier disables this process:
// Vertex Shader
flat out vec3 vColor;
// Fragment Shader
flat in vec3 vColor;Because interpolation is disabled, the fragment shader requires a
definitive source for vColor. The pipeline resolves this
ambiguity by designating one vertex in the primitive as the source of
truth: the provoking vertex. Every fragment generated by that primitive
receives the exact attribute values emitted by this single vertex's
execution.
Provoking Vertex Conventions
OpenGL supports two primary conventions for selecting the provoking
vertex: First-Vertex and Last-Vertex.
Historically, OpenGL defaulted to the last-vertex convention, whereas
Direct3D utilized the first-vertex convention. In modern OpenGL (version
3.2 and later), developers can explicitly toggle between these modes
using the glProvokingVertex API:
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
// or
glProvokingVertex(GL_LAST_VERTEX_CONVENTION);The convention chosen determines which index in the primitive
assembly stage provides the flat outputs.
Determining the Provoking Vertex by Primitive Type
The assignment of the provoking vertex depends on the primitive topology and the active convention. Assuming zero-based indexing for vertices forming the \(i\)-th primitive:
1. Points
For point primitives (GL_POINTS), there is only one
vertex per primitive. That single vertex is always the provoking vertex,
regardless of the active convention.
2. Independent Lines
For GL_LINES, each segment \(i\) consists of vertices \(2i\) and \(2i +
1\):
- First-Vertex Convention: Vertex \(2i\)
- Last-Vertex Convention: Vertex \(2i + 1\)
3. Line Strips and Line Loops
For GL_LINE_STRIP and GL_LINE_LOOP, segment
\(i\) is formed by vertices \(i\) and \(i +
1\):
- First-Vertex Convention: Vertex \(i\)
- Last-Vertex Convention: Vertex \(i + 1\)
4. Independent Triangles
For GL_TRIANGLES, each triangle \(i\) is defined by vertices \(3i\), \(3i +
1\), and \(3i + 2\):
- First-Vertex Convention: Vertex \(3i\)
- Last-Vertex Convention: Vertex \(3i + 2\)
5. Triangle Strips
For GL_TRIANGLE_STRIP, each triangle shares vertices
with adjacent triangles. Triangle \(i\)
uses vertices \(i\), \(i + 1\), and \(i
+ 2\):
- First-Vertex Convention: Vertex \(i\)
- Last-Vertex Convention: Vertex \(i + 2\)
Because winding order alternates with each step in a triangle strip, hardware primitive assembly accounts for this offset internally to ensure that provoking vertex assignments remain consistent without corrupting attribute mapping.
6. Triangle Fans
For GL_TRIANGLE_FAN, triangle \(i\) is defined by the origin vertex \(0\), vertex \(i +
1\), and vertex \(i + 2\):
- First-Vertex Convention: Vertex \(i + 1\) (the shared origin vertex \(0\) is intentionally avoided to allow unique attribute assignments per fan segment)
- Last-Vertex Convention: Vertex \(i + 2\)
Implications for Shader Development
Understanding the provoking vertex rule is critical when packing
non-interpolated data into vertex buffers, such as material IDs, face
normals, or transform indices. If the application's drawing code relies
on the first vertex while the driver defaults to the last vertex, the
fragment shader will sample data from an unintended vertex. Explicitly
setting the convention via glProvokingVertex ensures
deterministic behavior across different GPU vendors and rendering
backends.