How Does EmitVertex Work in GLSL Geometry Shaders?

In the OpenGL Shading Language (GLSL), EmitVertex() is a built-in function that allows geometry shaders to dynamically generate and output vertex data to the graphics pipeline. Unlike vertex shaders, which process a single incoming vertex and produce a single outgoing vertex, geometry shaders operate on entire primitives and can output a variable number of vertices. This makes EmitVertex() essential for procedural geometry generation, particle expansion, billboarding, and custom silhouette extrusion.

The Role of EmitVertex in the Graphics Pipeline

The geometry shader stage resides between primitive assembly (or tessellation evaluation) and rasterization. It takes an entire input primitive—such as a point, line, or triangle—and produces output primitives.

The output primitives are not returned as an array or returned directly from main(). Instead, GLSL employs a stream-based emission model. Every time the shader calls EmitVertex(), the GPU takes the current values of gl_Position and any user-defined out variables, snapshots them, and sends a completed vertex to the current primitive assembly stream.

Output State and Variable Latching

When calling EmitVertex(), all output variables defined in the geometry shader behave like state registers:

Because attribute states persist across calls unless explicitly reassigned, failing to update an output variable before the next EmitVertex() call causes the subsequent vertex to reuse the previous value.

Pairing EmitVertex with EndPrimitive

Geometry shaders typically emit connected topologies using strip layouts, such as points, line_strip, or triangle_strip.

Example GLSL Geometry Shader Implementation

The following example demonstrates how a geometry shader takes a single point primitive and expands it into a screen-aligned quad (two triangles using a triangle_strip):

#version 330 core

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

in VS_OUT {
    vec3 color;
} gs_in[];

out vec3 fColor;

void main() {
    fColor = gs_in[0].color;

    // Bottom-Left
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, -0.1, 0.0, 0.0);
    EmitVertex();

    // Bottom-Right
    gl_Position = gl_in[0].gl_Position + vec4( 0.1, -0.1, 0.0, 0.0);
    EmitVertex();

    // Top-Left
    gl_Position = gl_in[0].gl_Position + vec4(-0.1,  0.1, 0.0, 0.0);
    EmitVertex();

    // Top-Right
    gl_Position = gl_in[0].gl_Position + vec4( 0.1,  0.1, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

In this code, four sequential calls to EmitVertex() generate four vertices that form a quad via triangle_strip. EndPrimitive() closes the quad.

Practical Considerations and Constraints