文章目录[x]
- 1:深度测试
- 1.1:开启深度测试
- 2:模板测试
深度测试
在前面的 [OpenGL]MVP矩阵变换(坐标变换) 中,我们开启了深度测试来绘制一个3D箱子。因为当时发现箱子绘制出来后,其箱子的表面遮挡关系不正确。
在OpenGL中,深度测试默认是关闭的,此时物体的排序是使用的 画家算法 ,它就像画家画画一样,后画的景色永远会挡住先画的景色,如果不使用深度测试而要想使我们的物体遮挡关系正确,就必须每次绘制的时候都要对物体进行一次排序。
但即便如此,画家算法依然无法解决物体穿插的情况。如下图,此时无论怎么排序,都是不正确。
深度测试,其实就是一个与视口大小相同的数组(16,24,32位的float),里面存储了每个片元的深度信息(深度值),当执行完模板测试后(模板测试在片元着色器之后执行)就开始执行深度测试,OpenGL会用新的片元与深度缓冲里的深度值进行比较,常规情况下,当新的片元深度值比深度缓冲里的深度值小时,就用新片元的深度值替换掉深度深度缓冲中的深度值,否则不予通过,即不绘制在屏幕上。
开启深度测试
GLCall(glEnable(GL_DEPTH_TEST));
GLCall(glDepthFunc(GL_LESS)); // 小于当前zbuffer中的值才替换
class DepthTest : public Basic
{
public:
virtual void Setup() override
{
Basic::Setup();
// GL_ALWAYS 永远通过深度测试
// GL_NEVER 永远不通过深度测试
// GL_LESS 在片段深度值小于缓冲的深度值时通过测试
// GL_EQUAL 在片段深度值等于缓冲区的深度值时通过测试
// GL_LEQUAL 在片段深度值小于等于缓冲区的深度值时通过测试
// GL_GREATER 在片段深度值大于缓冲区的深度值时通过测试
// GL_NOTEQUAL 在片段深度值不等于缓冲区的深度值时通过测试
// GL_GEQUAL 在片段深度值大于等于缓冲区的深度值时通过测试
glDepthFunc(GL_ALWAYS);
}
};
当把深度测试的方法设置为 GL_ALWAYS
后,所有的片元都能通过深度测试,就会出现上图的情况,最后画的地板将先画的两个箱子挡住了,且箱子自身的深度表现也异常了。
模板测试
class StencilTest : public Basic
{
// 使用模板缓冲的一般步骤
// 1.启用模板缓冲的写入。
// 2.渲染物体,更新模板缓冲的内容。
// 3.禁用模板缓冲的写入。
// 4.渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。
Shader* m_OutlineShader;
public:
virtual void Setup() override
{
Basic::Setup();
m_OutlineShader = new Shader("SandBox/15_DepthTestAndStencilTest/Outline.shader");
// GL_KEEP 保持当前储存的模板值
// GL_ZERO 将模板值设置为0
// GL_REPLACE 将模板值设置为glStencilFunc函数设置的ref值
// GL_INCR 如果模板值小于最大值则将模板值加1
// GL_INCR_WRAP 与GL_INCR一样,但如果模板值超过了最大值则归零
// GL_DECR 如果模板值大于最小值则将模板值减1
// GL_DECR_WRAP 与GL_DECR一样,但如果模板值小于0则将其设置为最大值
// GL_INVERT 按位翻转当前的模板缓冲值
glEnable(GL_DEPTH_TEST);
// 当模板测试失败时,当模板测试通过但是深度测试失败时,当模板测试和深度测试都通过时
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
virtual void Render() override
{
glStencilMask(0x00); // 绘制地板时不使用模板缓冲
m_TestScene->Render();
// 1.在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
// 第一个参数语义和深度测试类似,GL_ALWASY代表永远通过模板测试
// 第二个参数是参考值,新的模板值会与这个值
// 第三个参数是掩码,取值范围是[0, 255]
glStencilFunc(GL_ALWAYS, 1, 0xFF); // 所有的片段都应该更新模板缓冲
glStencilMask(0xFF); // 启用模板缓冲写入
// 2.渲染物体。
float height = 1.0f;
m_Texture[0]->Bind(0);
m_Shader[0]->Bind();
m_Shader[0]->SetInt("uTexture1", 0);
m_Shader[0]->SetMat4f("view", m_Camera->GetViewMatrix());
m_Shader[0]->SetMat4f("projection", m_Camera->GetProjectionMatrix());
glm::mat4 model = glm::identity<glm::mat4>();
model = glm::translate(model, glm::vec3(-1.0f, height, -1.0f));
m_Shader[0]->SetMat4f("model", model);
m_Renderer.Draw(m_VA[0], 36, m_Shader[0]);
model = glm::identity<glm::mat4>();
model = glm::translate(model, glm::vec3(2.0f, height, 0.0f));
m_Shader[0]->SetMat4f("model", model);
m_Renderer.Draw(m_VA[0], 36, m_Shader[0]);
// 3.禁用模板写入以及深度测试。
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // 只要不等于1的片段都不通过模板测试
glDisable(GL_DEPTH_TEST); // 禁用深度测试
// 4.将每个物体放大一点点。
// 5.使用一个不同的片段着色器,输出一个单独的(边框)颜色。
// 6.再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
float scale = 1.05f;
m_OutlineShader->Bind();
m_OutlineShader->SetMat4f("view", m_Camera->GetViewMatrix());
m_OutlineShader->SetMat4f("projection", m_Camera->GetProjectionMatrix());
model = glm::identity<glm::mat4>();
model = glm::translate(model, glm::vec3(-1.0f, height, -1.0f));
model = glm::scale(model, glm::vec3(scale, scale, scale));
m_OutlineShader->SetMat4f("model", model);
m_Renderer.Draw(m_VA[0], 36, m_OutlineShader);
model = glm::identity<glm::mat4>();
model = glm::translate(model, glm::vec3(2.0f, height, 0.0f));
model = glm::scale(model, glm::vec3(scale, scale, scale));
m_OutlineShader->SetMat4f("model", model);
m_Renderer.Draw(m_VA[0], 36, m_OutlineShader);
// 7.再次启用模板写入和深度测试。
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
}
};
这是一个用模板测试给物体描边的案例。
但是此时我们的UI出问题了,如下图
不仅UI上的文字消失了,连箱子也画在了UI的上层。
为了解决这个,需要修改一下UI绘制的代码。在 游戏循环中即将要绘制UI的上方添加两句代码即可
// 确保UI永远在最上层
glStencilFunc(GL_ALWAYS, 255, 0xFF);
glStencilMask(0xFF);
ImGUI::Begin("OpenGL Test")
// ...略
这下文字和排序都正常啦。