[OpenGL]深度测试和模板测试

文章目录[x]
  1. 1:深度测试
  2. 1.1:开启深度测试
  3. 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);
    }
};

StencilTest

这是一个用模板测试给物体描边的案例。

 

但是此时我们的UI出问题了,如下图

不仅UI上的文字消失了,连箱子也画在了UI的上层。

 

为了解决这个,需要修改一下UI绘制的代码。在 游戏循环中即将要绘制UI的上方添加两句代码即可

// 确保UI永远在最上层
glStencilFunc(GL_ALWAYS, 255, 0xFF);
glStencilMask(0xFF);
ImGUI::Begin("OpenGL Test")
// ...略

这下文字和排序都正常啦。

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像