我们将接触到一个新的着色器变量类型——一致变量 属性变量与一致变量之间的区别:属性变量包含顶点特性数据所以每次调用shader都会从顶点缓冲区中重新导入新的数据,而一致变量中的值在整个绘制过程中都保持不变。这意味着我们在绘制过程之前就为一致变量赋值并且在着色器的每次调用中都可以访问这个相同的值。一致变量对于保存光照参数(光源位置和光照方向等),变换矩阵、纹理对象句柄等都是非常有用的。 在此教程中我们最终实现的功能是使某些东西在屏幕上不停的运动。要实现这个功能,我们需要使用一个一致变量和 GLUT 库提供的 idle() 回调函数,一致变量的值在每一帧中都会被改变。问题在于 GLUT 并不会反复调用我们的渲染回调函数——除非它不得不调用。GLUT 只有在如下列事件发生时才会强制调用渲染回调函数:最大化或者最小化窗口、被另一个窗口遮挡,遮挡窗口移除等。如果我们在程序运行之后对窗口的布局不做任何变化则渲染回调函数只会调用一次。你可以通过在渲染函数中添加一个打印函数来验证,你会看到打印函数只执行一次,如果你将窗口最大化或者最小化你可以发现打印函数会再次被执行。在 GLUT 中只注册一个渲染回调函数对于前面的教程来说是合适的,但是现在我们想要不断的改变变量的值,要实现这个功能我们需要注册一个 idle() 回调。Idle函数会在窗口系统未接收到事件的时候被GLUT调用,你可以为 idle() 回调函数设计一个专用功能函数用于做一些与时间更新类似的记录工作,或者简单的将渲染回调函数注册为idle回调函数即可。在本教程中我们直接将渲染函数注册为idle回调函数并且在渲染函数中对一直变量进行更新。 1.

glutIdleFunc(RenderCallBack);

将渲染回调注册为全局闲置回调。 2.

gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);

在对 Shader 程序对象进行链接之后我们需要从 Shader 程序对象中获取指向一致变量的地址。这是 C/C++ 应用程序执行环境需要映射 Shader 执行环境的另一个例子。当你编译 Shader 程序时 GLSL 会自动为每个一致变量分配一个索引,在 Shader 内部编译器就是通过索引来解决变量的访问问题的。通过使用程序对象句柄和变量的名字来调用 glGetUniformLocation 函数,应用程序同样可以获取这个索引,如果出现错误的话,这个函数会返回-1。所以错误检查是非常重要的(正如我们上面通过调用 assert() 函数一样),否则如果出现错误,则更新之后的变量就不会被传递到 Shader 中去。调用这个函数失败的原因有两个:其一是你弄错了变量名,其二是变量被编译器优化掉了。如果 GLSL 编译器发现变量在 Shader 中并没有被使用,则编译器会将其剔除。在这种情况下调用 glGetUniformLocation 函数就会失败。 3.

static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));

我们定义一个静态 float 变量并且在每次调用渲染函数的时候使其值增加一点(如果它在你的机器上运动的太快或者太慢,你可以将0.001改得小一点或者大一点),实际上传递到 Shader 中的值是 Scale 变量的 sin 值,这是为了创建一个在 -1.0 和 1.0 之间的循环。 需要注意的是 sinf()使用弧度值而不是角度值作为参数,我们需要 sin()函数产生的这个波动值。Sinf()所产生的结果通过 glUniform1f 函数传递到 Shader 中。OpenGL 提供了多个形如 glUniform{1234}{if} 的函数,你可以使用这些函数将值传递到 1D,2D,3D 或者 4D(由位于glUniform后面的数字确定)向量中,这些值可以是浮点数也可以是整数(由后缀‘i’和‘f’确定)。类似的我们也可以使用向量地址或者矩阵地址作为参数来传递一个向量或者矩阵。这个函数的第一个参数是我们通过 glGetUniformLocation() 获得的变量的索引地址。 4.

uniform float gScale;

在 Shader 中声明一个一致变量。 5.

gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z,1.0);

在每一帧中,我们都让位置向量的 X 分量和 Y 分量乘上从应用程序中传入的值。 项目代码:

#include <stdio.h>
#include<string.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include “ogldev_math_3d.h” //用于OpenGL的3d数学库,这里主要用到了顶点这个数据结构,里面报错的代码可以先注释掉
#include “ogldev_util.h” //用于读取文本文件

GLuint VBO; //全局GLuint引用变量,来操作顶点缓冲器对象
GLuint gScaleLocation; //位置中间变量

// 定义要读取的顶点着色器脚本和片断着色器脚本的文件名,作为文件读取路径
const char* pVSFileName = “shader.vs”;
const char* pFSFileName = “shader.fs”;

static void RenderCallBack()
{
glClear(GL_COLOR_BUFFER_BIT); //清空颜色缓存

static float Scale = 0.0f;   //维护一个不断慢慢增大的静态浮点数
Scale += 0.001f;    //如果图像变化太快或者太慢,可调节此数值
glUniform1f(gScaleLocation, sinf(Scale));   // 将值传递给shader

glEnableVertexAttribArray(0);   //开启顶点属性
glBindBuffer(GL\_ARRAY\_BUFFER, VBO);   //绑定GL\_ARRAY\_BUFFER缓冲器
glVertexAttribPointer(0, 3, GL\_FLOAT, GL\_FALSE, 0, 0);   //管线解析bufer中的数据

glDrawArrays(GL\_TRIANGLES, 0, 3);   //画三角形,3个顶点
glDisableVertexAttribArray(0);   //禁用顶点数据
glutSwapBuffers();   //交换前后缓存

}

static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderCallBack);
glutIdleFunc(RenderCallBack); //将渲染回调注册为全局闲置回调
}

static void CreateVertexBuffer()
{
Vector3f Vertices[3]; //创建含有3个顶点的顶点数组
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);

glGenBuffers(1, &VBO);   //创建缓冲器
glBindBuffer(GL\_ARRAY\_BUFFER, VBO);   //绑定GL\_ARRAY\_BUFFER缓冲器

glBufferData(GL\_ARRAY\_BUFFER, sizeof(Vertices), Vertices, GL\_STATIC\_DRAW);   //绑定顶点数据

}

//使用shader文本编译shader对象,并绑定shader到着色器程序中
static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
// 根据shader类型参数定义两个shader对象
GLuint ShaderObj = glCreateShader(ShaderType);
// 检查是否定义成功
if (ShaderObj == 0)
{
fprintf(stderr, “Error creating shader type %d\n”, ShaderType);
exit(0);
}

// 定义shader的代码源
const GLchar\* p\[1\];
p\[0\] = pShaderText;
GLint Lengths\[1\];
Lengths\[0\] = strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);
glCompileShader(ShaderObj);    // 编译shader对象

// 检查和shader相关的错误
GLint success;
glGetShaderiv(ShaderObj, GL\_COMPILE\_STATUS, &success);
if (!success)
{
    GLchar InfoLog\[1024\];
    glGetShaderInfoLog(ShaderObj, 1024, NULL, InfoLog);
    fprintf(stderr, "Error compiling shader type %d: '%s'\\n", ShaderType, InfoLog);
    exit(1);
}

// 将编译好的shader对象绑定到program object程序对象上
glAttachShader(ShaderProgram, ShaderObj);

}

// 编译着色器函数
static void CompileShaders()
{
// 创建着色器程序
GLuint ShaderProgram = glCreateProgram();
// 检查是否创建成功
if (ShaderProgram == 0)
{
fprintf(stderr, “Error creating shader program\n”);
exit(1);
}

// 存储着色器文本的字符串缓冲
string vs, fs;

// 分别读取着色器文件中的文本到字符串缓冲区
if (!ReadFile(pVSFileName, vs))
{
    exit(1);
};
if (!ReadFile(pFSFileName, fs))
{
    exit(1);
};

// 添加顶点着色器和片段着色器
AddShader(ShaderProgram, vs.c\_str(), GL\_VERTEX\_SHADER);
AddShader(ShaderProgram, fs.c\_str(), GL\_FRAGMENT\_SHADER);

// 链接shader着色器程序,并检查程序相关错误
GLint Success = 0;
GLchar ErrorLog\[1024\] = { 0 };
glLinkProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL\_LINK\_STATUS, &Success);
if (Success == 0)
{
    glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
    fprintf(stderr, "Error linking shader program: '%s'\\n", ErrorLog);
    exit(1);
}

// 检查验证在当前的管线状态程序是否可以被执行
glValidateProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL\_VALIDATE\_STATUS, &Success);
if (!Success)
{
    glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
    fprintf(stderr, "Invalid shader program: '%s'\\n", ErrorLog);
    exit(1);
}

// 设置到管线声明中来使用上面成功建立的shader程序
glUseProgram(ShaderProgram);

// 查询获取一致变量的位置
gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);    // 检查错误

}

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

glutInitWindowSize(400, 400);
glutInitWindowPosition(100, 100);
glutCreateWindow("Shader一致向量三角形");

glutDisplayFunc(RenderCallBack);  //开始渲染

InitializeGlutCallbacks();

// 检查GLEW是否就绪,必须要在GLUT初始化之后!
GLenum res = glewInit();
if (res != GLEW\_OK)
{
    fprintf(stderr, "Error: '%s'\\n", glewGetErrorString(res));
    return 1;
}

glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //缓存清空后的颜色值
CreateVertexBuffer(); //创建顶点缓冲器

CompileShaders();   // 编译着色器

glutMainLoop();   //通知开始GLUT的内部循环

return 0;

}

shader.vs脚本代码:

#version 330 //告诉编译器我们的目标GLSL编译器版本是3.3

layout (location = 0) in vec3 Position; // 绑定定点属性名和属性,方式二缓冲属性和shader属性对应映射

uniform float gScale;

//在 Shader 中声明一个一致变量
void main()
{
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0); // 为glVertexAttributePointer提供返回值
}

shader.fs脚本代码:

#version 330 //告诉编译器我们的目标GLSL编译器版本是3.3

out vec4 FragColor; // 片段着色器的输出颜色变量

// 着色器的唯一入口函数
void main()
{
// 定义输出颜色值
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

运行效果如下(gif图片): 参考链接: http://ogldev.atspace.co.uk/www/tutorial05/tutorial05.html http://blog.csdn.net/cordova/article/details/52504118