前言
属性是每个顶点位置、表面法现和纹理坐标等都需要的,而统一值则用于为整个图元批次向保持不变的(统一(uniform)的)着色器传递数据。
任何着色器变量都可以指定为一个统一值,而统一值可以在3个着色器阶段中的任何一个阶段中(当前只讨论顶点着色器和片段着色器)。
声明统一值
创建一个统一值非常简单,只需在变量声明开始时放置一个uniform关键词。
cpp
uniform float fTime;
uniform int iIndex;
uniform vec4 vColorValue;
uniform mat4 mvpMatrix;
统一值不能被标记为in或out,它们也不能在着色器阶段之间进行插值(虽然可以将它们复制到经过插值的变量中),并且它们总是只读的。
寻找统一值
在一个着色器进行编译和连接之后,我们必须在着色器中寻找统一值位置。
可以使用glGetUniformLocation函数来定位着色器代码中的统一值位置:
cpp
GLint glGetUniformLocation(GLuint shaderID, const GLchar* varName);
这个函数返回一个有符号的整数,代表在shaderID指定的着色器中由varName命名的变量的位置。
例如,为了获取一个名为vColorValue的统一值的位置,我们应该进行如下操作:
cpp
GLint iLocation = glGetUniformLocation(myShader, "vColorValue");
着色器变量名是区分大小写的,如果glGetUniformLocation的返回值是-1,就说明统一值的名称在着色器中不能被定位。
我们应当牢记,即使着色器编译正确,如果不是在着色器中直接使用,那么一个统一值名称仍然可能在这个着色器中"消失"。
如果声明了一个统一值而不去使用它,那么编译器将会丢弃它。
设置统一值
在通过glGetUniformLocation得到着色器中统一值的位置后,就可以针对统一值的类型设置各种数据值。
设置标量和向量统一值
一个单独的标量和向量数据类型可以在glUniform函数中使用下面的变量进行设置。
cpp
void glUniform1f(GLint location, GLfloat v0);
void glUniform2f(GLint location, Glfloat v0, GLfloat v1);
void glUniform3f(GLint location, GLfloat v0, GLfloat v1,
GLfloat v2);
void glUniform4f(GLint location, GLfloat v0, GLfloat v1,
GLfloat v2, GLfloat v3);
void glUniform1i(GLint location, GLint v0);
void glUniform2i(GLint location, GLint v0, GLint v1);
void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2,
GLint v3);
例如,考虑在一个着色器中声明的4个变量,如下所示。
cpp
uniform float fTime;
uniform int iIndex;
uniform vec4 vColorValue;
uniform bool bSomeFlag;
为了在着色器中寻找并设置这些值,我们的C/C++代码可能会像下面这样。
cpp
GLint locTime, locIndex, locColor, locFlag;
locTime = glGetUniformLocation(myShader, "fTime");
locIndex = glGetUniformLocation(myShader, "iIndex");
locColor = glGetUniformLocation(myShader, "vColorValue");
locFlag = glGetUniformLocation(myShader, "bSomeFlag");
......
......
glUseProgram(myShader);
glUniform1f(locTime, 45.2f);
glUniform1i(locIndex, 42);
glUniform4f(locColor, 1.0f, 0.0f, 0.0f, 1.0f);
glUniform1i(locFlag, GL_FALSE);
注意,我们是在使用一个整数版本的glUniform来传递一个bool值。
布尔值也可以作为浮点值进行传递,0.0代表假,1.0则代表真。
设置统一数组
glUniform函数还接受一个指针,假定指向一个数值数组。
cpp
void glUniform1fv(GLint location, GLuint count, GLfloat* v);
void glUniform2fv(GLint location, GLuint count, Glfloat* v);
void glUniform3fv(GLint location, GLuint count, GLfloat* v);
void glUniform4fv(GLint location, GLuint count, GLfloat* v);
void glUniform1iv(GLint location, GLuint count, GLint* v);
void glUniform2iv(GLint location, GLuint count, GLint* v);
void glUniform3iv(GLint location, GLuint count, GLint* v);
void glUniform4iv(GLint location, GLuint count, GLint* v);
在这里,count值代表每个含有x个分量的数组中有多少个元素,其中x是位于函数名末尾的数字。例如,如果我们有一个4分量的统一值。
cpp
uniform vec4 vColor;
在C/C++中,我们可以将它表示为一个浮点数组。
cpp
GLfloat vColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
但是,这是一个包含4个值的单个数组,所以会像下面这样将它传递到着色器中。
cpp
glUniform4fv(iColorLocation, 1, vColor);
如果我们在着色器中有一个颜色值数组uniform vec4vColors2;,那么在C/C++中,我们可以像下面这样表示和传递一个数组。
cpp
GLfloat vColors[2][4] = {{ 1.0f, 1.0f, 1.0f, 1.0f },
{ 1.0f, 0.0f, 0.0f, 1.0f }};
......
glUniform4fv(iColorLocation, 2, vColors);
在最简单的情况下,我们可以像下面这样设置一个单个浮点统一值。
cpp
GLfloat fValue = 45.2f;
glUniform1fv(iLocation, 1, &fValue);
设置统一矩阵
最后,我们来看一看如何设置一个矩阵统一值。
着色器矩阵数据类型只有浮点类型,这样变量就减少了很多。
下面的函数分别用来加载一个2 x 2、3 x 3和4 x 4矩阵。
cpp
glUniformMatrix2fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);
glUniformMatrix3fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);
glUniformMatrix4fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);
变量count代表指针参数m中存储的矩阵数量(没错,我们可以使用矩阵数组)。
如果矩阵已经按照列优先排序(OpenGL推荐的方式)进行存储,布尔值标记transpose将被设为GL_TRUE。
将这个值设置为GL_FALSE会导致这个矩阵在复制到着色器中时发生变换。
例如,在着色器中声明统一矩阵变量:
cpp
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
在C/C++代码中获取变量位置并设置矩阵值:
cpp
locMVP = glGetUniformLocation(ADSTextureShader, "mvpMatrix");
locMV = glGetUniformLocation(ADSTextureShader, "mvMatrix");
locNM = glGetUniformLocation(ADSTextureShader, "normalMatrix");
// ...
glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniformMatrix4fv(locMV, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix3fv(locNM, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
平面着色器
现在让我们来看一个使用统一值的示例着色器。
在存储着色器中,有一个平面着色器,它的功能就是将几何图形进行变换并将其设置为一个单色。
它使用的顶点属性只有顶点位置。它还需要使用两个统一值、一个变换矩阵和一个颜值。
顶点着色器代码如下:
cpp
#version 130
uniform mat4 mvpMatrix;
in vec4 vVertex;
void main()
{
gl_Position = mvpMatrix * vVertex;
}
可以看到在顶点着色器代码中声明了一个统一值变量:变化矩阵,然后在主函数中应用变换。
片段着色器代码如下:
cpp
#version 130
uniform vec4 vColorValue;
out vec4 vFragColor;
void main()
{
vFragColor = vColorValue;
}
和之前一样没有多大变化,只是简单的颜色传递。
C/C++代码如下:
cpp
#include "flatshader/flatshader.h"
#include <GLTools.h>
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#ifndef FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif
#include <GL/glut.h>
namespace flatshader
{
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch torusBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLuint flatShader; // 自定义的平面着色器
GLint locMVP; // 模型视图投影矩阵常量的位置
GLint locColor; // 颜色值均匀分布的位置
void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
viewFrame.MoveForward(4.0f);
gltMakeTorus(torusBatch, 0.80f, 0.25f, 52, 26);
flatShader = gltLoadShaderPairWithAttributes(
"./src/chapter_6/rewrite/flatshader/flatshader.vp"
, "./src/chapter_6/rewrite/flatshader/flatshader.fp"
, 1
, GLT_ATTRIBUTE_VERTEX
, "vVertex"
);
locMVP = glGetUniformLocation(flatShader, "mvpMatrix");
locColor = glGetUniformLocation(flatShader, "vColorValue");
}
void ShutdownRC()
{
}
void RenderScene()
{
static CStopWatch rotTimer;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix(viewFrame);
{
modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 10.f, 0.0f, 1.0f, 0.0f);
GLfloat vColor[] = { 0.1f, 0.1f, 1.0f, 1.0f };
glUseProgram(flatShader);
glUniform4fv(locColor, 1, vColor);
glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
}
torusBatch.Draw();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}
void ChangeSize(int w, int h)
{
if (h == 0)
h = 1;
glViewport(0, 0, w, h);
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 1000.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
int flatshader_test(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Simple Transformed Geometry");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
}
在RenderScene中,图形颜色的统一值和变换矩阵的统一值将在花托对象上调用Draw函数之前进行设置。
最终效果如下:
