Computer graphics and opengl C++ version study notes Chapter 13 Geometry Shader

In the OpenGL pipeline, the tessellation stage is followed by the geometry stage. At this stage, the programmer has the option to include geometry shaders. This stage actually existed before the tessellation stage, which became part of the OpenGL core in version 3.2 (2009).

Like tessellation, geometry shaders enable programmers to manipulate groups of vertices in ways not possible in vertex shaders. In some cases, the same task can be accomplished using a tessellation shader or a geometry shader, since their functionality overlaps in some ways.

13.1 Primitive-by-primitive processing in OpenGL

The geometry shader stage is located between tessellation and rasterization, within the segment of the pipeline used for primitive processing (see Figure 2.2). Vertex shaders allow you to operate on one vertex at a time, while fragment shaders can operate on one fragment (actually one pixel) at a time, but geometry shaders can operate on one primitive at a time.

Recall that primitives are the basic components of drawing objects in OpenGL. There are only a few types of primitives; we will mainly focus on geometry shaders that manipulate triangle primitives. So when we say that a geometry shader can operate on one primitive at a time, we usually mean that the shader can access 3 vertices of a triangle at a time. The geometry shader allows access to all vertices in the primitive at once, then:

  • Outputting the same primitives remains unchanged;
  • Output primitives of the same type with modified vertex positions;
  • Output different types of primitives;
  • Output more other primitives;
  • Remove primitives (do not export at all).

Similar to the tessellation evaluation shader, the incoming vertex attributes can be accessed as an array in the geometry shader. However, in the geometry shader, the incoming attribute array is only indexed up to the size of the primitive. For example, if the primitive is a triangle, the available indices are 0, 1, 2. Access the vertex data itself using a predefined arraygl_in as shown below.

gl_in[2].gl_Position // 第三个顶点的位置

Similar to the tessellation evaluation shader, the vertex attributes output by the geometry shader are all scalars. That is, the output is a stream of the individual vertices that form the primitive (their positions and other attribute variables, if any).

has a layout modifier that sets the primitive input/output type and output size. The special GLSL commandEmitVertex() specifies that a vertex is to be output. The special GLSL commandEndPrimitive() indicates that a specific primitive is constructed.

There is a built-in variablegl_PrimitiveIDIn, which saves the ID of the current primitive. IDs start at 0 and count until the total number of primitives is minus 1.

We'll explore four common types of operations:

  • Modify graphics elements;
  • Delete elements;
  • Add graphics elements;
  • Change the element type.

13.2 Modify graphics elements

Geometry shaders are convenient when a change in the shape of an object can be affected by a single change to a primitive (usually a triangle).

For example, consider the torus we presented earlier in Figure 7.12. Suppose the torus represents the space inside it (for example when representing a tire) and we want to "inflate" it. Simply applying a scaling factor in C++/OpenGL code will not achieve this, as its basic shape will not change. To give it an "inflated" appearance, you also need to make the inner hole smaller as the torus extends into the empty center space.

One way to solve this problem is to add the surface normal vector to each vertex. Although this can be done in a vertex shader, we practice in a geometry shader. Program 13.1 shows the code for the GLSL geometry shader. Other modules are the same as Program 7.3, with a few minor changes: fragment shader input names now need to reflect the output of the geometry shader (e.g., varyingNormal becomes varyingNormalG), and C++/OpenGL applications need to compile the geometry shader and convert it before linking Attached to the shader program. The new shader is designated as a geometry shader as shown below.

GLuint gShader = glCreateShader(GL_GEOMETRY_SHADER);

Program 13.1 Geometry Shader: Modifying Vertices
Insert image description here

Note in Program 13.1 that the input variables corresponding to the vertex shader's output variables are declared as arrays. This provides the programmer with a mechanism to access each vertex in a triangle primitive and its attributes using indices 0, 1, and 2. We want to move these vertices outward along their surface normal vectors. In the vertex shader, both vertices and normal vectors have been converted to view space. We add a small portion of the normal vector to each incoming vertex bit (gl_in[i].gl_Position) and then apply the projection matrix to the result, producing each output gl_Position.

Insert image description here

Figure 13.1 “Inflated” torus, vertices modified by geometry shader

It is worth noting that GLSL callsEmitVertex() are used to specify when we have finished computing the outputgl_Position and its associated vertex attributes and are ready to output the vertex . EndPrimitive()The call specifies that we have finished defining the set of vertices that make up the primitive (in this case, the triangle). The results are shown in Figure 13.1.

The geometry shader includes two layout qualifiers. The first specifies the input primitive type and must be compatible with the primitive type in the C++ side glDrawArrays() or glDrawElements() call. The options are shown in Table 13.1.

Table 13.1 Options for primitive input types

Insert image description here
The various OpenGL primitive types (including the "strip" and "fan" types) are covered in Chapter 4. The "adjacent" type is used in OpenGL for use with geometry shaders, and they can access vertices adjacent to primitives. We don't use them in this book, but they are listed for completeness.

The output primitive type must be points, line_strip, or triangle_strip. Note that the output layout qualifier also specifies the maximum number of vertices the shader outputs per call.

This specific change to the torus can be made easier in the vertex shader. However, suppose that instead of moving each vertex outward along its own surface normal vector, you wish to move each triangle outward along its surface normal vector, effectively "exploding" the torus' constituent triangles outward. The vertex shader cannot do this because the normal vector of the triangle is calculated

The vertex normal vectors of the 3 triangle vertices need to be averaged, and the vertex shader can only access the vertex attributes of one vertex in the triangle at a time. However, we can do this in the geometry shader because the geometry shader has access to all 3 vertices in each triangle. We calculate the triangle's surface normal vector by averaging their normal vectors, and then add this average normal vector to each vertex in the triangle primitive. Figure 13.2, Figure 13.3 and Figure 13.4 show the average surface normal vector, the modified geometry shader main() code and the output results respectively.
Insert image description here

Figure 13.2 Applying average triangular surface normal vectors to triangle vertices

Insert image description here

Figure 13.3 Modified geometry shader for "exploding" torus

Insert image description here

Figure 13.4 “Exploded” torus

The appearance of an "exploded" torus can be improved by ensuring that the interior of the torus is also visible (normally these triangles would be culled by OpenGL since they are the "back"). One solution is to have the torus be rendered twice, once in the normal way and once with the wrapping order reversed (reversing the wrapping order effectively switches which faces face forward and which face behind). We also send a flag to the shader (via the uniform variable) to disable diffuse and specular lighting on the back-facing triangles to make them less prominent. The code changes are as follows.

Modifications to the display() function:

Insert image description here

Modifications to fragment shader:

Insert image description here
The resulting "exploded" torus, including the back side, is shown in Figure 13.5.

Insert image description here

Figure 13.5 “Exploded” torus, including back side

13.3 Delete primitives

A common use of geometry shaders is to build rich decorative objects from simple objects by rationally removing some primitives. For example, removing some triangles from our torus can turn it into a complex lattice structure that would be much more difficult to model from scratch. The geometry shader that does this is shown in Program 13.2, and the output is shown in Figure 13.6.
Insert image description here

Figure 13.6 Geometry shader: deleting primitives

Program 13.2 Geometry Shader: Removing Primitives
Insert image description here
No other changes to the code are required. Please note that the mod function is used here - all vertices are passed except for the first vertex of every 3 primitives which is ignored. Here, rendering back-facing triangles can also improve realism, as shown in Figure 13.7.
Insert image description here

Figure 13.7 Showing element deletion on the back side

13.4 Add graphics elements

Perhaps the most interesting and useful use of geometry shaders is to add additional vertices and/or primitives to the model being rendered. This makes it possible to do things like increase detail in the object to improve the height map, or completely change the shape of the object.

Consider the following example where we change each triangle in the torus into a tiny pyramid of triangles.

Our strategy is similar to our previous "exploding" torus example, shown in Figure 13.8. The vertices of the triangle primitive passed in are used to define the base of the pyramid. The walls of the pyramid are made up of those vertices and new points (called "peak points") calculated by averaging the normal vectors of the original vertices. The new normal vector for each of the 3 "sides" of the pyramid is then calculated by the cross product of the two vectors from the peak point to the base.
Insert image description here

Figure 13.8 Converting a triangle to a pyramid

The geometry shader in Program 13.3 does this for each triangle primitive in the torus. For each input triangle, it outputs 3 triangle primitives, for a total of 9 vertices. Each new triangle is constructed in function makeNewTriangle(), which is called 3 times. It computes the normal vector of the specified triangle and then calls the functionsetOutputValues()to assign the appropriate output vertex attribute to each vertex emitted. After emitting all 3 vertices, it calls EndPrimitive(). To ensure that lighting is performed accurately, a new value for the lighting direction vector is calculated for each newly created vertex.

Program 13.3 Geometry Shader: Adding Primitives
Insert image description here

The resulting output is shown in Figure 13.9. If the spike length (sLen) variable is increased, the added surface "pyramid" will be taller. However, without shadows, they may not look realistic. Adding shadow maps to Program 13.3 is left as an exercise.

Insert image description here

Figure 13.9 Geometry shader: adding primitives

Careful application of this technique can simulate spikes, thorns, and other fine surface protrusions, or reverse indentations, dimples (References [DV14, TR13, KS16]), etc.

13.5 Changing element type

OpenGL allows changing primitive types in geometry shaders. A common use for this feature is to convert an input triangle into one or more output line segments to simulate fur or hair. While generating convincing hair remains one of the more difficult real-world projects, geometry shaders can help achieve real-time rendering in many situations.

Program 13.4 shows a geometry shader that converts each input 3-vertex triangle into an outward 2-vertex line segment. It first calculates the starting point of the hair bundle by averaging the triangle vertex positions to generate the centroid of the triangle. It then uses the same "peak point" as in Program 13.3 as the end point of the hair. The output primitive is specified as a line segment with two vertices, the first vertex being the start point and the second vertex being the end point. The results are shown in Figure 13.10 for instantiating a torus of dimension 72 slices.

Insert image description here

Figure 13.10 Changing triangle primitives to line primitives

Of course, this is just the starting point for producing fully realistic hair. Making the hair bend or move will require several modifications, such as generating more vertices for the line and calculating their position along the curve and/or incorporating randomness. Since the line segment has no obvious surface normal, lighting can be complicated; in this example, we simply specify that the normal vector is the same as the original triangle's surface normal.

Program 13.4 Geometry Shader: Changing Primitive Type

Insert image description here

Additional information

One of the attractions of geometry shaders is that they are relatively easy to use. While many applications of geometry shaders can be implemented using tessellation, the mechanics of geometry shaders generally make them easier to implement and debug. Of course, the relative suitability of geometry versus tessellation depends on the specific application.

Generating convincingly realistic hair or fur is challenging and requires a variety of techniques depending on the application scenario. In some cases, a simple texture is sufficient, or tessellation or geometry shaders can be used, such as the basic techniques shown in this chapter. Movement (animation) and lighting become tricky when more realistic effects are required. Two specialized tools for hair and fur generation are HairWorks and TressFX. HairWorks is part of the NVIDIA GameWorks suite [GW18], while TressFX is developed by AMD [TR18]. The former works with both OpenGL and DirectX, while the latter only works with DirectX. Examples of using TressFX can be found in [GP14].

Guess you like

Origin blog.csdn.net/weixin_44848751/article/details/131198434