本篇将紧跟上卷的脚步,继续讲解OpenGL中的一些高级概念,由于篇幅实在过于庞大,原本打算直接一卷写完,发现内容还是太多了,所以插入一个中卷(∩_∩)O),下卷放在后边再写,下卷主要涉及到一些高级光照部分如切线空间由来,法线贴图,视差贴图,阴影算法CSM,SSAO,间接光IBL,PBR等等,主要集中在GLSL的算法上。 而本篇则是针对OpenGL中的一些高级概念和用法做一些介绍,主要针对OpenGL的API上而言。 废话不多说,直接开始正文。 由于编写仓促,如有任何问题,欢迎指出。
19,帧缓冲对象FrameBufferObject
FBO概念:在OpenGL中,渲染管线中的顶点,纹理经过一系列过程之后,最终显示在2D屏幕设备上,渲染管线的最终目的地就是帧缓冲区。帧缓冲包括OpenGL使用的颜色缓冲区(color buffer),深度缓冲区(depth buffer),模板缓冲区(stencil buffer),它被储存在内中中。我们前面一直在使用的默认缓冲区由窗口系统GLFW为我们创建称为窗口系统提供的缓冲区,除此之外,OpenGL还允许我们手动创建自己的缓冲区,并将渲染结果重定向到这个缓冲区,称之为应用程序缓冲区。
创建FBO:和其他对象的创建方式一样,先创建帧缓冲对象,再将它绑定为激活的(Active)帧缓冲,做一些操作,之后解绑帧缓冲。
unsigned int fbo;
glGenFramebuffers(1, &fbo);
//还有用于只读GL_READ_FRAMEBUFFER和只写的GL_DRAW_FRAMEBUFFER的方式
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
一个完整的帧缓冲炫耀包含以下条件:
- 附加至少一个缓冲(颜色、深度或模板缓冲)。
- 至少有一个颜色附件(Attachment)。
- 所有的附件都必须是完整的(保留了内存)。
- 每个缓冲都应该有相同的样本数。
全部操作完以后,可以使用glCheckFramebufferStatus来检测
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
纹理附件:当把一个纹理附加到帧缓冲的时候,所有的渲染命令将会写入到这个纹理,就想它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有操作的结果将会存储在一个纹理图像中,我们之后可以很方便地使用它。 创建方式和普通纹理基本相同:
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
//暂时不绑定数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//附加到帧缓冲上
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
渲染缓冲对象:和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节,整数,像素等。渲染缓冲对象附加的好处是,它会将数据缓存为OpenGL原生的数据格式,它是为离屏渲染到帧缓冲优化过的。因为它的数据已经是原生的格式了,当写入或复制它的数据到其他缓冲区时是非常快的。所以交换缓冲这样的操作在使用渲染缓冲对象时非常快。我们在每个渲染迭代后使用的glfwSwapBuffers也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。渲染缓冲对象对这种操作非常完美.
20,立方体贴图Cubemap
基础概念:一种将多个纹理组合起来映射到纹理上的纹理类型。其实就是一个包含了6个2D纹理的图像,每个2D纹理都组成了立方体的一个面;立方体贴图有一个非常有用的特性,它可以通过一个方向向量来进行索引/采样,如下所示使用橘黄色的方向向量从立方体贴图上采样
注:方向向量的大小并不重要,只要提供了方向,OpenGL就会获取方向向量(最终)所击中的向量,并返回对应的采样纹理值。
创建立方体贴图:基本创建方式如下:
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
因为立方体贴图包含6个纹理,每个面1个,需要调用glTexImage2D函数6次,纹理目标(target)设置为立方体贴图的一个特定的面,而每个target正好是递增顺序
纹理设置方式也和普通2D有所不同:
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
shader中采样方式改变:
in vec3 textureDir; // 代表3D纹理坐标的方向向量
uniform samplerCube cubemap; // 立方体贴图的纹理采样器
void main()
{
FragColor = texture(cubemap, textureDir);
}
天空盒:游戏中的天空盒可以看作是一个包含了整个场景的大立方体,它包含周围环境的6个图像。
如果将上图折成1个立方体,就会得到一个完全贴图的立方体。
加载天空盒:和之前的2D纹理加载类似,不过需要重复6次,代表立方体贴图的不同面。
优化方式显示天空盒:欺骗深度缓冲,让深度缓冲认为天空盒有着最大深度1.0,只要前面有一个物体,深度测试就会失败。透视除法是在顶点着色器中执行的,执行完后gl_Position的xyz坐标除以w分量,除完结果以后z分量等于顶点的深度值。利用这些信息,将输出的z分量直接设置为w分量,这样透视除法过后将永远等于1.0(z/w=1.0)
反射:表现为反射周围的环境,即根据观察者的视角,物体的颜色或多或少等于它的环境。例如最常见的镜子,会根据视角不同反射环境信息不同。
片段shader代码
#version 330 core
out vec4 FragColor;
in vec3 Normal;
in vec3 Position;
uniform vec3 cameraPos;
uniform