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
- Primitive Input/Output: Geometry shaders can take point lists, line lists, triangle lists, or patches as input. They can output point lists, line lists, triangle lists, or even lists of lines or triangles.
- Primitive Generation: A single input primitive can result in zero, one, or many output primitives. This is a core strength for effects like generating billboards from a single point or expanding a line into a ribbon.
- Primitive Instancing: Geometry shaders can be used to efficiently draw multiple instances of the same geometry from a single draw call, often used for rendering large numbers of objects like trees or particles.
- Primitive Reordering and Filtering: They can be used to filter out primitives that don't meet certain criteria or to reorder them for specific rendering passes.
- Shader Model Support: Geometry shaders are supported from Shader Model 4.0 onwards.
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.
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:
- Minimize Vertex Emission: Keep the number of vertices generated per input primitive as low as possible.
- Keep Shaders Simple: Complex computations within the geometry shader can be costly.
- Avoid Excessive Primitive Generation: Generating vast numbers of primitives can overwhelm the downstream stages.
- Compare with Other Techniques: For simple instancing, hardware instancing is often more efficient.
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.