II. Positioning---Chapter 3----Multiple Shaders
Multiple Shaders
Well, moving the triangle around is nice and all, but it would also be good if we could do something time-based in the fragment shader. Fragment shaders cannot affect the position of the object, but they can control its color. And this is what fragChangeColor.cpp
does.
Fragment shaders不能影响object的位置,但是他们能控制object的颜色.
The fragment shader in this tutorial is loaded from the file data\calcColor.frag
:
Example 3.9. Time-based Fragment Shader
#version 330 out vec4 outputColor; uniform float fragLoopDuration; uniform float time; const vec4 firstColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); const vec4 secondColor = vec4(0.0f, 1.0f, 0.0f, 1.0f); void main() { float currTime = mod(time, fragLoopDuration); float currLerp = currTime / fragLoopDuration; outputColor = mix(firstColor, secondColor, currLerp); }
This function is similar to the periodic loop in the vertex shader (which did not change from the last time we saw it). Instead of using sin/cos functions to compute the coordinates of a circle, interpolates between two colors based on how far it is through
the loop. When it is at the start of the loop, the triangle will be firstColor
, and when it is at the end of the loop, it will be secondColor
.
这个函数与顶点shader中的周期循环相似(从我们最后一次看到就没再改变).不是使用sin/cos来计算圆周的坐标,而是按how far it is through the loop在两个颜色值间进行插值.当它在刚开始进行循环,triangle将会是 firstColor,
当结束循环,它是secondColor.
The standard library function mix
performs linear interpolation between two values. Like many GLSL standard functions, it can take vector parameters; it will perform component-wise
operations on them. So each of the four components of the two parameters will be linearly interpolated by the 3rd parameter. The third parameter, currLerp
in this case,
is a value between 0 and 1. When it is 0, the return value frommix
will be the first parameter; when it is 1, the return value will be the second parameter.
标准mix()函数是在两个值间进行线性插值.像其他GLSL标准函数一样,它可以有vector参数,能按component-wise计算.所以两个参数的每四个component将会被interpolated第三个参数.例子中第三个参数 currLerp
范围为0到1.当它是0,它返回的是mix()函数的第一个参数,当它是1,它返回mix()函数的第二个参数.
Here is the program initialization code:
Example 3.10. More Shader Creation
void InitializeProgram() { std::vector<GLuint> shaderList; shaderList.push_back(Framework::LoadShader(GL_VERTEX_SHADER, "calcOffset.vert")); shaderList.push_back(Framework::LoadShader(GL_FRAGMENT_SHADER, "calcColor.frag")); theProgram = Framework::CreateProgram(shaderList); elapsedTimeUniform = glGetUniformLocation(theProgram, "time"); GLuint loopDurationUnf = glGetUniformLocation(theProgram, "loopDuration"); GLuint fragLoopDurUnf = glGetUniformLocation(theProgram, "fragLoopDuration"); glUseProgram(theProgram); glUniform1f(loopDurationUnf, 5.0f); glUniform1f(fragLoopDurUnf, 10.0f); glUseProgram(0); }
As before, we get the uniform locations for time
and loopDuration
, as well as
the new fragLoopDuration
. We then set the two loop durations for the program.
像以前一样我们查询time,loopDuration和新变量fragLoopDuration的location.然后设置两个loop duration.
You may be wondering how the time
uniform for the vertex shader and fragment shader get set? One of the advantages of the GLSL compilation model, which links vertex and
fragment shaders together into a single object, is that uniforms of the same name and type are concatenated. So there is only one uniform location for time
, and it refers
to the uniform in both shaders.
你也许想知道在vertex shader和fragment shader中uniform变量time怎样获得它的设置值的?GLSL编译模型的一个优势是将vertex shader和fragment shader链接到一起成为一个单独的object,也就是说,有相同名称和类型的uniform变量是有concatenated(关联的).所以time变量只有一个uniform location,关联到两个shader中.
The downside of this is that, if you create one uniform in one shader that has the same name as a uniform in a different shader, but a differenttype, OpenGL will give you a linker error and fail to generate a program. Also, it is possible to accidentally link two uniforms into one. In the tutorial, the fragment shader's loop duration had to be given a different name, or else the two shaders would have shared the same loop duration.
不利的是,如果你在一个shader中创建一个uniform变量与另一个shader中的uniform变量有相同名称,但是不同类型,那么OpenGL将会返回一个链接错误并且无法产生program.所以可能偶然把两个uniform链接称为一个.本文中,fragment shader的loop duration变量必须重新命名或者两个shader共享同一个loop duration.
In any case, because of this, the rendering code is unchanged. The time uniform is updated each frame with FreeGLUT's elapsed time.
这样,rendering代码不变.time变量每一fram都会变化.
Globals in shaders. Variables at global scope in GLSL can be defined with certain storage qualifiers: const
, uniform
, in
,
and out
. A const
value works like it does in C99 and C++: the value does not
change, period. It must have an initializer. An unqualified variable works like one would expect in C/C++; it is a global value that can be changed. GLSL shaders can call functions, and globals can be shared between functions. However, unlike in
, out
,
and uniforms
, non-const and const
variables are not shared
between stages.
Globals in shaders. GLSL中全局变量能被定义为: const
, uniform
, in
,out
.一个 const原理与C99和C++中一样,值不允许改变,必须初始化.一个unqualified变量是值可变的全局类型变量.GLSL
shader可以调用函数,全局变量可以在函数间共享.但是不像 in
, out
,
uniforms三种类型变量 non-const
和
const
变量不能在stages之间共享.
Further Study 课后作业
There are several things you can test to see what happens with these tutorials.
-
With
vertCalcOffset.cpp
, change it so that it draws two triangles moving in a circle, with one a halfloopDuration
ahead of the other. Simply change the uniforms after theglDrawArrays
call and then make theglDrawArrays
call again. Add half of the loop duration to the time before setting it the second time. -
In
fragChangeColor.cpp
, change it so that the fragment program bounces betweenfirstColor
andsecondColor
, rather than popping fromsecondColor
back to first at the end of a loop. The first-to-second-to-first transition should all happen within a singlefragLoopDuration
time interval. In case you are wondering, GLSL supports theif
statement, as well as the ?: operator. For bonus points however, do it without an explicit conditional statement; feel free to use a sin or cos function to do this. -
Using our knowledge of uniforms, go back to Tutorial 2's FragPosition tutorial. Modify the code so that it takes a uniform that describes the window's height, rather than using a hard-coded value. Change the
reshape
function to bind the program and modify the uniform with the new height.