Geometry Shading

Geometry shaders provide a powerful programmable stage in the DirectX graphics pipeline that allows you to generate new primitives, modify existing primitives, or discard primitives. Unlike vertex shaders, which process vertices individually, geometry shaders operate on entire primitives (points, lines, or triangles) as input and can output a variable number of new primitives.

Overview

The geometry shader stage sits between the vertex shader and the rasterizer. It receives primitive data (e.g., a triangle with its three vertices) from the vertex shader. For each input primitive, the geometry shader can:

This flexibility makes geometry shaders suitable for a variety of advanced rendering techniques.

Key Concepts

Input Primitives

Geometry shaders can accept different types of primitives as input, defined by the primitive topology set for the graphics pipeline. Common input topologies include:

Output Primitives

The geometry shader explicitly declares the type of primitives it will output. This can be different from the input primitive type. Common output topologies include:

Stream Output

Geometry shaders use stream output to emit vertices. The stream output buffer can be used to write generated geometry directly to vertex buffers, allowing for post-processing or further rendering passes without needing to re-process the geometry on the CPU.

Vertex Counts

When outputting primitives, the geometry shader specifies the number of vertices for each output primitive. For example, when outputting a triangle, it will output 3 vertices. The maximum number of output vertices per stream output buffer is limited.

Use Cases

Geometry shaders enable a range of sophisticated visual effects:

Generating Procedural Geometry

Create complex shapes or details on the fly from simpler input. For instance, generating fur strands from a single surface point, or creating particles with complex animations.

Example: Generating Billboards

A common use case is generating billboards. A single point from the vertex shader can be expanded into a quadrilateral (a quad) in the geometry shader, which always faces the camera, regardless of the original point's orientation. This is efficient for rendering sprites or particles.


// Simplified GS Example for Billboards
// Input primitive: Point
// Output primitive: Triangle Strip (a quad)

[maxvertexcount(4)]
void GS(point VertexOutput input[1], inout TriangleStream OutputStream)
{
    // Assume input[0].position contains the world position of the point
    // Assume input[0].texCoord contains the texture coordinate for the point

    // Define quad vertices relative to the input point
    // Calculate offsets based on desired billboard size and camera direction
    float halfSize = 0.5f;
    float3 cameraRight = normalize(cross(input[0].normal, cameraUp)); // Requires cameraUp and normal
    float3 cameraUp = float3(0, 1, 0); // Example camera up vector

    float3 v0 = input[0].position - cameraRight * halfSize + cameraUp * halfSize;
    float3 v1 = input[0].position + cameraRight * halfSize + cameraUp * halfSize;
    float3 v2 = input[0].position - cameraRight * halfSize - cameraUp * halfSize;
    float3 v3 = input[0].position + cameraRight * halfSize - cameraUp * halfSize;

    PixelShaderInput vert;

    // Output first vertex of the triangle strip
    vert.position = mul(worldViewProjectionMatrix, float4(v0, 1.0f));
    vert.texCoord = float2(0.0f, 0.0f); // Adjust tex coords as needed
    OutputStream.Append(vert);

    // Output second vertex
    vert.position = mul(worldViewProjectionMatrix, float4(v1, 1.0f));
    vert.texCoord = float2(0.0f, 1.0f);
    OutputStream.Append(vert);

    // Output third vertex
    vert.position = mul(worldViewProjectionMatrix, float4(v2, 1.0f));
    vert.texCoord = float2(1.0f, 0.0f);
    OutputStream.Append(vert);

    // Output fourth vertex
    vert.position = mul(worldViewProjectionMatrix, float4(v3, 1.0f));
    vert.texCoord = float2(1.0f, 1.0f);
    OutputStream.Append(vert);

    OutputStream.RestartStrip();
}
                

Tessellation Expansion

While modern DirectX (DX11+) features dedicated tessellation shaders, geometry shaders can also be used for simpler forms of geometric subdivision or expansion.

Rendering Fur, Grass, and Hair

Generate multiple strands or blades from a single point, simulating dense foliage or fur.

Generating Shadows

Certain shadow mapping techniques can utilize geometry shaders to efficiently generate shadow volumes or project shadow data.

Instancing Without CPU Involvement

The ability to generate multiple instances of geometry from a single input makes it a candidate for certain instancing-like effects without relying solely on CPU instancing.

Limitations and Considerations

Shader Model Versions

Geometry shaders were introduced with Shader Model 4.0. Subsequent shader models have refined their capabilities and performance.

Conclusion

Geometry shaders are a powerful, albeit sometimes complex, tool in the DirectX graphics pipeline. They offer unprecedented control over primitive generation and manipulation, enabling visually rich and unique rendering effects when used judiciously.