Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一,其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/08%20Advanced%20GLSL/关于高级GLSL技巧的详细知识了解请看原教程,本篇旨在对Vires基于visual studio平台的编程思想与c++代码做纯Qt平台的移植,代码移植顺序基本按照原教程顺序,并附加一些学习心得,重在记录自身学习之用
Tip1: Joey De Vries的教程写的真是非常好呢,他本人可能想不到,他的中文版教程在中国的CSDN上有N多的“备份”,为什么有的人通篇转载他的教程时不能加上“转载”两个字呢。
Tip2: 博客最后的“Uniform缓冲对象”特别有用,特别推荐看Vries原教程的解析,强推!!!。
Tip3: 这节的Qt代码移植没有值得注意的地方,可直接将Vries的代码直接复制进来使用。
程序源代码链接:https://pan.baidu.com/s/1stvBQWxAI6Pu_GznF4Jc7Q 提取码:juh9
编译环境:Qt5.9.4
编译器:Desktop Qt5.9.4 MSVC2017 64bit
IDE:QtCreator
这节的内容在讲,关于着色器shader本身,一些有趣且有用的内建变量,比如gl_Position就是着色器的一个内建变量,负责在顶点着色器中,向其他着色器传递空间中点的坐标。因为这些内建变量都是着色器本身就具有的,所以在移植到Qt时,不需要结合Qt本身平台做一些函数的特殊化处理,直接粗暴复制代码即可。
当以GL_POINTS格式进行绘制点时,默认的点非常小,如左下图所示。
core->glDrawArrays(GL_POINTS, 0, 36);
而顶点着色器中的内建变量gl_PointSize就可以改变绘制的点的大小。
让我们简单粗暴一些,通过下属代码开启修改点大小的功能:
core->glEnable(GL_PROGRAM_POINT_SIZE);
pointsize.vert
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(){
gl_Position = projection * view * model * vec4(aPos, 1.0f);
gl_PointSize = 10.0f;
}
片段着色器中的这个变量,gl_FragCoord.z代表片段的深度,gl_FragCoord.x与gl_FragCoord.y代表窗口的坐标。怎么理解它的xy呢?举Vries的例子:
fragcoord.frag:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D ambientMap;
uniform float halfWindowWidth; //窗口宽度的一半 ,比如生成一个600*600的窗口,那我就传进来一个300
void main(){
if(gl_FragCoord.x < halfWindowWidth)
FragColor = texture2D(ambientMap, TexCoords);
else
FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
渲染结果就是,我在窗口的左半边用纹理渲染,右半边用纯红色渲染。这个方法在比较两种不同的光照效果时,真的很有用也很方便,真的。
这个变量与面剔除那节的知识有些联系,识别片段所在的面是正向面还是反向面。这里要注意的是立方体的顶点绘制顺序,必须按制定顺序绘制三角形,才能正确被GL识别。
还是举个简单例子:
frontfacing.frag
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D ambientMap1;
uniform sampler2D ambientMap2;
void main(){
if(gl_FrontFacing)
FragColor = texture2D(ambientMap1, TexCoords);
else
FragColor = texture2D(ambientMap2, TexCoords);
}
正向面用一个纹理,反向面用另一个纹理,当然,不要开启面剔除,反向面都让你剔了,还怎么映射纹理。
知道这个变量可以在片段着色器中,指定片段的深度就可以了。不同于gl_FragCoord.z,因为他只能读深度,而gl_FragDepth可以修改深度。
接口块的概念和c++的struct有些像,在用时,注意几个细节:
xxx.vert
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out VS_OUT
{
vec2 TexCoords;
} vs_out;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vs_out.TexCoords = aTexCoords;
}
xxx.frag
#version 330 core
out vec4 FragColor;
in VS_OUT
{
vec2 TexCoords;
} fs_in;
uniform sampler2D texture;
void main()
{
FragColor = texture(texture, fs_in.TexCoords);
}
你是否因为每生成一个着色器,都要重复设定他们的projection,view矩阵而烦恼?如下图所示,现在学习了Uniform Buffer Object,一切问题迎刃而解。只需要设置一次projection矩阵的Uniform变量,以后只要稍微修改一下着色器中的绑定点,就不用再管这些矩阵啦。
..................
ResourceManager::getShader("cube").use().setMatrix4f("projection", projection);
ResourceManager::getShader("cube").use().setMatrix4f("view", view);
ResourceManager::getShader("cube_nonLinear").use().setMatrix4f("projection", projection);
ResourceManager::getShader("cube_nonLinear").use().setMatrix4f("view", view);
ResourceManager::getShader("cube_linear").use().setMatrix4f("projection", projection);
ResourceManager::getShader("cube_linear").use().setMatrix4f("view", view);
ResourceManager::getShader("plane").use().setMatrix4f("projection", projection);
ResourceManager::getShader("plane").use().setMatrix4f("view", view);
ResourceManager::getShader("plane_nonLinear").use().setMatrix4f("projection", projection);
ResourceManager::getShader("plane_nonLinear").use().setMatrix4f("view", view);
ResourceManager::getShader("plane_linear").use().setMatrix4f("projection", projection);
ResourceManager::getShader("plane_linear").use().setMatrix4f("view", view);
ResourceManager::getShader("coordinate").use().setMatrix4f("projection", projection);
ResourceManager::getShader("coordinate").use().setMatrix4f("view", view);
.....................
这里简单说说怎么用,具体知识请转进Vries的教程,链接在本文第一行,真不想打这么多字了。
使用std140布局,布局规则如下,GLSL中的变量int,float,bool都被定义为4字节,而每4个字节将会用一个N
来表示。
Vries举了一个例子
当然,我们实际应用的情况没有这么复杂,针对projection与view矩阵,我们将他绑定至一个BindingPoint “0”上。
GLSL_BLUE.vert
#version 420 core
layout (location = 0) in vec3 aPos;
layout (std140, binding = 0) uniform Matrices{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main(){
gl_Position = projection * view * model * vec4(aPos, 1.0f);
}
这里亲测在 420 core的着色器中,直接指定BindingPoint即可。不要那么麻烦的用
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);
这俩函数了。
着色器搞定了,说说管理代码怎么写:
GLuint uboMatrices;
void initializeGL(){ //在QOpenGLWidget的初始化函数中
.............
core->glGenBuffers(1, &uboMatrices); //提前开辟ubo的内存空间
core->glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
/*
* 因为我们要绑定的是两个简单的4x4矩阵,所以声明8个4维向量的大小即可。
* 为什么不用sizeof(QMatrix4x4)呢,因为
* sizeof(QMatrix4x4) = 68; 多了4个字节应该是文件头之类的玩意吧,惊喜吧
* sizeof(QVector4D) = 16;
*/
core->glBufferData(GL_UNIFORM_BUFFER, 2 * 4 * sizeof(QVector4D), NULL, GL_STATIC_DRAW);
core->glBindBuffer(GL_UNIFORM_BUFFER, 0);
core->glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * 4 * sizeof(QVector4D));//将ubo绑定至BindingPoint上
}
void updateGL(){ //这个一个不断循环的更新函数
/*********** 处理Uniform Buffer相关参数 **************/
core->glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices); //向ubo缓冲中传递数据
core->glBufferSubData(GL_UNIFORM_BUFFER, 0, 4 * sizeof(QVector4D), &projection);
core->glBufferSubData(GL_UNIFORM_BUFFER, 4 * sizeof(QVector4D), 4 * sizeof(QVector4D), &view);
core->glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
然后,用四个不同颜色的着色器画四个cube立方体,Vries的例子就完成啦。
越写越放飞自我。。。。