在这里的例子中,后面做了一个颜色会闪烁的三角形,根据他的思路在原来三角形的基础上做一遍。本质就是修改片元着色器,修改所有顶点的颜色。
Uniforms是一种将数据传输到着色器的方式,本质上是全局变量,所有顶点的数据都是一样的。
将我们片元着色器的改成如下。
const char* fragment_shader_source =
"#version 330 core \n"
"uniform vec3 triangleColor; \n"
"out vec4 FragColor; \n"
"void main(){ \n"
" FragColor = vec4(triangleColor, 1.0f);\n"
"} \n";
就是添加了一个三维向量triangleColor,然后用它代替我们之前写死的RGB三个分量。
然后去到我们渲染循环里面,通过以下代码获取则个triangleColor的位置。
GLint uniColor = glGetUniformLocation(shaderProgram, "triangleColor");
然后通过glUniform3f来修改三个float类型的参数。
glUniform3f(uniColor, 1.0f, 0.0f, 0.0f); // 这样子就是一个红色的
怎么实现闪烁呢?例子中是通过sin计算时间来修改R的值,并且用了一些方法计算时间,但是我发现其实GLFW提供了获取时间的方法。所以就可以把刚才的代码改成如下。
glUniform3f(uniColor, (sin(glfwGetTime() * 10.0f) + 1.0f) / 2.0f, 0, 0);
为什么要时间×10,sin结果再+1再÷2呢?时间×10是修改闪烁的时间,学过三角函数都知道这样会加大频率。然后sin算出来的数值在1到-1之间,然而这里的RGB的值是取值从0到1的,所以就通过加以之后再除以二来映射到0到1之间。运行效果如图(两边的黑白是录频分辨率比窗口分辨率大的问题)。
我们可以再次的修改,把RGB都改了,乘上不同的速度,效果如下(RGB!)。
glUniform3f(uniColor,
(sin(glfwGetTime() * 0.8f) + 1.0f) / 2.0f,
(sin(glfwGetTime() * 1.35f) + 1.0f) / 2.0f,
(sin(glfwGetTime() * 2.0f) + 1.0f) / 2.0f);
(压缩GIF的时候比例有点变了)
刚才的方法是直接修改了顶点的颜色,但是那样对于所有顶点都是一样的。现在让我们来换一种方法,给顶点加上颜色吧!
我们在顶点中除了位置信息再添加颜色的RGB。
float vertices[] = {
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 前三个是xyz后三个是rgb
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
};
既然我们在顶点数组中增加了rgb信息,那么我们就要修改VAO中的属性指针的内容,让他理解我们顶点数组中数据的信息。我们再把1属性作为颜色,把偏移量等参数都调一下。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
小插曲开始
上面是按之前绘制三角形的方法写的,看了看例子里面的代码,发现了一个新写法,如下。
GLint posAttrib = glGetAttribLocation(shaderProgram, "aPos");
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(posAttrib);
···
直接获取对应变量的位置,通过这个位置来修改属性指针。然后着色器里面这样写就可以了,去掉location。
//layout(location = 0) in vec3 aPos;
in vec3 aPos;
小插曲结束
然后回到正题。shader源码也要做相应的修改,在顶点着色其中增加在1位置中获取颜色,也是一个vec3变量。然后增加一个输出,然后直接把颜色输出作为下一个着色器的输入。片元着色器把输入的颜色设置为顶点的颜色。
// 顶点着色器源码
const char* vertex_shader_source =
"#version 330 core \n"
"layout(location = 0) in vec3 aPos; \n"
"layout(location = 1) in vec3 aColor;\n"
"out vec3 Color; \n"
"void main(){ \n"
" gl_Position = vec4(aPos, 1.0); \n"
" Color = aColor; \n"
"} \n";
//片元(片段)着色器源码
const char* fragment_shader_source =
"#version 330 core \n"
"in vec3 Color; \n"
"out vec4 FragColor; \n"
"void main(){ \n"
" FragColor = vec4(Color, 1.0f);\n"
"} \n";
要注意的就是,片元着色器中的in vec3 Color是对应顶点着色器中的out vec3 Color。一开始我编译出错了,就是忘记在顶点着色其中设置输出是Color。
然后我们的结果就是如下啦,渐变的三角形(三个顶点的颜色分别是红绿蓝,顶点之间的颜色OpenGL会自动的帮我们插值运算)。
让我们来结合前面颜色的闪烁做一个闪烁的五彩斑斓的三角形吧。
同样是修改片元着色器,上面那一次是直接在外面算好rgb颜色然后传进来的,这次就不了。直接在shader中计算sin。
const char* fragment_shader_source =
"#version 330 core \n"
"uniform float time; \n"
"in vec3 Color; \n"
"out vec4 FragColor; \n"
"void main(){ \n"
" FragColor = vec4(Color.x * (sin(time) + 1)/2,Color.y * (sin(time) + 1)/2,Color.z * (sin(time) + 1)/2, 1.0f);\n"
"} \n";
我本以为glsl有自带time(或许是我的操作不对?),然而没有,那就用一个uniform在外头传一个吧。同样的操作。
/*传入时间*/
GLint timer = glGetUniformLocation(shaderProgram, "time");
glUniform1f(timer, glfwGetTime() * 2);
然后试了下发现把背景改为黑色更好看。
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
然后这是结果啦。
也可把再把变换颜色的函数改改,让他更有意思一些。
FragColor = vec4((sin(time*0.5 + Color.x) + 1)/2,(sin(time*1.5+Color.y * 2) + 1)/2,(sin(time*1.23+Color.z * 2.5) + 1)/2, 1.0f);
(有点像我的手机壁纸噢…。本来还想再做几个练习的,但是这次的内容也不少了,就放到下次吧。)