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:
- Output zero primitives (effectively culling the primitive).
- Output one or more new primitives. This can include replicating the input primitive, generating entirely new geometry, or expanding a single point into a more complex shape.
- Output the same primitive unchanged.
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:
D3D10_PRIMITIVE_TOPOLOGY_POINTLISTD3D10_PRIMITIVE_TOPOLOGY_LINELISTD3D10_PRIMITIVE_TOPOLOGY_TRIANGLELISTD3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ(Line List with adjacency)D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ(Triangle List with adjacency)
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:
D3D10_PRIMITIVE_TOPOLOGY_POINTLISTD3D10_PRIMITIVE_TOPOLOGY_LINELISTD3D10_PRIMITIVE_TOPOLOGY_TRIANGLELISTD3D10_PRIMITIVE_TOPOLOGY_LINESTRIPD3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
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
- Performance: Geometry shaders can be computationally expensive. Overuse or inefficient implementation can significantly impact frame rates.
- Complexity: Writing and debugging geometry shaders can be more complex than vertex or pixel shaders due to the primitive-level operations and variable output counts.
- Hardware Support: While widely supported, older hardware might have limited capabilities or performance characteristics for geometry shaders.
- Alternatives: For many tasks, especially with newer DirectX versions, compute shaders or dedicated tessellation stages might offer more efficient or specialized solutions.
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.