Passing Data to the Vertex Shader
Vertex Attributes
At the start of the OpenGL pipeline,we use the in keyword to bring inputs into the vertex shader.
Between stages,in and out can be used to form conduits from shader to shader and pass data
between them.
For now, consider the input to the vertex shader and what happens if you declare a variable with an in storage qualifier. This marks the variable as an input to the vertex shader, which means that it is automatically filled in by the fixed-function vertex fetch stage. The variable becomes known as a vertex attribute.
Vertex attributes are how vertex data is introduced into the OpenGL pipeline. To declare a vertex attribute, declare a variable in the vertex shader using the in storage qualifier.
1 #version 430 core
2 // "offset" is an input vertex attribute
3 layout (location = 0) in vec4 offset;
4 void main(void)
5 {
6 const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0),
7 vec4(-0.25, -0.25, 0.5, 1.0),
8 vec4( 0.25, 0.25, 0.5, 1.0));
9 // Add "offset" to our hard-coded vertex position
10 gl_Position = vertices[gl_VertexID] + offset;
11 }
We can tell this stage what to fill the variable
with by using one of the many variants of the vertex attribute functions,
glVertexAttrib*(). The prototype for glVertexAttrib4fv(),
which we use in
this example, is
void glVertexAttrib4fv(GLuint index, const GLfloat * v);
index is used to reference the attribute and v is a pointer to the new data to put into the attribute.
The layout (location = 0) code in the declaration of the offset attribute.This is a layout qualifier, and we have used it to set the location of the vertex attribute to zero. This location is the value we’ll pass in index to refer to the attribute.
Passing Data from Stage to Stage
built-in variables such as gl_VertexID and gl_Position.
We use in and out to pass data from a stage to another.
Interface Blocks
Declare interface variables to communicate a number of different pieces of data between stages, and these may include arrays, structures, and other complex arrangements of variables.To achieve this, we can group together a number of variables into an interface block. The declaration of an interface block looks a lot like a structure declaration, except that it is declared using the in or out keyword depending on whether it is an input to or output from the shader.
Interface blocks are matched between stages using the block name (VS_OUT in this case), but are referenced in shaders using the instance name.
Matching interface blocks by block name but allowing block instances to have different names in each shader stage serves two important purposes:
First, it allows the name by which you refer to the block to be different in each stage, avoiding confusing things such as having to use vs_out in a fragment shader;
Second, it allows interfaces to go from being single items to arrays when crossing between certain shader stages, such as the vertex and tessellation or geometry shader stages as we will see in a short while.
Note that interface blocks are only for moving data from shader stage to shader stage — you can’t use them to group together inputs to the vertex shader or outputs from the fragment shader.
Tessellation
Tessellation is the process of breaking a high-order primitive (which is known as a patch in OpenGL) into many smaller, simpler primitives such as triangles for rendering.
OpenGL includes a fixed-function, configurable tessellation engine that is able to break up quadrilaterals, triangles, and lines into a potentially large number of smaller points, lines, or triangles that can be directly consumed by the normal rasterization hardware further down the pipeline.
Logically, the tessellation phase sits directly after the vertex shading stage in the OpenGL pipeline and is made up of three parts: the tessellation control shader, the fixed-function tessellation engine, and the tessellation evaluation shader.
Tessellation Control Shaders
Each patch is formed from a number of control points. The number of control points per patch is configurable and set by calling glPatchParameteri() with pname set to GL_PATCH_VERTICES and value set to the number of control points that will be used to construct each patch. The prototype of glPatchParameteri() is
void glPatchParameteri(GLenum pname, GLint value);
By default, the number of control points per patch is three.
If this is what you want (as in our example application), you don’t need to call it at all.
The vertex shader runs once per control point whilst the tessellation control shader runs in batches on groups of control points where the size of each batch is the same as the number of vertices per patch.
Vertices are used as control points, and the result of the vertex shader is passed in batches to the tessellation control shader as its input. The number of control points per patch can be changed such that the number of control points that is output by the tessellation control shader can be different from the number of control points that it consumes. The number of control points produced by the control shader is set using an output layout qualifier in the control shader’s source code. Such a layout qualifier looks like: layout (vertices = N) out; Here, N is the number of control points per patch.
The control shader is responsible for calculating the values of the output control points and for setting the tessellation factors for the resulting patch that will be sent to the fixed-function tessellation engine. The output tessellation factors are written to the gl_TessLevelInner and gl_TessLevelOuter built-in output variables, whereas any other data that is passed down the pipeline is written to user-defined output variables (those declared using the out keyword, or the special built-in gl_out array) as normal.
Listing 3.7 shows a simple tessellation control shader. It sets the number of output control points to three (the same as the default number of input control points) using the layout (vertices = 3) out; layout qualifier, copies its input to its output (using the built-in variables gl_in and gl_out), and sets the inner and outer tessellation level to 5. The built-in input variable gl_InvocationID is used to index into the gl_in and gl_out arrays. This variable contains the zero-based index of the control point within the patch being processed by the current invocation of the tessellation control shader:
1 #version 430 core
2
3 ayout (vertices = 3) out;
4
5 void main(void)
6
7 {
8
9 if (gl_InvocationID == 0)
10
11 {
12 gl_TessLevelInner[0] = 5.0;
13 gl_TessLevelOuter[0] = 5.0;
14 gl_TessLevelOuter[1] = 5.0;
15 gl_TessLevelOuter[2] = 5.0;
16 }
17
18 gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
19
20 }
The tessellation engine
The tessellation engine is a fixed-function part of the OpenGL pipeline that takes high-order surfaces represented as patches and breaks them down into simpler primitives such as points, lines, or triangles. Before the tessellation engine receives a patch, the tessellation control shader processes the incoming control points and sets tessellation factors that are used to break down the patch. After the tessellation engine produces the output primitives, the vertices representing them are picked up by the tessellation evaluation shader. The tessellation engine is responsible for producing the parameters that are fed to the invocations of the tessellation evaluation shader, which it then uses to transform the resulting primitives and get them ready for rasterization.
Tessellation Evaluation Shaders
【To be continue...】