准备知识
在这个教程中我们会遇到一种新的Shader变量类型,即uniform变量。attribute(属性)变量和uniform变量的不同之处在于attribute 变量中包含顶点的具体数据,当每次执行shader调用时会从顶点缓存中重新加载一个新的值。而uniform类型的变量在整个绘制调用中始终使用同一个变量。这意味着你在绘制调用前加载的值在每个vertex shader调用时能访问到相同的值。uniform变量在存储光照参数(光照位置、方向等)、变换矩阵、纹理对象句柄等这一类型的数据时非常有用。
在这个教程中我们最终会看到一些东西在屏幕上移动。我们將它和一个uniform变量进行绑定,GLUT为我们提供了空闲回调函数,在绘制每一帧的时候改变变量的值。GLUT并不会重复的调用渲染回调函数,除非在需要的时候,例如窗口最大化、最小化或被其他窗口覆盖。如果我们在启动应用程序后不对窗口进行任何操作,渲染回调函数仅仅会被调用一次。可以通过在这个回调函数中使用printf向控制台打印信息来验证这个结论。在GLUT中只注册渲染回调函数在前几节教程中没什么问题,但本节我们需要反复的改变变量的值,因此我们通过注册空闲回调函数实现。空闲处理函数即使没有接收到来自Windows系统事件时也会被GLUT实时的调用。你可以为这个回调专门写一个函数,或者把渲染回调函数也指定为空闲回调函数。在本教程我们我们采用后者,在渲染回调函数中更新变量的值。
项目配置
参考上节。
程序代码
我们在上一节代码基础上进行调整。
清单1.主程序 tutorial05.cpp代码
/*Copyright 2010 Etay MeiriThis program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program. If not, see <http://www.gnu.org/licenses/>.Tutorial 05 - uniform variables */#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "ogldev_math_3d.h"
GLuint VBO;
GLuint gScaleLocation;const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs";static void RenderSceneCB()
{glClear(GL_COLOR_BUFFER_BIT);static float Scale = 0.0f;Scale += 0.001f;glUniform1f(gScaleLocation, sinf(Scale));glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, VBO);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);glDrawArrays(GL_TRIANGLES, 0, 3);glDisableVertexAttribArray(0);glutSwapBuffers();
}static void InitializeGlutCallbacks()
{glutDisplayFunc(RenderSceneCB);glutIdleFunc(RenderSceneCB);
}static void CreateVertexBuffer()
{Vector3f Vertices[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);glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{GLuint ShaderObj = glCreateShader(ShaderType);if (ShaderObj == 0) {fprintf(stderr, "Error creating shader type %d\n", ShaderType);exit(1);}const GLchar* p[1];p[0] = pShaderText;GLint Lengths[1];Lengths[0]= strlen(pShaderText);glShaderSource(ShaderObj, 1, p, Lengths);glCompileShader(ShaderObj);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);}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);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);}glUseProgram(ShaderProgram);gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");assert(gScaleLocation != 0xFFFFFFFF);
}
int _tmain(int argc, _TCHAR* argv[])
{glutInit(&argc, argv);glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA);glutInitWindowSize(1024, 768);glutInitWindowPosition(100, 100);glutCreateWindow("Tutorial 05");InitializeGlutCallbacks();// Must be done after glut is initialized!GLenum res = glewInit();if (res != GLEW_OK) {fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));return 1;}printf("GL version: %s\n", glGetString(GL_VERSION));glClearColor(0.0f, 0.0f, 0.0f, 0.0f);CreateVertexBuffer();CompileShaders();glutMainLoop();return 0;
}
代码解读
glutIdleFunc(RenderSceneCB);
这里我们將渲染回调函数RenderSceneCB也注册为空闲回调函数,要注意的是如果你你打算定义一个专门的函数处理空闲回调,需要在后面增加glutPostRedisplay() 函数的调用,这样的话空闲回调函数就会反复的调用而渲染回调函数则不会。
gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);
在链接后,我们查询Program对象获取uniform变量的位置,这是另外一个c/c++执行环境需要映射到shader执行环境的案例。你不能直接访问shader的内容,也不能直接更新shader的变量。当你编译shader后,GLSL编译器会为每一个uniform变量分配一个索引。shader在编译器内部表示中都通过索引来访问它的变量。这个索引在程序中通过glGetUniformLocation也能够获取,调用函数时需要传入program对象的句柄和变量名称,该函数会返回变量的索引(出错时返回-1)。这里检测错误是非常必要的,否则将来更新变量时不会传递到shader中。这个函数调用失败主要两个原因,你的变量名拼写错误或它被编译器优化掉了,如果GLSL编译器发现该变量在Shader中没有用到就会去除它,这种情况下会导致glGetUniformLocation 调用失败。
static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));
这里我们定义一个静态float类型变量Scale ,在每次调用渲染回调函数时把它的值增加0.001,实际上传递给shader的值是Scale 变量的正弦值,这样做很好的创建了一个在-1.0到1.0之间的循环。需要注意的是sinf的参数是要传入一个弧度还是角度,这里我们无需关注,我们只需要产生一个在-1到1的正弦波。sinf函数的返回值通过glUniform1f函数传递到shader中,OpenGl提供的这个函数有多种形式glUniform{1234}{if}。你可以使用它將值加载到1维、2维、3维或4维向量中。第一个参数为之前调用glGetUniformLocation获取的位置索引。
清单2. shader.vs代码
#version 330layout (location = 0) in vec3 Position;uniform float gScale;void main()
{gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);
}
uniform float gScale;
这里我们定义了一个uniform类型变量。
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);
我们用gScale乘上Positon向量X/Y的值,确保vec4函数参数值在每一帧渲染时会改变,然后你就能够解释屏幕中的三角形为什么一会变大一会变小。
编译运行
运行程序你会看到大小不断变化的三角形。