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.