Geometry Shading

Manipulating primitives on the GPU

Introduction to Geometry Shading

Geometry shaders are a programmable stage in the DirectX graphics pipeline that sits between the hull/domain shader stages (for tessellation) and the rasterizer. Unlike vertex shaders, which process individual vertices, geometry shaders operate on entire primitives (points, lines, triangles) and can generate new primitives, discard existing ones, or modify vertex data. This allows for powerful techniques like instancing, line-to-triangle conversion, and complex particle system generation directly on the GPU.

Key Features and Capabilities

Shader Model 4.0 and Later

Geometry shaders were introduced with Shader Model 4.0. They are written in High-Level Shading Language (HLSL) and are compiled into bytecode that runs on the GPU. The geometry shader stage is optional and can be skipped if not needed for the rendering effect.

HLSL for Geometry Shaders

A typical geometry shader function in HLSL has a return type specifying the type of primitive it outputs and takes an array of input stream data. The [maxvertexcount(N)] attribute is crucial, indicating the maximum number of vertices the shader can emit for a single input primitive.


struct GS_INPUT {
    float4 pos : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

struct GS_OUTPUT {
    float4 pos : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

[maxvertexcount(8)] // Example: Can output up to 8 vertices per input primitive
void GS_Main(
    triangle GS_INPUT input[3], // Input is a triangle
    inout primitive Stream0 : stream // Output stream
) {
    // ... shader logic to process input[0], input[1], input[2] ...
    // ... emit new vertices using Stream0.Append() ...
    // Stream0.RestartStrip(); // If necessary
}
            

Common Use Cases

1. Generating Billboards

Geometry shaders are excellent for creating billboard effects, where sprites or textures always face the camera. A single point can be expanded into a quad.

Geometry shader expanding a point into a quad for a billboard

Figure 1: Geometry shader transforming a point into a textured quad.

2. Generating Ribbons and Trails

For effects like motion trails or procedural ribbons, a geometry shader can take a few points and generate a strip of triangles forming a continuous ribbon.

3. Line to Triangle Conversion

When rendering thick lines or fuzzy effects, a geometry shader can take a line segment and extrude it into a triangle strip.

4. Instancing without Multiple Draw Calls

While hardware instancing is generally preferred for simple object duplication, geometry shaders offer more flexibility. They can generate the instances' vertex data dynamically based on parameters passed to the shader.

5. Level of Detail (LOD) Generation

A geometry shader could potentially simplify complex geometry on the fly, reducing the number of vertices sent down the pipeline for objects far from the camera.

Performance Considerations

While powerful, geometry shaders can introduce performance overhead. The key is to use them judiciously:

Tip

When geometry shaders are not needed, disabling them or using empty shaders can improve performance by allowing the pipeline to skip this stage.

Example: Simple Line to Triangle Conversion

This simplified example shows how a geometry shader might convert a line segment into a thin triangle strip.


// Vertex shader output
struct VS_OUTPUT {
    float4 pos : SV_POSITION;
    float3 worldPos : WORLD_POSITION;
};

// Geometry shader input (a line strip)
struct GS_INPUT {
    float4 pos : SV_POSITION;
    float3 worldPos : WORLD_POSITION;
};

// Geometry shader output
struct GS_OUTPUT {
    float4 pos : SV_POSITION;
    float3 worldPos : WORLD_POSITION;
};

// Input line is two vertices
[maxvertexcount(2)] // We'll emit two vertices to form a degenerate triangle (line)
void GS_LineToStrip(
    line GS_INPUT input[2],
    inout PrimitiveStream stream
) {
    // For simplicity, this example doesn't add thickness.
    // A real implementation would calculate offset vertices.

    GS_OUTPUT output;

    // Vertex 1
    output.pos = input[0].pos;
    output.worldPos = input[0].worldPos;
    stream.Append(output);

    // Vertex 2
    output.pos = input[1].pos;
    output.worldPos = input[1].worldPos;
    stream.Append(output);

    stream.RestartStrip();
}
            

Note: This is a highly simplified example. A practical implementation for thick lines would require calculating perpendicular vectors and emitting four vertices per input line segment to form a quad.

Important

Geometry shaders add complexity and potential performance bottlenecks. Always profile your application to determine if the benefits outweigh the costs for your specific use case.

Conclusion

Geometry shaders are a powerful and flexible stage in the DirectX pipeline, enabling sophisticated graphical effects that are difficult or impossible to achieve with vertex shaders alone. By understanding their capabilities and limitations, developers can leverage them to create visually rich and dynamic 3D scenes.