Using a geometry shader

In the rendering pipeline, a geometry shader works between the vertex shader and the fragment shader. It processes the groups of vertices that are organized in primitives. The possible primitives are point (one vertex), line (two vertices), and triangle (three vertices). Also, there are two new primitives, line with adjacent and triangle with adjacent, which represent the line and the triangle with some additional vertices providing adjacency information needed for computing the normals.

The geometry shader gets access to the input positions of the primitive's vertices using a built-in array gl_PositionIn, which holds values of type vec4. These positions are equal to the output values gl_Position generated by the vertex shader.

During its work, processing of the geometry shader should generate a number of output vertices by setting some values to gl_Position, gl_FrontColor, and other variables (similar to the vertex shader), and finally calling the EmitVertex() function. This function tells OpenGL that the geometry shader has finished forming values for the next vertex and rendered it.

The type of primitives rendered by the geometry shader is often different from its input primitives' type. The output type can be a point, a line strip, or a triangle strip. The last two types are strips, and so they can contain a different number of vertices. To denote whether the geometry shader finished the strip primitive, it should call the EndPrimitive() function. Then OpenGL finishes rendering the last primitive and is ready to render a new one.

The "classical usage" of the geometry shader is in smoothing curves and surfaces by subdividing (dividing each line segment or triangle on several primitives of the same type). We will consider an example of more experimental usage of the geometry shader.

The furry carpet example

Let's make a shader that replaces each passed line with a bunch of lines such that these vertices' positions will be distorted using Perlin noise. We obtain a "furry" collection of lines. Additionally, we set the color of each generated line as an average color of a random image along this line. Finally, we will obtain a colored 2D "furry carpet" with a picture resembling the original image.

Note

This is example 08-Shaders/07-GeometryFurryCarpet.

The example is based on the example given in the A simple fragment shader example section. See the entire source code of the project in 08-Shaders/07-GeometryFurryCarpet. Here we note just the example's key points:

A new file, shaderGeom.c, which contains the geometry shader is added there. It takes the vertices' positions, and the input lines are as follows:

vec2 p0 = gl_PositionIn[0].xy;
vec2 p1 = gl_PositionIn[1].xy;

In the next step it generates 50 lines. Namely, it distorts the positions p0 and p1 by Perlin noise and obtains distorted positions q0 and q1. At the next stage, the shader computes the average color of the input image along the line segment [q0, q1]. Finally, it emits two corresponding vertices.

In the example the line of vertex shader code, which computes the output vertex position (gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;) is replaced by trivially passing original position of the output:

gl_Position = gl_Vertex;

The reason is that the geometry shader needs an unchanged vertex position for computing, and finally makes a transformation with gl_ModelViewProjectionMatrix by itself.

The body of the fragment shader's main() function consists of just one line, which only passes the input color:

gl_FragColor = gl_Color;

In the openFrameworks's project, one line of the setup() function which loads the shader is replaced with the following code:

shader.setGeometryInputType(GL_LINES);
shader.setGeometryOutputType(GL_LINE_STRIP);
shader.setGeometryOutputCount(128);
shader.load( "shaderVert.c", "shaderFrag.c", "shaderGeom.c" );

The first three lines set the geometry shader's parameters—its input and output primitive types—and also the maximum possible number of output vertices. You need to specify such parameters for the geometry shader before loading this. The shader.load() function here contains three parameters with filenames for the vertex, fragment, and geometry shader.

Instead of using the sunflower.png image, we are using the version with a transparent background, sunflower-transp.png.

Finally, in the draw() function, we enable shader and draw a number of vertical lines. So each line passes through the vertex, geometry, and fragment shaders. During processing by the geometry shader, the line is replaced with 50 lines that are actually rendered on the screen. The important point here is that we do some optimization, and do not render lines that lie in the transparent background.

Running the project, you will see a slow moving carpet resembling the original sunflower image, as shown in the following screenshot:

The furry carpet example
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset