Mesh Shader Output to Fragment Shader Input in GLSL
This article explains how GLSL mesh shaders route per-vertex output
attributes to fragment shader location bindings. In modern GPU pipelines
using GL_EXT_mesh_shader, mesh shaders replace traditional
vertex and geometry stages by explicitly generating primitives and
vertex data. By declaring per-vertex output blocks with specified
location qualifiers and writing vertex topology through primitive index
arrays, the hardware rasterizer interpolates these outputs and binds
them seamlessly to matching fragment shader input locations.
Declaring Per-Vertex Outputs in the Mesh Shader
In GLSL mesh shaders, per-vertex outputs are defined using an
interface block or individual variables qualified with the
out vertices storage qualifier. To route data predictably
to the fragment stage, each attribute is assigned an explicit index
using layout(location = N).
#version 460
#extension GL_EXT_mesh_shader : require
layout(local_size_x = 32) in;
layout(triangles, max_vertices = 64, max_primitives = 126) out;
// Explicit location assignment on a per-vertex interface block
layout(location = 0) out VertexOutput {
vec3 normal;
vec2 uv;
} v_out[];In this setup, v_out[] is an array sized by
max_vertices. The attribute normal consumes
location = 0, while uv automatically
increments to location = 1 unless explicitly assigned
otherwise.
Populating the Outputs and Defining Topology
Mesh shaders decouple vertex generation from primitive connectivity.
You must set the actual output counts at runtime via
SetMeshOutputsEXT(numVertices, numPrimitives). Mesh shader
invocations then write directly to individual vertex indices within the
output arrays:
uint vID = gl_LocalInvocationIndex;
if (vID < numVertices) {
gl_MeshVerticesEXT[vID].gl_Position = transform * vec4(inPositions[vID], 1.0);
v_out[vID].normal = inNormals[vID];
v_out[vID].uv = inUVs[vID];
}To form geometry, primitive indices are populated using the built-in
index array (such as gl_PrimitiveTriangleIndicesEXT):
if (gl_LocalInvocationIndex < numPrimitives) {
gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(i0, i1, i2);
}Rasterization and Interpolation
The hardware rasterizer reads the indices stored in
gl_PrimitiveTriangleIndicesEXT to gather vertex positions
from gl_MeshVerticesEXT and corresponding attributes from
v_out[].
For each fragment covered by a generated primitive, the rasterizer
performs perspective-correct interpolation on the attributes defined at
v_out[i0], v_out[i1], and
v_out[i2]. Unless qualified with the flat
interpolation modifier, floating-point vectors are interpolated across
the face of the primitive according to the fragment's barycentric
coordinates.
Binding to Fragment Shader Inputs
The fragment shader receives the interpolated attributes by declaring
input variables with matching location qualifiers. The variable names in
the fragment shader do not need to match those in the mesh shader; the
linkage is determined solely by the location layout:
#version 460
layout(location = 0) in vec3 inNormal;
layout(location = 1) in vec2 inUV;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(inNormal * 0.5 + 0.5, 1.0);
}Because inNormal is bound to location = 0,
it automatically receives the interpolated values of
v_out[].normal. Similarly, inUV maps directly
to location = 1.
Key Rules for Mesh-to-Fragment Routing
- Location Matching: The layout
location = Nin the fragment shader must match the base location assigned to the corresponding attribute in the mesh shader. - Type and Qualifier Matching: The data type and
interpolation qualifiers (such as
flat,noperspective, orcentroid) must match between the mesh shader's per-vertex output and the fragment shader's input. - Distinction from Per-Primitive Data: Mesh shaders
can also emit per-primitive attributes using the
perprimitiveEXTqualifier. Per-primitive attributes also uselayout(location = N), but their location namespace is shared with per-vertex attributes. You cannot assign the same location number to both a per-vertex and a per-primitive output within the same pipeline.