How it works...

The vertex shader is a simple passthrough shader. It converts the vertex position and normal to camera coordinates and sends them along, via VPosition and VNormal. These will be used for shading within the fragment shader and will be passed along (or ignored) by the geometry shader. The position is also converted to clip coordinates (or normalized device coordinates) by transforming with the model-view projection matrix, and it is then assigned to the built-in gl_Position.

The geometry shader begins by defining the input and output primitive types using the layout directive:

layout( triangles_adjacency ) in; 
layout( triangle_strip, max_vertices = 15 ) out;

This indicates that the input primitive type is triangles with adjacency information, and the output type is triangle strips. This geometry shader will produce a single triangle (the original triangle) and at most one quad for each edge. This corresponds to a maximum of 15 vertices that could be produced, and we indicate that maximum within the output layout directive.

The GIsEdge output variable is used to indicate to the fragment shader whether or not the polygon is an edge quad. The fragment shader will use this value to determine whether to shade the polygon. There is no need to interpolate the value and since it is a Boolean, interpolation doesn't quite make sense, so we use the flat qualifier.

The first few lines within the main function take the position for each of the six vertices (in clip coordinates) and divide it by the fourth coordinate in order to convert it from its homogeneous representation to the true Cartesian value. This is necessary if we are using a perspective projection, but is not necessary for orthographic projections.

Next, we determine whether the main triangle (defined by points 0, 2, and 4) is front-facing. The isFrontFacing function returns whether the triangle defined by its three parameters is front-facing using the equation described previously. If the main triangle is front-facing, we will emit a silhouette edge quad only if the adjacent triangle is not front-facing.

The emitEdgeQuad function produces a quad that is aligned with an edge defined by the e0 and e1 points. It begins by computing ext, which is the vector from e0 to e1, scaled by PctExtend (in order to slightly lengthen the edge quad). We lengthen the edge quad in order to cover gaps that may appear between quads (we'll discuss this further in There's more...).

Note also that we drop the z coordinate here. As the points are defined in clip coordinates, and we are going to produce a quad that is aligned with the x-y plane (facing the camera), we want to compute the positions of the vertices by translating within the x-y plane. Therefore we can ignore the z coordinate for now. We'll use its value unchanged in the final position of each vertex.

Next, the v variable is assigned to the normalized vector from e0 to e1. The n variable gets a vector that is perpendicular to v (in 2D, this can be achieved by swapping the x and y coordinates and negating the new x coordinate). This is just a counter-clockwise 90-degree rotation in 2D. We scale the n vector by EdgeWidth because we want the length of the vector to be the same as the width of the quad. The ext and n vectors will be used to determine the vertices of the quad, as shown in the following diagram:

The four corners of the quad are given by e0 - ext, e0 - n - ext, e1 + ext, and e1 - n + ext. The z coordinate for the lower two vertices is the same as the z coordinate for e0, and the z coordinate for the upper two vertices is the z coordinate for e1.

We then finish up the emitEdgeQuad function by setting GIsEdge to true in order to let the fragment shader know that we are rendering a silhouette edge, and then emitting the four vertices of the quad. The function ends with a call to EndPrimitive to terminate the processing of the triangle strip for the quad.

Back within the main function, after producing the silhouette edges, we proceed by emitting the original triangle unchanged. VNormal, VPosition, and gl_Position for vertices 0, 2, and 4 are passed along without any modification to the fragment shader. Each vertex is emitted with a call to EmitVertex, and the primitive is completed with EndPrimitive.

Within the fragment shader, we either shade the fragment (using the toon shading algorithm), or simply give the fragment a constant color. The GIsEdge input variable will indicate which option to choose. If GIsEdge is true, then we are rendering a silhouette edge so the fragment is given the line color. Otherwise, we are rendering a mesh polygon, so we shade the fragment using the toon shading technique from Chapter 4, Lighting and Shading.

..................Content has been hidden....................

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