Sending data to a shader using uniform
Preface
上一节我们介绍了通过顶点属性量进行数据传递,今天我们介绍一下通过uniform变量来进行数据传递的方法。
注意:此处的uniform与vertex attributes为两种数据量,不具相互替换性,功能各不相同,各司其职
uniform量更适用于着色器程序中可能改变的量,比如说,矩阵变换。
当然了,我们今天是带着例子来的,通常情况下,我们每讲一节都会给出一个例子,为了弥补上一节缺的一个例子,我们这次给大家展示两个。
而uniform传递数据也分为两种:
主要内容
1. using uniform variables example:旋转三角
2. using uniform blocks example:太阳纹理生成
Getting Ready
在此之前,我们好像需要先干点儿啥。
我们需要一个数学库实现矩阵的一些个运算,嗯,我们装一个glm库
链接:https://pan.baidu.com/s/1GL3tiAkEKnE43U15TDYyww 密码:i6dp
下载好呢,里面有关于该库的说明和使用,这些都不用看,我们只需要里面的一个叫glm的文件夹。
然后把上述的那个glm文件夹放置在VS的安装目录下,就像下面这样:
啊,终于可以玩啦,啦啦啦。
using uniform variables
按照惯例,先上效果
澄清一下,这个是动画,并不是静态的转动角度结果展示。
那我们来想一下,怎么实现它,细想一下,我们之前是怎么实现画图的,我们先创建好着色器,然后将它们编译连接,随后,我们在应用程序(opengl主程序)中进行数据创建,并将数据传递到着色器脚本程序中,经过处理,最后应用程序(opengl主程序)中进行渲染。
那么我们现在要实现一个动画,使得三角形旋转起来,那就是说,我们需要将三角形的顶点位置实时更新、渲染。
根据图形学中的矩阵变换,我们只需要得到一个变换矩阵M作为顶点矩阵的变换系数,然后两个矩阵相乘得到新的顶点位置矩阵。
ok,我们可以实现这一步,再稍微想一下,M这个系数应该是随时间而改变的,然后每次乘以顶点位置矩阵,得到新的位置。
矩阵变换,属于OpenGL中的数学运算环节,之前我们就提到了GLSL出现的背景是为了减轻CPU在图形绘制中运算负担,而使用GLSL语言将其中的运算环节交付GPU去完成,所以这个环节应该写于着色器程序中,而这又属于顶点属性相关的运算,所以我们写入vertex shader中
basic.vert
#version 430 layout (location=0) in vec3 VertexPosition; layout (location=1) in vec3 VertexColor; out vec3 Color; uniform mat4 RotationMatrix; void main() { Color = VertexColor; gl_Position = RotationMatrix * vec4(VertexPosition, 1.0); }
而我们的片元着色器则不用理会
和以前一样:
basic.frag
#version 430 in vec3 Color; layout(location = 0)out vec4 FragColor; void main() { FragColor = vec4(Color, 1.0); }
GLSL语言中mat4代表四阶方阵。
我们来阐述一些细节:
在着色器中,uniform variables 权限为只读,它们的值只能在shader外部通过OpenGL API进行改变,值得注意的是,它们可以在shader中被已声明的常量初始化。
uniform variables能够出现在任何shader中,且总是作为输入变量而存在。在一个着色器程序(shader program)中,它们可以在一个或多个shader(着色器)中被声明,但是,这种情况下,同一个名字的uniform variable 在所有的着色器中必须声明为同一个类型。换句话来讲,它们 存在于一个shader program 的 命名空间(namespace)中,在已经编译连接好的着色器程序(shader program)中,所有的着色器(shader)共享之。
有趣的小插曲
???? 那么,存在于多个shader中同名的uniform variable 属于同一个量么,这是个问题 ????
呃,,这个问题之前并没注意到,文章写到此处,大脑突然发出的问题,刚刚写了一个测试,发现:
所有的uniform同名同型变量均为同一个存在。
我们在后续会为大家解释,而且,测试效果会给大家带来不一样的超凡体验!!
end
我们继续
让我们在应用程序中创建一个变换矩阵,然后将其传递到着色器中。
glClear(GL_COLOR_BUFFER_BIT);
mat4 rotationMatrix = glm::rotate(mat4(1.0), angle, vec3(0.0f, 0.0f, 1.0f)); GLuint location = glGetUniformLocation(programHandle, "RotationMatrix"); if (location >= 0) glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]); else cout << "error" << endl;
glBindVertexArray(vaoHandle);
glDrawArrays(GL_TRIANGLES, 0, 3);
很明显这是渲染模块里面的代码,当然书上也只给出了这一段,可以说是,emmm,只有发动机和轮子,车子都要自己动手造!
这里需要吐槽一下,包括红宝书在内,很多opengl书籍教程只在一开始给出一个程序例子,以示作框架,之后讲解知识,只作关键模块代码讲解,剩下的,各个模块间的搭配组装都需要自己悟,自己摸索。以致大部分人都放弃了,因为很多知识都看不到效果,因为书中只给出了核心代码,在没有对程序流程机制有很深入的了解的情况下,无法将给出核心代码组装到自己的测试程序中的,那还学个撒子。
所以,第一章,无论讲解什么知识点,我都会给出整个程序的所有代码,基本上有5、6个例子,让学习的人能够熟练掌握,以达到“给出某个模块的核心代码,你能够组装到自己的程序中,看到效果”的目的。
先来说明一下上面的核心代码的意图:
在GLSL语言中,mat4(1.0f) 是一个主对角线元素均为1.0f的一个四阶对角矩阵,现在也就是四阶单位矩阵。
glm::rotate 函数,第一个参数为旋转矩阵,第二个参数为旋转角,第三个参数为旋转轴。
旋转角如何随时间变化而变化,之后我们自己造,我看过learn opengl的中文网上教程,将英文版直译过来那种,里面的实现整的有点复杂了,我们这个相对简单些。
之后一行就属于常规操作了,让我们根据uniform variable 名字获取到其location值。
如果该uniform变量非活动量(未激活),那么location值将会为-1。
而后面那个glUniformMatrix4fv函数就是将一个四阶方阵传入到location处,后面的数字代表矩阵的个数,第三个bool量为是否需要转置,最后为旋转系数矩阵数据的首地址。
插曲问题解决
看到上面的获取location代码了么,这就是答案,第一个参数传入的是整个着色器程序的句柄,而不是单个着色器句柄,so,所有的同名uniform变量均会在同一时刻改变。
我们可以将basic.frag代码改成如下进行测试(下面是测试,你也可以运行出上面的三角形然后在测试下面的效果)
#version 430 in vec3 Color; layout(location = 0)out vec4 FragColor; uniform mat4 RotationMatrix; void main() { FragColor = RotationMatrix * vec4(Color, 1.0); }
我们在frag shader中也添加一个同名的uniform variable ,然后用它去和颜色矩阵相乘,那么我们就可以得到这样的效果:
三角形的顶点位置以及颜色会以同一个系数随着时间的改变而一起改变:
之前的只是不变色,单纯转动。
end
解释完了,轮到我们上场组装汽车了。
我们如何实现angle随时间的变化而变化呢。
在glut库中有一个时间函数,glutTimerFunc
它有三个参数,第一个参数为毫秒,第二个参数为 回调函数指针,第三个参数为回调函数编号。
如何使用呢?
如果你在应用程序主函数中调用了该函数:
glutTimerFunc(10, render, 1); //每10ms调用render函数,该函数在时间事件中编号为1
那么在render中必须再次调用方可避免render只调用一次:
void render(int) { //代码书写区 ..... glutTimerFunc(10, render, 1); }
时间函数的第二个回调函数类型必须为 void (*)(int)
说过了,后面的int参数为该回调函数在时间事件中的编号,可以没有参数名,如果无法理解,可以采用联想理解法,之前的C++ 语法中出现过类似的现象,如:运算符重载中的后置++符号重载函数的实现
好了我们可以通过这个函数将angle随着时间的变化而变化了。
void render(int) { static GLfloat angle = 0.; angle += 3.14 / 360; if (angle >= 360.0f)angle -= 360.0f; glClear(GL_COLOR_BUFFER_BIT); mat4 rotationMatrix = glm::rotate(mat4(1.0), angle, vec3(0.0f, 0.0f, 1.0f)); GLuint location = glGetUniformLocation(programHandle, "RotationMatrix"); if (location >= 0) glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]); else cout << "error" << endl; glBindVertexArray(vaoHandle); glDrawArrays(GL_TRIANGLES, 0, 3); glutSwapBuffers(); glutTimerFunc(10, render, 1); }
至此,我们终于将核心技术组装到我们的测试程序中了
好了,如果还有什么问题,可以看一下下面的完整代码:
着色器程序已给出,下方不作展示
//配置代码 #if _MSC_VER>=1900 #include "stdio.h" _ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned); #ifdef __cplusplus extern "C" #endif FILE* __cdecl __iob_func(unsigned i) { return __acrt_iob_func(i); } #endif /* _MSC_VER>=1900 */ //code-list //using namespace std; #include <iostream> #include <fstream> using namespace std; #include <vgl.h> #include <glmglm.hpp> using namespace glm; #include <glmgtcmatrix_transform.hpp> GLint vertShader, fragShader; GLuint vaoHandle, programHandle; float positionDate[] = { -0.8f,-0.8f,0.0f, 0.8f,-0.8f,0.0f, 0.0f,0.8f,0.0f, }; float colorDate[] = { 1.0f,0.0f,0.0f, 0.0f,1.0f,0.0f, 0.0f,0.0f,1.0f, }; void init(); void render(int); void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName); //编译着色器 void _Link_Shader_(); //链接着色器 bool readFile(const char*, string&); int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize(1024, 768); glutInitWindowPosition(20, 20); glutCreateWindow("Rotated-Triangle"); if (glewInit()) { cout << "Error!" << glGetString(glewInit()) << endl; exit(EXIT_FAILURE); } cout << "GL version:" << glGetString(GL_VERSION) << endl; _Compiling_Shader_(vertShader, GL_VERTEX_SHADER, "basic.vert"); _Compiling_Shader_(fragShader, GL_FRAGMENT_SHADER, "basic.frag"); _Link_Shader_(); init(); glutTimerFunc(10, render, 1); glutMainLoop(); } void init() { GLuint vboHandles[2]; glGenBuffers(2, vboHandles); GLuint postionBufferHandle = vboHandles[0]; GLuint colorBufferHanle = vboHandles[1]; glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle); glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionDate, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle); glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorDate, GL_STATIC_DRAW); glGenVertexArrays(1, &vaoHandle); glBindVertexArray(vaoHandle); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr); } void render(int) { static GLfloat angle = 0.; angle += 3.14 / 360; if (angle >= 360.0f)angle -= 360.0f; glClear(GL_COLOR_BUFFER_BIT); mat4 rotationMatrix = glm::rotate(mat4(1.0), angle, vec3(0.0f, 0.0f, 1.0f)); GLuint location = glGetUniformLocation(programHandle, "RotationMatrix"); if (location >= 0) glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]); else cout << "error" << endl; glBindVertexArray(vaoHandle); glDrawArrays(GL_TRIANGLES, 0, 3); glutSwapBuffers(); glutTimerFunc(10, render, 1); } bool readFile(const char* filename, string& content) { ifstream infile; infile.open(filename); if (!infile.is_open())return false; char ch; infile >> noskipws; while (!infile.eof()) { infile >> ch; content += ch; } infile.close(); content += '