Geometry Shader
The Geometry Shader is an optional stage in the DirectX graphics pipeline that can be used to create or destroy primitives (like points, lines, and triangles) after the vertex shader has processed the geometry. Unlike the vertex shader, which operates on individual vertices, the geometry shader operates on entire primitives and can emit new primitives.
Purpose and Capabilities
- Primitive Generation: Create new geometry on the fly, such as extruding vertices to form triangles or generating billboards from points.
- Primitive Modification: Modify existing primitives. For example, a geometry shader can take a line and emit two triangles to represent a thicker line.
- Primitive Removal: Discard primitives that do not meet certain criteria, effectively culling geometry early in the pipeline.
- Tessellation: While the Hull and Domain Shaders are the primary components for hardware tessellation, geometry shaders can also contribute to generating finer geometry.
How it Works
The geometry shader receives a single primitive (a point, line, or triangle, or potentially a patch for tessellation) as input. It can then iterate over the vertices of this primitive and emit zero or more new primitives. The output can be of a different type than the input. For example, a geometry shader could take a single point and emit a triangle.
Shader Model Versions
Geometry shaders are supported starting with Shader Model 4.0.
Input and Output
Geometry shaders use specific input and output structures to define the data passed between stages. The output primitive type can be specified as part of the shader declaration.
Example Shader Declaration (HLSL)
struct GS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
struct GS_OUTPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
[maxvertexcount(10)] // Example: Maximum 10 vertices can be emitted
void GS_Main( primitive GS_INPUT input[3],
inout stream GS_OUTPUT output [10] )
{
// Shader logic to process input primitive and emit output primitives
// For example, to emit a triangle for each input vertex:
for (uint i = 0; i < input.length(); ++i)
{
output[i].pos = input[i].pos;
output[i].tex = input[i].tex;
}
output.Append(); // Emit the generated primitive
}
Use Cases
- Procedural Geometry Generation: Creating complex shapes like fur, grass, or particle systems dynamically.
- Instancing: Generating multiple copies of an object from a single draw call.
- Shadow Maps: Generating shadow volumes or optimizing shadow map rendering.
- Post-Processing Effects: Applying screen-space effects by generating a fullscreen quad.
Performance Considerations
While powerful, geometry shaders can introduce performance overhead. The number of primitives emitted can significantly impact performance. It's crucial to profile and optimize geometry shader code, limiting the number of emitted vertices and avoiding excessive complexity. In many modern rendering scenarios, tessellation (Hull and Domain Shaders) or compute shaders are preferred for certain tasks that geometry shaders were historically used for.
maxvertexcount attribute to inform the driver of the maximum number of vertices your shader can emit, allowing for better resource management.