Geometry shaders are an optional stage of 3D rendering. A geometry shader program is executed once for each primitive, like a single triangle. It works between the vertex and fragment shader — it knows all the outputs from the vertex shader, and is responsible for passing them to the rasterizer. Geometry shader can use the information about given primitive (vertex positions and attributes) and can generate other primitives. A single geometry shader may generate any number of primitives, so it is possible to “explode” a single input primitive into a number of others. Or we can entirely discard some primitives. The type of the primitive may be changed by the geometry shader — for example, triangles may be converted into points or the other way around.
More details about using the geometry shaders
with X3D and our engine are on http://castle-engine.sourceforge.net/x3d_implementation_shaders.php#section_geometry.
The examples there show how to use the standard
ComposedShader
node to define a geometry shader for the shape.
Here, we investigate how our effects improve the geometry shaders.
When we write a geometry shader, we want to control the logic
of the primitive generation and the output primitive type.
This means that the shader author wants to provide the main part
of the geometry shader, that contains the main()
entry point and necessary layout
declarations.
We want to enable integration of our effects with user geometry shaders.
It must be possible to write a flexible geometry shader code,
that works with any internal effects and user effects
in Effect
nodes. In other words, when writing
the geometry shaders, we do not want to hardcode what values have
to be passed from vertex processor to the rasterizer.
To make this possible, with every geometry shader we will link additional
code with three functions:
void geometryVertexSet(const int index)
— set output vertex to be equal to the input vertex of the given index.void geometryVertexZero()
— set all output vertex attributes to zero. This is really useful only before doing a series ofgeometryVertexAdd
calls.void geometryVertexAdd(const int index, const float scale)
— add to the output vertex given input vertex, scaled.
The idea is that geometryVertexSet
can be used to simply pass-through values from vertex processing to the rasterizer.
Calling geometryVertexSet(i)
is equivalent (but possibly more efficient) to
geometryVertexZero(); geometryVertexAdd(i, 1.0);
For example, this is a trivial pass-through geometry shader,
that doesn't do anything. Thanks to using geometryVertexSet
,
every other effect still works (without geometryVertexSet
,
various effects would break, because values would not be passed from vertex
shaders to fragment shaders). This is what we mean by “robust”
geometry shaders.
effects Effect { language "GLSL" parts EffectPart { type "GEOMETRY" url "data:text/plain, #version 150 layout(triangles) in; layout(triangle_strip, max_vertices = 3) out; void geometryVertexSet(const int index); void main() { for(int i = 0; i < gl_in.length(); i++) { gl_Position = gl_in[i].gl_Position; geometryVertexSet(i); EmitVertex(); } EndPrimitive(); }" } }
For more elaborate cases, geometryVertexAdd
may be used
to blend many input vertexes into one final output vertex.
An example below shows a geometry shader that
replaces every triangle with an averaged single point.
effects Effect { language "GLSL" parts EffectPart { type "GEOMETRY" url "data:text/plain, #version 150 layout(triangles) in; layout(points, max_vertices = 1) out; void geometryVertexZero(); void geometryVertexAdd(const int index, const float scale); void main() { gl_Position = ( gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position ) / 3.0; geometryVertexZero(); geometryVertexAdd(0, 1.0 / 3.0); geometryVertexAdd(1, 1.0 / 3.0); geometryVertexAdd(2, 1.0 / 3.0); EmitVertex(); EndPrimitive(); }" } }
The geometryVertexXxx
functions “magically”
take into account all the attributes that need to be handled for
renderer internal effects. User effects (Effect
nodes)
may have to override corresponding geometry_vertex_xxx
plugs to also be automatically handled this way.