OpenGL笔记<4> 数据传递二
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 <glm\glm.hpp> using namespace glm; #include <glm\gtc\matrix_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 += '\0'; return true; } void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName) { shaderHandle = glCreateShader(GL_Shader_type); //检查编译情况 if (0 == shaderHandle) { fprintf(stderr, "Error creating shader.\n"); exit(EXIT_FAILURE); } string ShaderCode; if (!readFile(shaderName, ShaderCode)) { cout << "readFile Error!" << endl; exit(EXIT_FAILURE); } const GLchar* shaderSource = ShaderCode.c_str(); const GLchar* codeArray[] = { shaderSource }; glShaderSource(shaderHandle, 1, codeArray, NULL); glCompileShader(shaderHandle); GLint result; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result); if (GL_FALSE == result) { fprintf(stderr, "shader compilation failed!\n"); GLint logLen; glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(shaderHandle, logLen, &written, log); fprintf(stderr, "Shader log:\n%s", log); delete[] log; } } } void _Link_Shader_() { programHandle = glCreateProgram(); if (0 == programHandle) { fprintf(stderr, "Error creating program object.\n"); exit(1); } glAttachShader(programHandle, vertShader); glAttachShader(programHandle, fragShader); glLinkProgram(programHandle); GLint status; glGetProgramiv(programHandle, GL_LINK_STATUS, &status); if (GL_FALSE == status) { fprintf(stderr, "Failed to link shader program!\n"); GLint logLen; glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log:\n%s", log); delete[] log; } } else glUseProgram(programHandle); }
using uniform blocks
感觉可能讲的有点多,准备把这一小节另外开一节来着,想了想,只是一个语法结构的运用,应该还能可以吧。
先看效果:
当你的应用程序有很多个着色器程序使用相同的uniform 变量的时候,必须在每一个shader program中单独管理。而uniform的location是在shader program 连接着色器的时候生成的,所以,uniform的location值的改变,可能会从一个程序传递到下一个程序。uniform的数据必须重新生成和应用到新的location中。
uniform blocks就是为了简化上述操作,我们可以为uniform block 创建一个缓冲区,存储所有uniform的值,将该缓冲区绑定到uniform block上。
当着色器程序改变时,我们只需要在新的着色器程序中把缓冲区重新绑定到对应的block上面即可
uniform block语法定义如下:
uniform BlobSettings { vec4 InnerColor; vec4 OuterColor; float RadiusInner; float RadiusOuter; };
类似结构体
glsl程序:
我们是用两个三角形,拼成一个正方形,然后在正方形中画一个模糊的圆。
我们需要用顶点坐标数据画方框,用纹理数据确定颜色信息,所以这两者都需要进入着色器程序中,换句话说,都需要将其传入vertex shader 中。
basic.vert
#version 430 layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexTexCoord; out vec3 TexCoord; void main() { TexCoord = VertexTexCoord; gl_Position = vec4(VertexPosition, 1.0); }
纹理坐标无需处理,直接传递到下一阶段
basic.frag
#version 430 in vec3 TexCoord; layout (location = 0) out vec4 FragColor; layout (binding = 0) uniform BlobSettings { vec4 InnerColor; vec4 OuterColor; float RadiusInner; float RadiusOuter; }; void main() { float dx = TexCoord.x - 0.5; float dy = TexCoord.y - 0.5; float dist = sqrt(dx * dx + dy * dy); FragColor = mix(InnerColor, OuterColor, smoothstep(RadiusInner, RadiusOuter, dist)); }
有趣小插曲
我们的程序可是兼备调试器功能的,例如不小心把上面的最后一行的mix,不小心写成了max
则会提示:
是不是发现咱的程序还是挺强的
end
上述定义了名为BlobSettings的uniform block类型。该block中的变量定义了模糊圈的参数。
变量OuterColor定义圆外的颜色。 InnerColor是圆圈内部的颜色。
RadiusInner定义了内部圆的半径,它是纯色(在模糊边缘内),即:从圆心到模糊边界内边缘的距离。
RadiusOuter是圆的模糊边界的外边缘(此处的背景颜色等于OuterColor)。
main函数中的代码,dist记录纹理坐标到位于(0.5,0.5)的四边形中心的距离。
然后使用该距离通过smoothstep函数计算颜色。当第三个参数的值介于前两个参数的值之间时,此函数提供一个在0.0和1.0之间平滑变化的值。
否则它将返回0.0或1.0,取决于dist是小于第一个参数还是大于第二个参数。然后使用混合函数根据smoothstep函数返回的值在InnerColor和OuterColor之间进行线性插值。
opengl程序:
我们需要创建uniform 缓冲区,然后将数据放入缓冲区,最后将该缓冲区的数据传递到着色器中。
1. 获取uniform的location值:
GLuint blockIndex = glGetUniformBlockIndex(programHandle, "BlobSettings");
2. 创建uniform buffer:
GLint blockSize; glGetActiveUniformBlockiv(programHandle, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize); GLubyte* blockBuffer; blockBuffer = (GLubyte*)malloc(blockSize);
3. 记录block中每个变量的偏移量,以便我们之后填充数据
const GLchar* names[]{ "InnerColor","OuterColor","RadiusInner","RadiusOuter" }; GLuint indices[4]; glGetUniformIndices(programHandle, 4, names, indices); GLint offset[4]; glGetActiveUniformsiv(programHandle, 4, indices, GL_UNIFORM_OFFSET, offset);
我们先通过glGetUniformIndices获取block中四个uniform变量的索引值(uniform index)
其中的参数含义一路走过来到现在差不多都能看懂了吧应该,第一个是shader program的句柄(handle,用于唯一标识着色器程序),第二个参数为uniform变量的个数,第三个参数为uniform变量的名字,第四个参数为用于记录索引序列的数组指针
最后一个就不用说了吧,各个参数都应该没有什么歧义
4. 创建数据并填充
GLfloat outerColor[]{ 1.0f,0.0f,0.0f,0.0f }; GLfloat innerColor[]{ 1.0f,1.0f,0.0f,1.0f }; GLfloat innerRadius = 0.22f, outerRadius = 0.48f; memcpy(blockBuffer + offset[0], innerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[1], outerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[2], &innerRadius, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[3], &outerRadius, 4 * sizeof(GLfloat));
5. 创建ubo(uniform buffer object),并传递数据到ubo
GLuint uboHandle; glGenBuffers(1, &uboHandle); glBindBuffer(GL_UNIFORM_BUFFER, uboHandle); glBufferData(GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_DYNAMIC_DRAW);
6. 将ubo 绑定到 binding point 上面,该point 的索引为片元着色器中binding关键字确定的值
glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboHandle);
你可能想知道为什么我们将glBindBuffer和glBindBufferBase与GL_UNIFORM_BUFFER这个binding point一起使用。
两个glBind*函数使用的binding point是否相同?
答案是GL_UNIFORM_BUFFER这个binding point可以在每个函数中使用,含义略有不同。
在glBindBuffer中,我们绑定到一个可用于填充或修改缓冲区的binding point,但不能用作着色器的数据源。
当我们使用glBindBufferBase时,我们绑定到一个可以由着色器直接获取的索引值,即:第二个参数0(出处为:basic.frag中的(layout (binding = 0)))
也就是说一个是将GL_UNIFORM_BUFFER这个binding point绑定到ubo缓冲区句柄(uboHandle),然后通过glBufferData,向uboHandle所代表的缓冲区中填充数据,最后利用枚举值GL_UNIFORM_BUFFER这一个binding point 告诉编译器,我要将uniformbuffer中的数据通过glBind*函数进行绑定,即:将uboHandle(代表一块缓冲区)绑定到一个着色器中的索引值上面(代表着着色器中的uniform量),如:本例子中basic.frag文件中的layout (binding = 0) uniform BlobSettings,我们通过最后一个绑定函数将opengl程序中的uboHandle句柄代表的缓冲区绑定到frag shader中索引值为0的uniform block类型BlobSettings中,从而使得BlobSettings中的所有变量拥有自己对应的值。
代码:
关于代码,我一直都在不断改进中,尽量使得其参数以及思路合理化,或者说是贴近opengl宏函数的参数设置理念,下面的main.cpp代码是改进过很多的比较合理的代码
可能之前写的代码有点不太合理,包括主函数流程以及注释。
//配置代码 #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 */ #include <iostream> #include <fstream> #include <vgl.h> using namespace std; GLuint vaoHandle; void _Compiling_Shader_(GLint& shaderHandle, GLint shaderType, GLchar* shadername); void _Link_Shader_(GLint& programHandle, GLint* shaderArray, GLint arraySize); GLboolean readFile(GLchar* shaderName, string& code); void Render(); void update(); void init_vbo(); void init_ubo(GLint programHandle); int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize(768, 768); glutCreateWindow("uniform_blocks"); if (glewInit()) { cout << "Error" << glGetString(glewInit()) << endl; exit(EXIT_FAILURE); } cout << "GL version:" << glGetString(GL_VERSION) << endl; GLint vertShader = 0, fragShader = 0, programHandle; GLint shaderArray[]{ vertShader, fragShader }; //定义该程序需要的着色器 handle _Compiling_Shader_(shaderArray[0], GL_VERTEX_SHADER, "basic.vert"); _Compiling_Shader_(shaderArray[1], GL_FRAGMENT_SHADER, "basic.frag"); _Link_Shader_(programHandle, shaderArray, 2); init_ubo(programHandle); init_vbo(); glutDisplayFunc(Render); glutMainLoop(); } void Render() { glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(vaoHandle); glDrawArrays(GL_TRIANGLES, 0, 6); glutSwapBuffers(); } void init_vbo() { float positionData[] = { -0.8f, -0.8f, 0.0f, 0.8f, -0.8f, 0.0f, 0.8f, 0.8f, 0.0f, -0.8f, -0.8f, 0.0f, 0.8f, 0.8f, 0.0f, -0.8f, 0.8f, 0.0f }; float tcData[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; // Create and populate the buffer objects GLuint vboHandles[2]; glGenBuffers(2, vboHandles); GLuint positionBufferHandle = vboHandles[0]; GLuint tcBufferHandle = vboHandles[1]; glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle); glBufferData(GL_ARRAY_BUFFER, 18 * sizeof(float), positionData, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, tcBufferHandle); glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), tcData, GL_STATIC_DRAW); // Create and set-up the vertex array object glGenVertexArrays(1, &vaoHandle); glBindVertexArray(vaoHandle); glEnableVertexAttribArray(0); // Vertex position glEnableVertexAttribArray(1); // Vertex texture coords //glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle); //glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL); glBindVertexBuffer(0, positionBufferHandle, 0, sizeof(GLfloat) * 3); glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(0, 0); //glBindBuffer(GL_ARRAY_BUFFER, tcBufferHandle); //glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL); glBindVertexBuffer(1, tcBufferHandle, 0, sizeof(GL_FLOAT) * 2); glVertexAttribFormat(1, 2, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(1, 1); glEnable(GL_BLEND); //激活颜色混合特性 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* 颜色混合函数 glBlendFunc(GL_SRC_ALPHA, GL_ONE); 表示把渲染的图像叠加到目标区域,也就是说源的每一个像素的alpha都等于自己的alpha,目标的每一个像素的alpha等于1。这样叠加次数越多,叠加的图元的alpha越高,得到的结果就越亮。因此这种融合用于表达光亮效果。 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 表示把渲染的图像融合到目标区域。也就是说源的每一个像素的alpha都等于自己的alpha,目标的每一个像素的alpha等于1减去该位置源像素的alpha。 因此不论叠加多少次,亮度是不变的。 可以玩一下outerColor和innerColor,一共四个值,rgb,最后一个就是alpha,某个位置的像素信息由多层混合而成,每一层的rgb颜色乘以alpha透明度然后加上上一层的,最后形成某个点的像素信息 比如:我们的太阳,中间圆的颜色是橘黄色,是由红:绿 = 1:1配比而成,而且alpha为1,所以底层背景色不参加颜色混合。 */ } void init_ubo(GLint programHandle) { GLuint blockIndex = glGetUniformBlockIndex(programHandle, "BlobSettings"); GLint blockSize; glGetActiveUniformBlockiv(programHandle, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize); GLubyte* blockBuffer; blockBuffer = (GLubyte*)malloc(blockSize); const GLchar* names[]{ "InnerColor","OuterColor","RadiusInner","RadiusOuter" }; GLuint indices[4]; glGetUniformIndices(programHandle, 4, names, indices); GLint offset[4]; glGetActiveUniformsiv(programHandle, 4, indices, GL_UNIFORM_OFFSET, offset); GLfloat outerColor[]{ 1.0f,0.0f,0.0f,0.0f }; GLfloat innerColor[]{ 1.0f,1.0f,0.0f,1.0f }; GLfloat innerRadius = 0.22f, outerRadius = 0.48f; memcpy(blockBuffer + offset[0], innerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[1], outerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[2], &innerRadius, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[3], &outerRadius, 4 * sizeof(GLfloat)); GLuint uboHandle; glGenBuffers(1, &uboHandle); glBindBuffer(GL_UNIFORM_BUFFER, uboHandle); glBufferData(GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_DYNAMIC_DRAW); glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboHandle); } void _Compiling_Shader_(GLint& shaderHandle, GLint shaderType, GLchar* shadername) { shaderHandle = glCreateShader(shaderType); //检查是否创建成功 if (0 == shaderHandle) { cout << "Error creating shader" << endl; exit(EXIT_FAILURE); } string shadercode; if (!readFile(shadername, shadercode)) { cout << "readFile Error" << endl; exit(EXIT_FAILURE); } const GLchar* shaderSource = shadercode.c_str(); const GLchar* codeArray[]{ shaderSource }; glShaderSource(shaderHandle, 1, codeArray, NULL); glCompileShader(shaderHandle); //检查编译情况 GLint result; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result); if (GL_FALSE == result) { fprintf(stderr, "shader compilation failed!\n"); GLint logLen; glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(shaderHandle, logLen, &written, log); fprintf(stderr, "Shader log:\n%s", log); delete[] log; } } } void _Link_Shader_(GLint& programHandle, GLint* shaderArray, GLint shaderNum) { programHandle = glCreateProgram(); //检查创建情况 if (0 == programHandle) { cout << "Error creating program" << endl; exit(EXIT_FAILURE); } for (int i = 0; i < shaderNum; ++i) glAttachShader(programHandle, shaderArray[i]);//*(shaderArray + i)); glLinkProgram(programHandle); GLint status; glGetProgramiv(programHandle, GL_LINK_STATUS, &status); if (GL_FALSE == status) { fprintf(stderr, "Failed to link shader program!\n"); GLint logLen; glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen); if (logLen > 0) { char* log = new char[logLen]; GLsizei written; glGetShaderInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log:\n%s", log); delete[] log; } } else glUseProgram(programHandle); } GLboolean readFile(GLchar* filename, string& content) { ifstream infile; infile.open(filename); if (!infile.is_open())return GL_FALSE; char ch; infile >> noskipws; while (!infile.eof()) { infile >> ch; content += ch; } infile.close(); content += '\0'; return GL_TRUE; }
感谢您的阅读,生活愉快~