opengl superbible 5 edition chapter 6——Thinking Outside the Box: Nonstock Shaders

In Chapter 3, “Basic Rendering,” you were first introduced to shaders and how to use them. If you skipped it to get right into shader programming, you really need to review that chapter first and make sure you understand how we use attributes and uniforms, and how we pass them into a shader from our client-side code. In that chapter we focused our attention entirely on the client side of things, and we used some prebuilt stock shaders that performed some routine and typical rendering operations. In this chapter, we go a bit deeper into the client side of things, but we are finally going to see how to write our own shaders, the server side of using shaders: shader programming and the shading language.

GLSL 101
The OpenGL Shading Language (GLSL) is a C-like high-level language that is compiled and linked by your OpenGL implementation and (usually) runs entirely on the graphics hardware. Shader programs look a lot like C, they start with the main function as their entry point, and you can write functions in GLSL that accept parameters and return values. Figure 3.1 from Chapter 3 is repeated here as Figure 6.1 and shows our basic shader architecture.

在这里插入图片描述
FIGURE 6.1 Our basic shader architecture.

As shown here, we need at a minimum two shaders: a vertex shader and a fragment shader. An optional third shader stage called a geometry shader is covered in more detail in Chapter 11, “Advanced Shader Usage.” You submit data to the vertex shader in one of
three different ways: attributes, which is a per vertex data item; uniforms, which are constant (thus uniform) for the entire batch of vertex data; and finally in Chapter 5, “Basic Texturing,” you learned to load and use texture data as well. You can also set uniforms and
texture data for your fragment shader. It doesn’t make any sense to send vertex attributes to the fragment shader, because the fragment shader is only concerned with filling in the fragments (pixels basically) as the primitive is rasterized. Per vertex data, however, can be
passed on to the fragment shader by the vertex program. In this case, however, the data may be constant (every fragment sees the same value), or the values may be interpolated in different ways across the surface of the primitive.

Shader programs look a lot like C programs; they start with the function main and use the same character set and commenting conventions and many of the same preprocessor directives. A complete language specification can be found in the OpenGL Shading
Language Specification. Appendix A, “Further Reading,” has some Web pointers to help you find this document as well as other good references and supplemental tutorials. For our purposes, we are going to make what should be a safe assumption, which is that you
already are familiar with C/C++, and thus we focus on GLSL from a C/C++ programmer’s perspective.

Variables and Data Types
A good place to start for learning GLSL is to discuss the data types available to you. There are only four: integers (both signed and unsigned), floats (single precision only as of OpenGL 3.3), and Booleans (bool). There are no pointers in GLSL, and there are no strings
or characters of any kind. opengl中没有指针,字符串
Functions can return any of these data types but can also be declared as void, but again, no void pointers allowed. The use of these data types in GLSL mirrors their usage in C/C++.

bool bDone = false; // Boolean true or false
int iValue = 42; // Signed integer
uint uiValue = 3929u; // unsigned integer
float fValue = 42.0f; // Floating point value

Vector Types
An exciting and unique feature of GLSL (as compared to C/C++) is the availability of vector data types. All four of the basic data types can be stored in two-, three-, or fourdimensional vectors. The complete list of vector data types is listed in Table 6.1.
在这里插入图片描述

A vector data type can be declared just like any other kind of variable; for example, you would declare a vertex position as a four-component floating-point vector like this:

vec4 vVertexPos;

You can also initialize a vector with a constructor:

vec4 vVertexPos = vec4(39.0f, 10.0f, 0.0f, 1.0f);

This should not be confused with C++ class constructors. GLSL vector data types are not classes; they are their own built-in data type. Vectors can be assigned to one another, added together, scaled by a scalar (nonvector type), and so on.

vVertexPos = vOldPos + vOffset;
vVertexPos = vNewPos;
vVertexPos += vec4(1.0f, 1.0f, 0.0f, 0.0f);
vVertexPos *= 5.0f;

Another unique feature to GLSL is how we can address individual elements of a vector. If you are familiar with the union construct from C/C++, vectors are like unions on steroids.
We use the dot notation to address up to four vector elements, but we can use any of the following three sets of identifiers: xyzw, rgba, or stpq. Typically we would use the xyzw set of identifiers when referring to vertex type data.

vVertexPos.x = 3.0f;
vVertexPos.xy = vec2(3.0f, 5.0f);
vVertexPos.xyz = vNewPos.xyz;

Then rgba when doing color work.

vOutputColor.r = 1.0f;
vOutputColor.rgba = vec4(1.0f, 1.0f, 0.5f, 1.0f);

And finally, when working with texture coordinates, stpq.

vTexCoord.st = vec2(1.0f, 0.0f);

The choice of which set of identifiers you use is completely arbitrary as far as GLSL is concerned; for example, you could easily do something like this:

vTexCoord.st = vVertex.st;

However, what you cannot do is mix the different groups within a single vector access, such as this:

vTexCoord.st = vVertex.xt; // mixing of x and t is not allowed!

Vector data types also support swizzling. A swizzle is when you swap two or more vector elements. For example, if you were converting color data from RGB ordering to BGR ordering, the following line of code would do the trick:

vNewColor.bgra = vOldColor.rgba;

Vector data types are not only native to GLSL, they are native to the hardware. They are fast, and operations are performed on all the components at once. For example, the following operation

vVertex.x = vOtherVertex.x + 5.0f;
vVertex.y = vOtherVertex.y + 4.0f;
vVertex.z = vOtherVertex.z + 1.0f;

would execute much faster if you instead use the native vector notation:

vVertex.xyz = vOtherVertex.xyz + vec3(5.0f, 4.0f, 1.0f);

Matrix Types
In addition to the vector data types, GLSL supports a number of matrix types. Unlike the vector types, however, the matrix types are all floating-point only—sorry, no integer or Boolean matrices, as these are not practically useful. Table 6.2 lists the supported matrix types.
在这里插入图片描述

A matrix is essentially an array of vectors in GLSL—column vectors, in fact (a review of
column major vector ordering from Chapter 4, “Basic Transformations: A Vector/Matrix
Primer,” may be in order here). For example, to set the last column of a 4 x 4 matrix, you
would write code similar to this:
mModelView[3] = vec4(0.0f, 0.0f, 0.0f, 1.0f);
Conversely, to retrieve the last column of a matrix:
vec4 vTranslation = mModelView[3];
Or even a finer grained query:
vec3 vTranslation = mModelView[3].xyz;
Matrices can be multiplied by vectors too; a common use of this is to transform a vertex
by the ModelViewProjection matrix, such as:
vec4 vVertex;
mat4 mvpMatrix;


vOutPos = mvpMatrix * vVertex;
Also, just like vectors, the matrix data types have their own constructors too. For example,
to hard code an inline 4 x 4 matrix, you can write code like this:

mat4 vTransform = mat4(1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);

In this case we made the transformation matrix the identity matrix. A quicker constructor for matrices that fills in just the diagonal with a single value can also be used.

mat4 vTransform = mat4(1.0f);

storage qualifiers
shader variable declarations may optinally have a storage qualifier specified. qualifiers are used to flag variables as input variables (in or uniform), output variables (out), or constant (const). Input variables receive data either from the OpenGL client (attributes
submitted via C/C++) or from the previous shader stage (for example, variables passed
from the vertex shader to the fragment shader). Output variables are variables you write to
in any of the shader stages that you want to be seen by the subsequent shader stages, for
example, passing data from the vertex shader to the fragment shader or writing the final
fragment color by the fragment shader. Table 6.3 lists the primary variable qualifiers.

在这里插入图片描述

One variable qualifier inout can only be used when declaring a parameter to a function.
Because GLSL does not support pointers (or references), this is the only way to pass a value
to a function and allow the function to modify and return the value of the same variable.
For example, this function declaration
int CalculateSometing(float fTime, float fStepSize, inout float fVariance);
would return an integer (perhaps a pass/fail flag), but also could modify the value of the
fVariance variable, and the calling code could read the new value back from the variable
as well. In C/C++, to allow modification of a parameter, you might well declare the function
this way using a pointer:
int CalculateSomething(float fTime, float fStepSize, float* fVariance);
The centroid qualifier has no effect unless rendering is being done to a multisampled
buffer. In a single sampled buffer, interpolation is always performed from the center of a
pixel. With multisampling, when the centroid qualifier is used, the interpolated value is
selected so that it falls within the primitive and the pixel. See Chapter 9, “Advanced
Buffers: Beyond the Basics,” for more details about how multisampling works.
By default parameters are interpolated between shader stages in a perspective correct
manner. You can specify nonperspective interpolation with the noperspective keyword or
even no interpolation at all with the flat keyword. You can also optionally use the smooth
keyword to explicitly state the variable is smoothly interpolated in a perspective correct
manner, but that is already the default. Here are a few example declarations.
smooth out vec3 vSmoothValue;
flat out vec3 vFlatColor;
noperspective float vLinearlySmoothed;

A Real Shader
Finally, let’s take a look at a real shader pair that does something useful. The GLShaderManager class has a stock shader called the identity shader. This shader does not transform geometry and draws a primitive with a single color. Perhaps that’s just a little too simple. Let’s take it up a notch and show how we might also shade a primitive such as a triangle by using different color values for each vertex. Listing 6.1 shows our vertex shader, and Listing 6.2 shows our fragment shader.
LISTING 6.1 The ShadedIdentity Shader Vertex Program

// The ShadedIdentity Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 330
in vec4 vVertex; // Vertex position attribute
in vec4 vColor; // Vertex color attribute
out vec4 vVaryingColor; // Color value passed to fragment shader
void main(void)
{
	vVaryingColor = vColor;// Simply copy the color value
	gl_Position = vVertex; // Simply pass along the vertex position
}

LISTING 6.2 The ShadedIdentity Shader Fragment Program

// The ShadedIdentity Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 330
out vec4 vFragColor; // Fragment color to rasterize
in vec4 vVaryingColor; // Incoming color from vertex stage
void main(void)
{
	vFragColor = vVaryingColor; // Interpolated color to fragment
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值