【OpenGL】模板测试(StencilTest)

目录

一、模板测试(StencilTest)的引入

二、相关API

1、模板测试的开启/关闭

2、模板测试通过/失败后

3.效果实现

三、模板测试的封装


前言:总结于b站赵新政老师的OpenGL课程,含个人理解,如有错误,很高兴大家可以指出,我会尽快修改的OwO(赵新政的个人空间-赵新政个人主页-哔哩哔哩视频

一、模板测试(StencilTest)的引入

现在,我们想要为一个物体进行"勾边",让他更为突出。

该效果实际上是由两个盒体进行实现,首先先渲染box本体,然后边框特效则是将原本的盒体扩大一点,替换成白色材质进行渲染。

可是呢,当我们进行渲染的时候,很显然后渲染的边框效果会遮盖住前面渲染的本体。而为了解决这个问题,就要确保边框效果不会覆盖掉本体内容。因而,就出现了模板测试(StencilTest)

首先了解模板缓冲(StencilBuffer):与深度缓冲相似(一张画布),记录了当前绘制的物体覆盖了哪些像素区域(8bit,一般而言8bit就足够使用了)。

借助模板缓冲,可以知道那些像素被绘制过,从而指定规则,只有通过本规则测试的像素才能绘制出来。

在上一物体进行渲染的时候,我们将会根据颜色缓冲 更新模板缓冲 ,在进行下一个物体的渲染的时候,会将当前的颜色缓冲模板缓冲进行比较,根据设置的模板测试类型来决定是否需要对当前颜色进行渲染。

二、相关API

1、模板测试的开启/关闭

void glEnable(GL_STENCIL_TEST);//开启模板测试

void glDisable(GL_STENCIL_TEST);//关闭模板测试

模板测试条件:设置当前渲染Mesh通过模板测试的条件

void glStencilFunc(GLenum func,GLint ref,GLuint mask);

++func:使用模板缓存中的数值与ref如何对比才算通过测试;++

++ref:参考数值++

++mask:缓存数值与参考值进行比较前,都要先与mask做AND(&)操作++

func:

GL_NEVER:总是不允许通过测试

GL_ALWAYS:总是通过测试

GL_EQUAL:相等才通过测试

GL_NOTEQUAL:不等才通过测试

GL_LESS:小于才通过测试

GL_GREATER:大于才通过测试

假如我们使用GL_EQUAL

那么假如当前的模板缓冲中的值为value=3,mask=1。

此时如果我们的模板数值ref=1

那么value&mask=1 , ref&mask=1

两个结果相同,那么当前通过了模板测试,可以进行渲染。

2、模板测试通过/失败后

void glStencilOp(GLenum sfail,GLenum zfail,GLenum zpass);

  1. sfail:模板测试失败的时候执行什么操作
  2. zfail:模板测试通过但深度测试失败时执行的操作
  3. zpass:模板测试和深度测试都通过时执行的操作

GL_KEEP:保持当前的模板值

GL_ZERO:将模板值设置为0

GL_REPLACE:将模板值设置为glStencilFunc中指定的参考值

GL_INCR:如果当前模板值小于最大值,则将模板值+1

GL_INCR_WRAP:类似于GL_INCR,但是如果增加后超过了最大值则会绕回0。

GL_DECR:如果当前模板值大于0,则将模板值减1

GL_DECR_WRAP:类似于GL_DECR,但是如果减小后小于0则会绕回最大值。

GL_INVERT:按位翻转当前的模板值

类似于深度缓冲写入,我们也可以决定是否对模板缓冲进行写入。

·开启模板测试情况下,禁止模板缓冲写入

void glStencilMask(GLuint mask)

mask:掩码的每一位对应于模板缓冲区中的每一位,,每当一个位是1时,模板缓冲区的相应位可以被写入;当一个位是0时,相应位不能被写入。

0x00:任何bit位都不允许写入

0xFF:任何bit位都允许写入

举例来讲,如果当前模板缓冲的参考值ref=3,且已经通过了模板测试,若mask=2,则其进行模板缓冲写入的时候,当前新的模板缓冲值=ref&mask=2,所以,如果mask=0x00,那么模板缓冲写入始终为0,而当mask=0xFF的时候,就会直接写入ref。

3.效果实现

因而,想要实现上述的效果,对于box本体而言,其应当开启模板测试之后,设置总是可以通过模板测试(GL_ALWAYS),并设置好ref值,模板测试mask和模板缓冲写入的mask设置为0xFF,其通过模板测试和深度测试的操作应当设置为替换(GL_REPLACE),其余为保持(GL_KEEP)

cpp 复制代码
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS,1,0xFF);
glStencilMask(0xFF);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);

而对于白色的框体而言,就应该在开启模板测试之后设置只有当模板测试值不等(GL_NOTEQUAL)的时候才进行渲染,ref值取1,模板测试的mask设置为0xFF,模板缓冲写入的mask设置为0x00(不进行写入)

cpp 复制代码
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NOTEQUAL,1,0xFF);
glStencilMask(0x00);

三、模板测试的封装

从设置模板测试的变量去看,我们需要为每一个材质material加入如下变量

cpp 复制代码
//Stencil模板测试
bool m_stencil_test{ false };
GLenum m_stencil_func{ GL_ALWAYS };
GLint m_stencil_ref{ 0 };
GLuint m_stencil_func_mask{ 0xFF };

GLenum m_fail{ GL_KEEP };
GLenum m_zfail{ GL_KEEP };
GLenum m_zpass{ GL_KEEP };
	
GLuint m_stencil_mask{ 0xFF };

于是,在渲染阶段,就需要根据材质material中的变量,来对模板测试进行设置,包括:

(1)是否要开启模板检测 glEnable/glDisable(GL_STENCIL_TEST)

(2)通过测试的操作设置 gl_StencilOp(fail,zfail,zpass)

(3)模板测试的比较方式 gl_StencilFunc(func,ref,mask)

(4)是否写入模板缓冲 gl_StencilMask(mask)

cpp 复制代码
void Renderer::set_stencil_test(Material* material)
{
    if (material->m_stencil_test)
    {
        glEnable(GL_STENCIL_TEST);
        glStencilOp(material->m_fail, material->m_zfail, material->m_zpass);
        glStencilFunc(material->m_stencil_func, material->m_stencil_ref, material->m_stencil_func_mask);
        glStencilMask(material->m_stencil_mask);
    }
    else
    {
        glDisable(GL_STENCIL_TEST);
    }
}

我们可以考虑去实现如下场景,对两个粘连在一起的正方体进行勾边:

在这种情况下,我们会发现,其中一个正方体的勾边遮住了另外一个正方体,在正常情况下我们是不希望这样的。

所以,为了解决这个问题,就需要关闭勾边的深度检测,这样,就可以确保勾边不会影响到原来的像素。

相关推荐
滴_咕噜咕噜5 小时前
【MFC】数据库操作:数据库动态生成
数据库·c++·mfc
WoodWall5 小时前
WebServer 00 重要前置知识
c++·后端
FFZero15 小时前
【C++/Lua联合开发】 (一) Lua基础知识
c++·音视频·lua
Yuroo zhou5 小时前
破空驭风,智领未来 --5KG物流配送无人机展示飞行!
人工智能·算法·机器人·硬件工程·无人机
墨尘笔尖5 小时前
使用子进程实现 C++ 与 Python 交互式控制台
c++·windows·python
CoovallyAIHub5 小时前
ICCV 2025 最佳论文出炉:CMU 团队用「AI 积木大师」BrickGPT 摘得桂冠!
深度学习·算法·计算机视觉
喜欢吃燃面5 小时前
算法中的链表结构
开发语言·c++·学习·算法
十五年专注C++开发5 小时前
Fruit框架:C++依赖注入解决方案
开发语言·c++·依赖注入·fruit框架
风亦辰7395 小时前
从 Hello World 到游戏世界——pygame 快速入门
python·游戏·pygame