【OpenGL】笔记二十、模板测试

1. 流程

当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。

一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。

利用模板测试,我们可以限制渲染区域、渲染阴影,渲染物体粗轮廓等,下面是一个限制区域的例子:

在这里插入图片描述

1.1 模板测试

同样的,我们可以启用GL_STENCIL_TEST来启用模板测试:

glEnable(GL_STENCIL_TEST);

而且我们也需要在每次迭代之前清除模板缓冲(为0)。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

和深度测试的glDepthMask函数一样,模板缓冲也有一个类似的函数。glStencilMask允许我们设置一个位掩码(Bitmask),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码所有位都为1,不影响输出,但如果我们将它设置为0x00,写入缓冲的所有模板值最后都会变成0.这与深度测试中的glDepthMask(GL_FALSE)是等价的。

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

1.2 模板函数

和深度测试的glDepthfunc函数一样,模板缓冲也有两个类似的函数。

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

func:设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
ref:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
mask:设置一个掩码,它将会与参考值和储存的模板值都进行进行与(AND)运算,两个的结果再进行比较。初始情况下所有位都为1。

但是glStencilFunc仅仅描述了OpenGL应该对模板缓冲内容做什么,而不是我们应该如何更新缓冲。这就需要glStencilOp这个函数了。

glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:

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

每个选项都可以选用以下的其中一种行为:
在这里插入图片描述

1.3 物体轮廓

接下来利用模板测试做一个实用的功能,那就是描绘出物体的轮廓,当你想要在策略游戏中选中一个单位进行操作的,想要告诉玩家选中的是哪个单位的时候,这个效果就非常有用了。

详细步骤如下:

1.首先清除模板缓存

    glEnable(GL_DEPTH_TEST);
    while (!glfwWindowShouldClose(window))
    {
        // 输入
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

2.在没有开启模板测试前先将其他物体(比如灯)先渲染出来,保证不受影响

        // 渲染指令
        float timeValue = glfwGetTime();
        deltaTime = timeValue - lastFrame;
        lastFrame = timeValue;

        view = projection = glm::mat4(1.0f);
        projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

        view = camera.GetViewMatrix();
        
        light.view = view;
        light.projection = projection;
        light.Draw(lightShader);

3.开启模板测试,在同时通过模板和深度测试以后就把箱子渲染出来,并且把通过测试的区域的模板缓存设为参考值1

        glEnable(GL_STENCIL_TEST);
        glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);
        box.view = view;
        box.projection = projection;
        box.Draw(ourShader, camera, timeValue);

4.改变模板测试的规则,使它只有在缓存值不为1的时候通过测试,而且通过以后不更新缓存(这里的例子后面不渲染物体了,所以更不更新都无所谓),然后用单色着色器画出放大1.05倍的箱子(那0.05倍的突出处缓存为0,所以单色着色器只会渲染这部分,它就成了边框)

        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00);

        box.Draw(singleShader, camera, timeValue, 1.05f);

5.最后关闭模板测试,进行下一轮渲染

        glDisable(GL_STENCIL_TEST);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

效果:
在这里插入图片描述

那么进一步的,我们可不可以让这个边框即使被遮挡住了也能被显示出来呢?当然可以:

我们稍作修改,让箱子即使没通过深度测试也能更新模板,这样,被灯遮住的箱子部分的模板缓存也被更新为1了,然后我们在用单色着色器渲染边框的时候关闭深度测试,这样我们就能即透过灯看到边框,也不用担心被灯遮住的箱子部分全部被渲染为红色了

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

        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);

        box.view = view;
        box.projection = projection;
        box.Draw(ourShader, camera, timeValue);

        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        //glStencilMask(0x00);
        glDisable(GL_DEPTH_TEST);
        box.Draw(singleShader, camera, timeValue, 1.05f);
        glEnable(GL_DEPTH_TEST);
        glDisable(GL_STENCIL_TEST);

效果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ycr的帐号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值