Geometry Shading
Geometry Shading is a programmable stage in the graphics pipeline that allows you to generate or modify primitives (points, lines, triangles) after the vertex shader but before the rasterizer. This stage provides significant flexibility for tasks such as expanding points into billboards, tessellating primitives, or generating entirely new geometry on the fly.
Core Concepts
- Input Primitives: Geometry shaders receive entire primitives (points, lines, or triangles) as input, along with their associated vertex data.
- Output Primitives: The geometry shader can output zero or more primitives of the same type or a different type than the input. This allows for the creation of new geometry.
- Generators: Special constructs within the geometry shader allow for the generation of new vertices that form new primitives.
- Stream Output: Data can be output to streams, which can be further processed by subsequent stages or written to buffers.
- Per-Vertex vs. Per-Primitive: Unlike vertex shaders which operate on individual vertices, geometry shaders operate on entire primitives.
Key Features and Use Cases
- Tessellation: Dynamically subdividing primitives to add more detail to surfaces.
- Billboarding: Generating quads (or other primitives) from points, which always face the camera.
- Generating Particles: Creating particles by expanding a single point into a more complex mesh.
- Procedural Geometry: Generating complex shapes and structures directly on the GPU.
- Simplification: Reducing the complexity of geometry by merging vertices or primitives.
Example: Expanding Points into Triangles
The following is a simplified example of a geometry shader that takes a single point as input and outputs a small triangle:
struct VertexIn {
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
struct GS_OUTPUT {
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
[maxvertexcount(3)]
void GS_main(point VertexIn input[1],
out PointStream<GS_OUTPUT> stream)
{
// Input is a single point. We will output a triangle.
GS_OUTPUT output;
// Define the vertices of the triangle relative to the input point
float4 center = input[0].pos;
float2 offset1 = float2(-0.1, -0.1);
float2 offset2 = float2( 0.1, -0.1);
float2 offset3 = float2( 0.0, 0.1);
// Vertex 1
output.pos = center + float4(offset1, 0, 0);
output.tex = input[0].tex; // Pass through texture coordinates
stream.Append(output);
// Vertex 2
output.pos = center + float4(offset2, 0, 0);
output.tex = input[0].tex;
stream.Append(output);
// Vertex 3
output.pos = center + float4(offset3, 0, 0);
output.tex = input[0].tex;
stream.Append(output);
stream.RestartStrip(); // Optional: for restarting primitive generation
}
Performance Considerations
While powerful, geometry shaders can introduce performance overhead. Carefully consider whether the benefits outweigh the potential performance impact. Complex geometry generation or large numbers of output primitives can be costly. For many common tasks like tessellation, dedicated hardware tessellation stages might offer better performance.