How GLSL Flat Variables Avoid Interpolation
In the OpenGL Shading Language (GLSL), data passed from the vertex
shader to the fragment shader undergoes perspective-correct
interpolation across the surface of a primitive by default. By applying
the flat interpolation qualifier to an output variable,
developers instruct the GPU hardware to bypass the rasterizer's
interpolation logic entirely. Instead of blending values across
barycentric coordinates, the rasterizer assigns the exact value from a
single, designated vertex—known as the provoking vertex—to every
fragment generated by that primitive.
Default Interpolation Mechanics
Under standard rendering pipelines, variables declared as
out in a vertex shader and in in a fragment
shader default to smooth interpolation. During
rasterization, the GPU calculates the barycentric coordinates \((\lambda_0, \lambda_1, \lambda_2)\) for
each pixel center inside a primitive. The fragment's input value is
computed mathematically by interpolating vertex attributes:
\[V_{fragment} = \frac{\sum_{i} \lambda_i \frac{V_i}{w_i}}{\sum_{i} \frac{\lambda_i}{w_i}}\]
This calculation involves perspective division using the clip-space \(w\) coordinate, ensuring that textures and attributes deform realistically with depth.
Disabling
Interpolation with the flat Qualifier
When a variable is declared using the flat qualifier,
the shader compiler and the hardware bypass this evaluation process:
// Vertex Shader
flat out int vMaterialID;
flat out vec3 vFacetNormal;
// Fragment Shader
flat in int vMaterialID;
flat in vec3 vFacetNormal;The flat keyword signals the graphics driver and
hardware scheduler that no mathematical blending should occur during
rasterization. The rasterizer skips barycentric weighting and
perspective correction for that particular attribute register,
conserving arithmetic logic unit (ALU) cycles during pixel
generation.
The Provoking Vertex Convention
Because no interpolation occurs, the pipeline must choose a single vertex to supply the attribute value for the entire primitive (line or triangle). This chosen vertex is called the provoking vertex.
- Triangles: The value is inherited from one specific
corner of the triangle. By default in OpenGL, this is typically the last
vertex specified (
GL_LAST_VERTEX_CONVENTION), though it can be configured to the first vertex (GL_FIRST_VERTEX_CONVENTION) via the host API usingglProvokingVertex(). - Lines: The value is pulled from either the start or the end vertex of the line segment, depending on the provoking vertex convention.
Every fragment generated during the rasterization of that primitive reads the identical value stored in the output register of that provoking vertex.
Mandatory Use with Integral and Non-Floating Types
The flat qualifier is not only an optimization or
stylistic tool; it is syntactically mandatory for specific data types in
GLSL. Because discrete values cannot be continuously interpolated across
space, the following types must always be qualified with
flat when passed between shader stages:
- Signed and unsigned integers (
int,ivec2,ivec3,ivec4,uint,uvec2,uvec3,uvec4) - Booleans (if passed via custom interfaces)
- Double-precision floating-point types in certain profile configurations
Attempting to pass integral types without the flat
keyword causes a shader compilation error.
Pipeline Benefits
By bypassing raster interpolation, flat outputs provide
two main advantages:
- Precision and Integrity: Data that represents discrete indices (such as material IDs, draw call IDs, or entity identifiers) remains uncorrupted by gradient blending.
- Computational Efficiency: The rasterizer reduces the number of attribute interpolation equations executed per fragment, avoiding unnecessary floating-point operations on data that is constant across the primitive.