模版测试 Stencil 简介,及在 Unity 中的应用

1 什么是模版测

   首先了解图形渲染管线,如下图:

    模版测试 Stencil 简介,及在 Unity 中的应用
  

    模版测试是GPU渲染流水线中的一个环节,在片元着色器之后的逐片元操作过程中执行。在透明度之后,深度测试之前。
    模版测试 Stencil 简介,及在 Unity 中的应用
    模版测试通过模版值进行相应操作,默认的模版值是0,是范围0-255的8位数。
    模版测试通过比较操作来决定片元是否渲染,以及如何更新模版值。参考值 Ref 和当前模版值 Mask 进行比较。
    --通过则进行渲染,并把模版值根据更新操作设置为参考值,
    --否则丢弃片元,且不更新模版值。
    
    比较操作:
    /* StencilFunction */
    #define GL_NEVER                          0x0200
    #define GL_LESS                           0x0201
    #define GL_EQUAL                          0x0202
    #define GL_LEQUAL                         0x0203
    #define GL_GREATER                        0x0204
    #define GL_NOTEQUAL                       0x0205
    #define GL_GEQUAL                         0x0206
    #define GL_ALWAYS                         0x0207

    更新操作:
    /* StencilOp */
    /*      GL_ZERO */                          0
    #define GL_KEEP                           0x1E00
    #define GL_REPLACE                        0x1E01
    #define GL_INCR                           0x1E02
    #define GL_DECR                           0x1E03
    /*      GL_INVERT */                      0x150A
    #define GL_INCR_WRAP                      0x8507
    #define GL_DECR_WRAP                      0x8508

    模版值的读取和写入,可以通过掩码控制。掩码分为 ReadMask 和 WiriteMask。
    掩码也是范围0-255的8位数,通过按位与进行掩码运算。这样模版值可以进行精准位操作,同时进行多层模版测试。
    --读入时值为 Mask & ReadMask
    --写入时值为 Ref & ReadMask

2 模版测试在OpenGL中怎么使用?
    第一步,开启模版测试。
    glEnable(GL_STENCIL_TEST);

    第二步,清除模版缓冲。下面同时包含清除颜色缓冲和深度缓冲。
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    第三步,设置掩码值,此掩码为 WriteMask。一般总是0或255。
    glStencilMask(0xFF);

    第四步,设置模版函数。func 为上面提到的比较操作;ref 为参考模版值;mask 为 ReadMask。
    glStencilFunc(GLenum func, GLint ref, GLuint mask)

    第五步,Ref 设置操作函数。 所有参数为上面提到的更新操作。sfail为模版测试失败的操作;
    dpfail为模版测试成功,深度测试失败的操作;dppass 为模版测试和深度测试都通过的操作。
    glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)

3 模版测试在GLSL中怎么使用?
    模版测试在 Shader 中通过下面函数进行控制,其中后面五个为可选,不填写使用默认值。

    stencil{
        Ref 128
        Comp GEqual
        ReadMask  255
        WriteMask 255
        Pass Replace
        Fail Keep
        ZFail Keep
    }

4 模版测试在Unity中怎么使用?
    在 unity 中在 UI 的默认着色器中支持了此功能,为了在同一个着色器中支持多个操作,参数通过数值进行操作。

    Stencil
    {
        Ref [_Stencil]
        Comp [_StencilComp]
        Pass [_StencilOp]
        ReadMask [_StencilReadMask]
        WriteMask [_StencilWriteMask]
    }

    _StencilComp ("Stencil Comparison", Float) = 8            --比较操作
    _Stencil ("Stencil ID", Float) = 0                        --参考值
    _StencilOp ("Stencil Operation", Float) = 0                --通过时 Ref 更新操作
    _StencilWriteMask ("Stencil Write Mask", Float) = 255    --写入掩码
    _StencilReadMask ("Stencil Read Mask", Float) = 255        --读取掩码

    下面为参数树枝和操作的对照关系表,因为在 OpenGL 中更新操作不是连续的枚举值,所以和下面的顺序无法对应,
    而且 unity 官方文档没有说明导致试使用起来比较迷惑~_~。将下面的值在 material 中填写即可完成模版测试。

    1 - Never
    2 - Less
    3 - Equal
    4 - LEqual
    5 - Greater
    6 - NotEqual
    7 - GEqual
    8 - Always

    0 - Keep 
    1 - Zero
    2 - Replace
    3 - IncrSat
    4 - DecrSat
    5 - Invert
    6 - IncrWrap
    7 - DecrWrap

5 模版测试的应用
    主要应用就是控制片元是否绘制来完成我们的图形绘制需求。且模版测试本身的性能不是问题,可以放心使用。
    例如:
        1 UGUI 的 SrollView 组件中,默认会有一个 Mask 组件,就是通过模版测试来控制是否可见进行渲染。
        实现方式
        --通过在运行时将子组件的 material 进行切换为含有默认模版测试的 material 进行渲染。
        导致问题
        --在子组件中不能使用我们自定义的 material,否则会被替换。
        --在替换的过程中还有一个遍历操作,当含有大量子组件时,性能会受到影响。

        2 引导过程中高亮显示。尤其是我就有高要求的轮廓显示和不规则图形的显示。

        3 规定类型和区域的渲染显示。


参考文章:
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/02%20Stencil%20testing/
https://docs.unity3d.com/Manual/SL-Stencil.html
https://blog.csdn.net/wangdingqiaoit/article/details/52143197

注:图片资源来源于网络