模板测试

当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),我们可以在渲染的时候更新它来获得一些很有意思的效果。

模板缓冲中的模板值(Stencil Value)通常是8位的,因此每个片段/像素共有256种不同的模板值(译注:8位就是1字节大小,可表示256个不同的值,因此和char的容量一样是256个不同值)。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。

  1. 工作过程 > 需要每次迭代前清除模板缓冲 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    1. 启用模板缓冲的写入。 glEnable(GL_STENCIL_TEST);
    2. 渲染物体,更新模板缓冲的内容。 glStencilMask, eg:
       glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
       glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
    1. 禁用模板缓冲的写入。
    2. 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。
  2. 模板函数 > 和深度测试一样,我们对模板缓冲应该通过还是失败,以及它应该如何影响模板缓冲,也是有一定控制的。一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp。

    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:模板测试和深度测试都通过时采取的行为。 * 一般参数默认值 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    行为 描述
    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 按位翻转当前的模板缓冲值
  3. 物体轮廓, 为物体创建轮廓的步骤如下: > OpenGL的指令开启或关闭,直到下次改变前的渲染都是同一个状态值。

    1. 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,此后物体的片段被渲染时,将模板缓冲更新为1。 cpp glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF);
    2. 渲染物体。 此时会得到之前物体作为模板缓冲。 cpp //.... glDrawArrays(GL_TRIANGLES, 0, 36);
    3. 禁用模板写入以及深度测试。 GL_NOTEQUAL 只绘制模板之外的地方 cpp glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); //首先深度测试不需要,另外开启的话会造成`深度冲突` glDisable(GL_DEPTH_TEST);
    4. 将每个物体缩放一点点。 绘制纯色的物体。 cpp glDrawArrays(GL_TRIANGLES, 0, 36);
    5. 使用一个不同的片段着色器再次绘制物体,输出一个单独的(边框)颜色。但只在它们片段的模板值不等于1时才绘制。 '绘制的纯色物体和模板进行测试'
    6. 再次启用模板写入和深度测试。
  4. 主要代码 cpp //1. 清除深度缓冲和模板缓冲 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // don't forget to clear the stencil buffer! } outlineShader.use(); glm::mat4 model = glm::mat4(1.0f); glm::mat4 view = camera.GetViewMatrix(); glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)wWidth / (float)wHeight, 0.1f, 100.0f); // ---- 基础设置 { //设置outlineShader的VP矩阵 { outlineShader.setMat4("view", view); outlineShader.setMat4("projection", projection); } //设置shader的VP矩阵 { shader.use(); shader.setMat4("view", view); shader.setMat4("projection", projection); } } //2. 绘制Plan,并设置为不影响模板(禁用模板写入) { glStencilMask(0x00); glBindVertexArray(planeVAO); glBindTexture(GL_TEXTURE_2D, floorTexture); shader.setMat4("model", glm::mat4(1.0f)); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } //3.绘制两个箱子,并且将箱子更新覆盖为模板缓冲(开启模板,并模板测试设置为GL_ALWAYS) { glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF); glBindVertexArray(cubeVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, cubeTexture); model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f)); shader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f)); shader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); } //4. 绘制放大后的箱子,不绘制原有箱子的位置,并且不刷新模板缓冲,"关闭深度测试"(关闭模板写入,并模板测试设置为GL_NOTEQUAL,深度测试没有用此时关闭它) { glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); glDisable(GL_DEPTH_TEST); outlineShader.use(); float scale = 1.1; // cubes glBindVertexArray(cubeVAO); glBindTexture(GL_TEXTURE_2D, cubeTexture); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f)); model = glm::scale(model, glm::vec3(scale, scale, scale)); outlineShader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f)); model = glm::scale(model, glm::vec3(scale, scale, scale)); outlineShader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); } //5. 最后重置选项 { glBindVertexArray(0); glStencilMask(0xFF); glStencilFunc(GL_ALWAYS, 0, 0xFF); glEnable(GL_DEPTH_TEST); }

  5. UnityShader 另类描边实现 (不使用模板和normal) > 两次绘制,纯色绘制 放大+ 偏移 cpp Shader "Unlit/StencilTest" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } //Outline pass Pass { name "Outline" CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct v2f { UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; v2f vert (float4 vertex : POSITION) { v2f o; // 缩放系数`1.2`可调 float3 vvertex = vertex.xyz*1.2 - (ObjSpaceViewDir(vertex).xyz); o.vertex = UnityObjectToClipPos(float4(vvertex, vertex.w)); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(0.7, 0.85, 0.3, 1); } ENDCG } } }


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录