OpenGL编程逐步深入(五)Uniform 变量
准备知识
在这个教程中我们会遇到一种新的Shader变量类型,即uniform变量。attribute(属性)变量和uniform变量的不同之处在于attribute 变量中包含顶点的具体数据,当每次执行shader调用时会从顶点缓存中重新加载一个新的值。而uniform类型的变量在整个绘制调用中始终使用同一个变量。这意味着你在绘制调用前加载的值在每个vertex shader调用时能访问到相同的值。uniform变量在存储光照参数(光照位置、方向等)、变换矩阵、纹理对象句柄等这一类型的数据时非常有用。
在这个教程中我们最终会看到一些东西在屏幕上移动。我们將它和一个uniform变量进行绑定,GLUT为我们提供了空闲回调函数,在绘制每一帧的时候改变变量的值。GLUT并不会重复的调用渲染回调函数,除非在需要的时候,例如窗口最大化、最小化或被其他窗口覆盖。如果我们在启动应用程序后不对窗口进行任何操作,渲染回调函数仅仅会被调用一次。可以通过在这个回调函数中使用printf向控制台打印信息来验证这个结论。在GLUT中只注册渲染回调函数在前几节教程中没什么问题,但本节我们需要反复的改变变量的值,因此我们通过注册空闲回调函数实现。空闲处理函数即使没有接收到来自Windows系统事件时也会被GLUT实时的调用。你可以为这个回调专门写一个函数,或者把渲染回调函数也指定为空闲回调函数。在本教程我们我们采用后者,在渲染回调函数中更新变量的值。
项目配置
参考上节。
程序代码
我们在上一节代码基础上进行调整。
清单1.主程序 tutorial05.cpp代码
/*
Copyright 2010 Etay Meiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the 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 of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along 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 330
layout (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函数参数值在每一帧渲染时会改变,然后你就能够解释屏幕中的三角形为什么一会变大一会变小。
编译运行
运行程序你会看到大小不断变化的三角形。