How Does the Flat Qualifier Work in GLSL?

The flat interpolation qualifier in GLSL disables the rasterizer's default barycentric interpolation when passing varying data from a vertex (or geometry) shader to a fragment shader. Instead of blending values smoothly across the surface of a primitive, the rasterizer assigns the exact output value of a single designated "provoking vertex" to all fragments generated by that primitive. This mechanism is essential for passing discrete non-numeric or integral data, such as material IDs, bitmasks, and unblended flat-shaded surface normals.

Default Interpolation vs. Flat Interpolation

During the fixed-function rasterization stage, hardware interpolates vertex attributes across primitives (triangles, lines, or points) before they reach the fragment shader.

GLSL provides three primary interpolation qualifiers:

The Provoking Vertex Mechanism

Because a triangle consists of three distinct vertices, the pipeline needs a rule to determine which vertex provides the flat value to the fragments. This source vertex is known as the provoking vertex.

For triangle strips or fans, the provoking vertex shifts dynamically according to the winding rules defined by the graphics specification.

Syntax and Mandatory Type Constraints

When using the flat qualifier, the declaration must match identically between the sending and receiving shader stages.

Vertex Shader Example

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in int aMaterialID;

flat out int vMaterialID;
flat out vec3 vFacetNormal;

void main() {
    vMaterialID = aMaterialID;
    vFacetNormal = aPos; // Evaluated at the provoking vertex
    gl_Position = vec4(aPos, 1.0);
}

Fragment Shader Example

#version 330 core
flat in int vMaterialID;
flat in vec3 vFacetNormal;

out vec4 FragColor;

void main() {
    if (vMaterialID == 1) {
        FragColor = vec4(vFacetNormal, 1.0);
    } else {
        FragColor = vec4(0.5, 0.5, 0.5, 1.0);
    }
}

Type Rules for Integers

In GLSL, all integer types (int, uint, and their vector variants like ivec2, uvec4) passed between stages must be qualified with flat. Because interpolating discrete integer IDs across continuous fragments results in fractional values that cannot be represented as integers, the GLSL specification enforces compile-time errors if integer varyings omit the flat qualifier.

Common Use Cases