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 using glProvokingVertex().
  • 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:

  1. Precision and Integrity: Data that represents discrete indices (such as material IDs, draw call IDs, or entity identifiers) remains uncorrupted by gradient blending.
  2. 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.