【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);
    }
}

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

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

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

相关推荐
zho_uzhou9 分钟前
c++ imgui implot绘图使用示例 visual studio
开发语言·c++·visual studio
dyyx11111 分钟前
C++中的过滤器模式
开发语言·c++·算法
星夜泊客26 分钟前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
机器视觉知识推荐、就业指导32 分钟前
用惯了QTimer定时器,如何快速在纯 C++ 项目中替换?
c++
lrh12280033 分钟前
详解决策树算法:分类任务核心原理、形成流程与剪枝优化
算法·决策树·机器学习
期末考复习中,蓝桥杯都没时间学了37 分钟前
力扣刷题15
算法·leetcode·职场和发展
消失的旧时光-194338 分钟前
从拷贝到移动:C++ 移动构造与移动赋值是怎么被逼出来的?(附完整示例)
开发语言·c++
2301_8174973343 分钟前
C++中的装饰器模式高级应用
开发语言·c++·算法
m0_549416661 小时前
C++编译期字符串处理
开发语言·c++·算法
m0_581124191 小时前
C++中的适配器模式实战
开发语言·c++·算法