DirectX Computational Graphics

Geometry Shaders

Geometry shaders are a programmable stage in the DirectX graphics pipeline that operates on entire primitives (points, lines, triangles) after they have been processed by the vertex shader. Unlike vertex shaders, which process individual vertices, geometry shaders can take one or more vertices as input and generate new primitives or discard existing ones. This capability allows for dynamic generation of geometry, complex tessellation, and advanced visual effects directly on the GPU.

When to Use Geometry Shaders

Geometry shaders are particularly useful for scenarios such as:

Input and Output Primitives

A geometry shader is defined to accept a specific type of input primitive (e.g., point, line, triangle) and can output one or more primitives of a specific type. The shader code determines how many vertices are emitted for each input primitive and what type of primitive they form.

Input Structure

The input to a geometry shader typically consists of a stream of vertices. Each vertex contains the data that has passed through the vertex shader, such as position, texture coordinates, normals, and any custom vertex attributes.

struct VS_OUTPUT {
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD0;
    float3 Normal   : NORMAL0;
};

Output Structure

The output from a geometry shader is a stream of vertices that form new primitives. The shader must explicitly emit vertices and specify the primitive type (e.g., using the `Append` and `RestartStrip` functions for line strips and triangle strips).

struct GS_OUTPUT {
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD0;
    float3 Normal   : NORMAL0;
};

Geometry Shader Example (Generating Billboards)

This example demonstrates a simple geometry shader that takes a single point from the vertex shader and expands it into a quad (billboard) facing the camera.

struct VS_OUTPUT {
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD0;
};

struct GS_OUTPUT {
    float4 Position : SV_Position;
    float2 TexCoord : TEXCOORD0;
};

[maxvertexcount(4)]
void GS_Billboard(point VS_OUTPUT input[1], inout PrimitiveStream output) {
    // Assume camera is at (0,0,0) and facing positive Z for simplicity
    // In a real scenario, you'd use view matrix to determine billboard orientation

    float2 texCoord = input[0].TexCoord;
    float screenPosZ = input[0].Position.z; // Depth from vertex shader

    // Define the four corners of the quad in screen space relative to the input point
    GS_OUTPUT v;

    // Top-left
    v.Position = float4(input[0].Position.x - 0.1f, input[0].Position.y + 0.1f, screenPosZ, 1.0f);
    v.TexCoord = float2(0.0f, 0.0f);
    output.Append(v);

    // Top-right
    v.Position = float4(input[0].Position.x + 0.1f, input[0].Position.y + 0.1f, screenPosZ, 1.0f);
    v.TexCoord = float2(1.0f, 0.0f);
    output.Append(v);

    // Bottom-left
    v.Position = float4(input[0].Position.x - 0.1f, input[0].Position.y - 0.1f, screenPosZ, 1.0f);
    v.TexCoord = float2(0.0f, 1.0f);
    output.Append(v);

    // Bottom-right
    v.Position = float4(input[0].Position.x + 0.1f, input[0].Position.y - 0.1f, screenPosZ, 1.0f);
    v.TexCoord = float2(1.0f, 1.0f);
    output.Append(v);

    output.RestartStrip(); // Indicate the end of this primitive
}

Performance Considerations

While powerful, geometry shaders can introduce performance overhead. They process entire primitives and can generate a variable number of output primitives, which can make predicting performance challenging for the GPU. It's crucial to use geometry shaders judiciously and profile their impact on your application's frame rate. In many cases, techniques like instancing or compute shaders might offer more efficient alternatives for certain tasks.

Explore Geometry Shader API Reference