参考网址:LearnOpenGL 中文版

第四章 高级OpenGL

4.1 深度测试

4.1.1 深度缓冲

1、深度缓冲用来防止被阻挡的面渲染到其它面的前面,由窗口系统自动创建,在每个片段中储存了它的深度值。当启用深度测试时,OpenGL会将一个片段的深度值与深度缓冲的深度值进行对比。如果通过了深度测试,则在深度缓冲中储存该片段的z值;如果没有通过,则会丢弃该片段。

2、深度缓冲创建于片段着色器运行之后,在屏幕空间中运行,与glViewport所定义的视口密切相关。屏幕空间坐标可使用GLSL内建变量gl_FragCoord从片段着色器中直接访问。gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(原点位于左下角),z分量为片段深度值。

3、现在的GPU都有提前深度测试,允许深度测试在片段着色器之前运行,这就要求片段着色器不能写入片段的深度值,否则无法进行提前深度测试。

glEnable(GL_DEPTH_TEST);//启用深度测试
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//每次渲染前清除深度缓冲

4、某些情况下需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲。因此需要一个只读的深度缓冲,将它的深度掩码设置为GL_FALSE就可以了。

glDepthMask(GL_FALSE);

4.1.2 深度测试函数

1、OpenGL允许修改深度测试中使用的比较运算符,控制什么时候该丢弃一个片段,什么时候去更新深度缓冲,调用glDepthFunc函数来设置比较运算符:

glDepthFunc(GL_LESS);

2、比较运算符
GL_ALWAYS 永远通过深度测试
GL_NEVER 永远不通过深度测试
GL_LESS 在片段深度值小于缓冲的深度值时通过测试
GL_EQUAL 在片段深度值等于缓冲区的深度值时通过测试
GL_LEQUAL 在片段深度值小于等于缓冲区的深度值时通过测试
GL_GREATER 在片段深度值大于缓冲区的深度值时通过测试
GL_NOTEQUAL 在片段深度值不等于缓冲区的深度值时通过测试
GL_GEQUAL 在片段深度值大于等于缓冲区的深度值时通过测试

3、使用GL_ALWAYS模拟没有启用深度测试时所得到的结果,最后绘制的片段将会总是会渲染在之前绘制片段的上面。


4.1.3 深度值精度

1、深度缓冲的深度值介于[0, 1]范围,而观察空间的z值可能是投影平截头体的近平面和远平面之间的任何值,需要一种方式将观察空间的z值变换到[0, 1]范围之间,其中的一种方式就是线性变换。


2、在线性方程中,1000单位远的物体和1单位远的充满细节的物体使用了相同的精度,这显然是不合理的。因此,使用与 1/z 成正比的非线性深度方程,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,占50%的float精度。在50.0和100.0之间的z值将会只占2%的float精度。


3、所以深度缓冲中的值在屏幕空间中不是线性的,深度缓冲为0.5的顶点的z值实际上非常接近近平面。这个变换方程是嵌入在投影矩阵中的,因此在透视矩阵应用之前在观察空间中是线性的。投影矩阵

4.1.4 深度缓冲可视化

1、将片段着色器内建向量gl_FragCoord的z值输出为颜色:

void main()
{FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}

2、能注意到几乎所有东西都是白色的,看起来所有的深度缓冲都是1.0。因为屏幕空间的深度缓冲是非线性的,片段的颜色值变化也是非线性的,会随着距离增大而迅速增加。所以几乎所有的顶点的深度值都是接近于1.0的。如果我们小心地靠近物体,颜色会渐渐变暗,显示它们的z值在逐渐变小:

4.1.5 深度冲突

1、当两个平面或者三角形非常紧密地平行排列在一起时,深度缓冲没有足够的精度来决定两个形状哪个在前面,结果就是这两个形状不断地在切换前后顺序,这个现象叫做深度冲突。

2、防止深度冲突

  • 第一个是不要把多个物体摆得太靠近,通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。
  • 第二个技巧是尽可能将近平面设置远一些,将近平面远离观察者,整个平截头体有着更大的精度。然而,将近平面设置太远将会导致近处的物体被裁剪掉。
  • 第三个是使用更高精度的深度缓冲。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。

4.2 模板测试

4.2.1 模板缓冲

1、当片段着色器处理完一个片段之后,先执行模板测试,丢弃某些片段后,被保留的片段进入深度测试。

2、模板测试根据模板缓冲进行,在模板缓冲中,每个模板值是8位的,所以每个片段有256种的模板值。将模板值设置为特定值,然后当某个片段有某个模板值的时候,就可以选择丢弃或是保留这个片段了。

3、例如,在模板缓冲中使用1填充了一个空心矩形,场景中的片段将会只在片段的模板值为1的时候会被渲染(其它的都被丢弃了)。

4、使用模板缓冲的大体步骤如下:

  • 启用模板缓冲的写入;
  • 渲染物体,更新模板缓冲的内容,将模板缓冲设为特定值;
  • 禁用模板缓冲的写入;
  • 在接下来渲染其它物体时,根据模板缓冲的内容丢弃特定的片段。
//启用模板测试,清除模板缓冲
glEnable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

5、利用glStencilMask函数来启用/禁用模板写入,该函数设置了一个位掩码,与将要写入缓冲的模板值进行AND运算。当位掩码所有位都为1,即开启模板缓冲写入;当位掩码所有位为0x00,写入缓冲的所有模板值最后都会变成0,即禁用模板缓冲写入。

glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)

4.2.2 模板函数

1、glStencilFunc计算模板测试应该通过还是失败,glStencilOp说明如何更新模板缓冲。

2、glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:

  • func:设置模板测试函数,应用到模板缓冲的模板值和ref值上。可用的选项有:GL_NEVERGL_LESSGL_LEQUALGL_GREATERGL_GEQUALGL_EQUALGL_NOTEQUALGL_ALWAYS
  • ref:设置模板测试的参考值,模板缓冲的模板值将会与这个值进行比较。
  • mask:设置一个掩码,它将会与参考值和储存的模板值在测试之前进行与(AND)运算。初始情况下所有位都为1。
//只要一个片段的模板值等于参考值1,片段将会通过测试并被绘制,否则会被丢弃。
glStencilFunc(GL_EQUAL, 1, 0xFF)

3、glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项:

  • sfail:模板测试失败时采取的行为。
  • dpfail:模板测试通过,但深度测试失败时采取的行为。
  • dppass:模板测试和深度测试都通过时采取的行为。

默认情况下是设置为(GL_KEEP, GL_KEEP, GL_KEEP),不论任何测试的结果如何,模板缓冲都会保留它的值。如果你想写入模板缓冲的话,至少对其中一个选项设置不同的值。

4.2.3 物体轮廓

1、使用模板测试可以完成物体轮廓的绘制,轮廓的步骤如下:

  • 在绘制物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1;
  • 渲染物体;
  • 禁用模板写入以及深度测试;
  • 将每个物体放大一点点;
  • 使用一个不同的片段着色器,输出一个单独的(边框)颜色;
  • 再次绘制放大版本的物体,但只在它们片段的模板值不等于1时才绘制,也就是物体的边框的位置。使用模板缓冲丢弃了放大版本中属于原物体片段的部分。
  • 再次启用模板写入和深度测试。

2、片段着色器,输出一个边框颜色

void main()
{FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}

3、启用模板测试,并设置测试通过或失败时的行为。如果其中的一个测试失败了,仅仅保留当前储存在模板缓冲中的值。如果都通过了,将储存的模板值设置为参考值。

glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

4、将模板缓冲清除为0,绘制两个箱子,对箱子中所有绘制的片段,将模板值更新为1。通过GL_ALWAYS模板测试函数,箱子的每个片段都会将模板缓冲的模板值更新为1。因为片段永远会通过模板测试,在绘制片段的地方,模板缓冲会被更新为参考值。

glStencilFunc(GL_ALWAYS, 1, 0xFF); // 所有的片段都应该更新模板缓冲
glStencilMask(0xFF); // 启用模板缓冲写入
normalShader.use();
DrawTwoContainers();

5、绘制放大的箱子,禁用模板缓冲的写入,将模板函数设置为GL_NOTEQUAL,保证只绘制箱子上模板值不为1的部分,即只绘制箱子在之前绘制的箱子之外的部分。注意:也禁用了深度测试,让放大的箱子,即边框,不会被地板所覆盖。完成之后重新启用深度缓冲。

glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // 禁止模板缓冲的写入
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();

整体流程如下所示:

//设置模型矩阵
glm::mat4 viewMat = camera.GetViewMatrix();
glm::mat4 modelMat = glm::mat4(1.0f);
glm::mat4 projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
//绑定着色器
myShader->use();
myShader->setMat4f("view", viewMat);
myShader->setMat4f("projection", projMat);
//禁用模板缓冲写入
glStencilMask(0x00);
//绘制地板
glBindVertexArray(planeVAO);
glBindTexture(GL_TEXTURE_2D, planeTexture);
myShader->setMat4f("model", glm::mat4(1.0f));
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);//开启模板缓冲写入,渲染时写入缓冲
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
// 绘制第一个箱体的主体
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
modelMat = glm::translate(modelMat, glm::vec3(-1.0f, 0.0f, -1.0f));
myShader->setMat4f("model", modelMat);
glDrawArrays(GL_TRIANGLES, 0, 36);
// 绘制第二个箱体的主体
modelMat = glm::mat4(1.0f);
modelMat = glm::translate(modelMat, glm::vec3(2.0f, 0.0f, 0.0f));
myShader->setMat4f("model", modelMat);
glDrawArrays(GL_TRIANGLES, 0, 36);///禁用深度缓冲写入,进行模板测试,禁用深度测试
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
float scale = 1.1f;
glDisable(GL_DEPTH_TEST);
//绑定着色器
myShader_SingleColor->use();
myShader_SingleColor->setMat4f("view", viewMat);
myShader_SingleColor->setMat4f("projection", projMat);
//绘制第一个箱体的轮廓
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
modelMat = glm::mat4(1.0f);
modelMat = glm::translate(modelMat, glm::vec3(-1.0f, 0.0f, -1.0f));
modelMat = glm::scale(modelMat, glm::vec3(scale, scale, scale));
myShader_SingleColor->setMat4f("model", modelMat);
glDrawArrays(GL_TRIANGLES, 0, 36);
// 绘制第二个箱体的轮廓
modelMat = glm::mat4(1.0f);
modelMat = glm::translate(modelMat, glm::vec3(2.0f, 0.0f, 0.0f));
modelMat = glm::scale(modelMat, glm::vec3(scale, scale, scale));
myShader_SingleColor->setMat4f("model", modelMat);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
//绘制结束,开启模板缓冲写入,更改模板函数,开启深度测试
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);

4.3 混合

4.3.1 透明度

1、混合是实现物体透明度的一种技术。透明就是说一个物体的颜色是本身的颜色和它背后其它物体的颜色的不同强度混合。

2、透明的物体可以是完全透明的,或者是部分透明的。透明度是通过它颜色的alpha值来决定的。当alpha值为0.0时物体将会是完全透明的。当alpha值为0.4时,物体的颜色有40%是来自物体自身的颜色,60%来自背后物体的颜色。

4.3.2 丢弃片段

1、有些图片只需要根据纹理颜色值,只显示一部分图像。要么是完全不透明的,要么是完全透明的。比如说草,将一个草的纹理贴在一个2D四边形上,然后将这个四边形放到场景中。只显示草纹理的某些部分,而忽略剩下的部分。

2、加载带有alpha通道的纹理图片,在片段着色器中纹理采样时获取纹理4个颜色分量:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
void main()
{FragColor = texture(texture1, TexCoords);
}

3、需要创建另外一个VAO,填充VBO,设置正确的顶点属性指针,绘制四边形,并贴上草的纹理。

 vector<glm::vec3> vegetation {glm::vec3(-1.5f, 0.0f, -0.48f),glm::vec3( 1.5f, 0.0f, 0.51f),glm::vec3( 0.0f, 0.0f, 0.7f),glm::vec3(-0.3f, 0.0f, -2.3f),glm::vec3 (0.5f, 0.0f, -0.6f)};glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);
for(unsigned int i = 0; i < vegetation.size(); i++)
{model = glm::mat4(1.0f);model = glm::translate(model, vegetation[i]);               shader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 6);
}


4、通过调用GLSL中有discard命令,可丢弃指定片段。在片段着色器中检测一个片段的alpha值是否低于某个阈值,如果是的话,则丢弃这个片段,就好像它不存在一样:

void main()
{             vec4 texColor = texture(texture1, TexCoords);if(texColor.a < 0.1)discard;FragColor = texColor;
}

5、当采样纹理的边缘的时候,如果环绕方式位GL_REPEAT,OpenGL会对边缘的值和纹理下一个重复的值进行插值。由于我们使用了透明值,纹理图像的顶部将会与底部边缘的纯色值进行插值,这样的结果是一个半透明的有色边框。要想避免这个,每当你alpha纹理的时候,请将纹理的环绕方式设置为GL_CLAMP_TO_EDGE

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format==GL_RGBA ? GL_CLAMP_TO_EDGE:GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);

4.3.3 混合

1、OpenGL中的通过混合方程来实现:

C r e s u l t = C s o u r c e ∗ F s o u r c e + C d e s t i n a t i o n ∗ F d e s t i n a t i o n C_{result}=C_{source}*F_{source}+C_{destination}*F_{destination} Cresult​=Csource​∗Fsource​+Cdestination​∗Fdestination​

C s o u r c e C_{source} Csource​:源颜色向量。未绘制,源自纹理的颜色向量。
C d e s t i n a t i o n C_{destination} Cdestination​:目标颜色向量。已经绘制,当前储存在颜色缓冲中的颜色向量。
F s o u r c e F_{source} Fsource​:源因子值。指定了alpha值对源颜色的影响。
F d e s t i n a t i o n F_{destination} Fdestination​:目标因子值。指定了alpha值对目标颜色的影响

2、将半透明的绿色方形绘制在红色方形之上,绿色方形是源颜色向量,红色方形是目标颜色(它先在颜色缓冲中)。将 F s o u r c e F_{source} Fsource​设置为源颜色向量的alpha值,目标因子值 F d e s t i n a t i o n F_{destination} Fdestination​应该为 1 − a l p h a 1-alpha 1−alpha。如果绿色方形对最终颜色贡献了60%,那么红色方块应该对最终颜色贡献了40%。

3、通过glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源和目标因子。为了获得之前两个方形的混合结果,我们需要使用源颜色向量的alpha作为源因子,使用1−alpha作为目标因子:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

4.3.4 渲染半透明纹理

1、启用混合,并设定相应的混合函数:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

2、片段着色器中,不需要丢弃片段,每当OpenGL渲染了一个片段时,它都会将当前片段的颜色和当前颜色缓冲中的片段颜色根据alpha值来进行混合。由于窗户纹理的玻璃部分是半透明的,就能通窗户中看到背后的场景了。

void main()
{             FragColor = texture(texture1, TexCoords);
}


3、从图中看出,最前面窗户的透明部分遮蔽了背后的窗户,这是由于深度缓冲不会检查片段是否是透明的,透明的部分会一样写入到深度缓冲中,导致背后的窗户在深度测试中会被丢弃。
而箱体不会被遮住的原因是,先渲染了箱体,后渲染半透明的窗户。因此,要想保证窗户中能够显示它们背后的窗户,必须先手动将窗户按照最远到最近来排序,再按照顺序渲染。
当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:

  • 先绘制所有不透明的物体。
  • 对所有透明的物体排序。
  • 按顺序绘制所有透明的物体。

4、排序透明物体的方法是,首先通过计算摄像机和物体之间的距离,然后将距离和物体位置向量存储到map数据结构中,它的距离作为键,位置向量作为键值,map会根据键对它的键值排序,即根据距离从小到大,对位置向量进行排序。

std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++)
{float distance = glm::length(camera.Position - windows[i]);sorted[distance] = windows[i];
}

5、排序后的容器对象,它根据distance键值从低到高储存了每个窗户的位置。在渲染的时候,以逆序(从远到近)从map中获取值,之后以正确的顺序绘制对应的窗户。

for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
{model = glm::mat4();model = glm::translate(model, it->second);              shader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 6);
}

OpenGL学习笔记(七)-深度测试-模板测试-混合相关推荐

  1. 迪文屏幕T5L平台学习笔记七:RS485测试

    由于串口通信距离近,且容易受到干扰,最近改为RS485通信方案,迪文屏幕DMG10600K070_03WTC正好也支持RS485通信,把调试过程记录下. 1.首先看下数据手册: 串口5支持RS485通 ...

  2. OpenGL学习笔记(八):进一步理解VAO、VBO和SHADER,并使用VAO、VBO和SHADER绘制一个三角形

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7888 ...

  3. 【OpenGL学习笔记⑥】——3D变换【旋转的正方体 实现地月系统 旋转+平移+缩放】

    ✈️ 文章目录 零. 成果预览图 一.3D立方体的顶点数组 二.纹理旋转 三.纹理缩放 四.画n个3D图形 五.轨道的数学公式 六.深度缓冲(Z 缓冲) 七.完整代码 八.参考附录: 神器的正方体 ☁ ...

  4. OpenGL学习笔记(一)绘制点线面及多面体

    OpenGL学习笔记(一)绘制点线面及多面体 绘制点线面 #include <iostream> #include <GL/GLUT.h> #define PI 3.14159 ...

  5. 【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】

    ✅ 重点参考了 LearnOpenGL CN 的内容,但大部分知识内容,小编已作改写,以方便读者理解. 文章目录 零. 成果预览图 一. 光照原理与投光物的配置 1.1 光照原理 1.2 投光物 二. ...

  6. 【K210】K210学习笔记七——使用K210拍摄照片并在MaixHub上进行训练

    [K210]K210学习笔记七--使用K210拍摄照片并在MaixHub上进行训练 前言 K210准备工作 K210如何拍摄照片 准备工作 拍摄相关代码定义 用K210拍摄到的照片在MaixHub平台 ...

  7. window的dos命令学习笔记 七

    文章目录 一.dos历史学习笔记(后期整合到这里,我想能学到这里的应该不多了,嘿嘿,加油) 二.执行状态返回值(`%errorlevel%`,和shell中`$?`相似): 三.视窗 1.color ...

  8. OpenGL学习笔记(一):环境搭建、三维空间坐标系理解以及OpenGL的基本使用

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7866 ...

  9. Typescript 学习笔记七:泛型

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

最新文章

  1. 清华校友打造Python调试神器
  2. 小目标检测的一些问题,思路和方案
  3. [LeetCode]--290. Word Pattern
  4. unix shell(壳)的简单实现
  5. python 程序开机自启动,亲测可用
  6. map和mapValues的纠纷
  7. Java io流学习总结(三)
  8. 使用了未经检查或不安全的操作_违规操作就是对家庭的不负责!电气安全员提醒你的安全常识...
  9. hdu 1209 clocks wrong answer 我的错误代码(没审好题唉,角度一样后,还要按小时排序。...
  10. asp.net mvc 之旅—— 第一站 从简单的razor入手
  11. get方式乱码post方式不会乱码原因
  12. 一个很好的makefile例子(经典)
  13. 3d模型多怎么优化_硕士生金属3D打印斯特林发动机模型,使用3DXpert增长增材制造经验...
  14. 2021年最新程序员培训机构排名,学习前避坑必看
  15. Android 微信登陆
  16. 【干货】JavaScript 资源大全
  17. 装的机械硬盘计算机里没有反应,固态硬盘和机械硬盘运行打开我的计算机图标,有时候读取没有响应,单独用固态硬盘是没问题的 ,机械硬盘也测试了,没有坏道什么的,始终找不到原因...
  18. 【T+】畅捷通T+存货档案批量修改存货属性
  19. 跟我一起编辑直播源码,直播app代码怎么写
  20. 《微积分基础》学习(一)

热门文章

  1. 成都百知教育:做Shopee店铺没有方向,这3大层级必须理清!
  2. Adobe flash player 因过期而遭到阻止 解决办法
  3. 为Array对象添加一个去除重复项的方法
  4. Nexus搭建Maven私服全攻略一:认识Nexus与索引
  5. 一份Hive面试题及答案
  6. mysql sql loader_Data Loader
  7. 浅谈嵌入式系统的应用场景
  8. 课程在线学习的小程序师生教学辅导
  9. 工业软件+无代码开发,国产软件崛起正当时
  10. ExpandableListView讲解