文章

半透明纯色物体描边

半透明纯色物体描边

半透明纯色物体描边

00 要求

  • 仅渲染纯色物体, 不采用贴图
  • 不能渲染出半透明物体内部的结构
  • 不要影响其他的半透明物体的排序

首先排除使用深度处理, 因为会影响到其他的半透明物体渲染. 其次, 由于是纯色物体, 那么仅考虑上色逻辑即可.

01 处理方法

  • 拆分

    • 无论内部结构多复杂, 仅渲染一次

    • 采用Stencil而不是深度, 则对其他的排序不影响

    • 使用RenderObjectFeature, 在渲染Transparent物体前先进行Stencil的预写入

  • 核心原理

    • 预写入Outline部分的Stencil信息, 将区域Stencil设定为255
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      
      Name "StencilMask"
      Tags { "LightMode" = "StencilMask" }
      Cull Front
      ZWrite Off
      Blend Off
      ColorMask 0
          
      Stencil
      {
          Ref 255             // 标记值
          Comp Always         // 总是通过
          Pass Replace        // 写入 Ref
          ZFail Replace       // 因为描边是Cull Front的, 所以必然通不过深度测试, 这个时候还是需要Replace
          ReadMask  255
          WriteMask 255
      }
      

      此时, 整个”描边”区域Stencil信息为255

    • 预写入物体本身的Stencil信息, 将区域Stencil-1变为254
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      Name "StencilMask"
      Tags { "LightMode" = "StencilMask" }
      Cull [_Cull]
      ZWrite Off
      Blend Off
      ColorMask 0
          
      Stencil
      {
          Ref 255                // 标记值
          Comp Equal             // 相等
          Pass DecrSat           // 写入 Ref 变为254
          ReadMask  255
          WriteMask 255
      }
      

      此时, 整个描边区域Stencil信息为255, 物体区域Stencil信息为254

    • 渲染Outline部分的颜色

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      Name "Outline"
      Tags { "LightMode" = "SRPDefaultUnlit" }
      Cull Front
      ZWrite Off
      Blend [_OutlineSrcBlend] [_OutlineDstBlend]
      BlendOp [_OutlineBlendOp]
          
      Stencil
      {
          Ref 255
          Comp Equal        // 仅在未被 Base 标记的像素处绘制
          Pass Keep
      }
      

      仅在255区域内渲染

    • 渲染物体本身的颜色

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      Name "Effect"
      Tags { "LightMode" = "SRPDefaultUnlit" }
      Cull Back
      ZWrite Off
      Blend [_SrcBlend] [_DstBlend]
      BlendOp [_BlendOp]
          
      Stencil
      {
          Ref 254
          Comp Equal
          Pass DecrSat	// 初次渲染时, 变为253, 多次渲染时, 会跳过
          ReadMask  255
          WriteMask 255
      }
      

      此时无论有多少层物体重叠, 都只会渲染一次.

      因为, 当254时, 则渲染, 同时将值改为253, 当多层重叠的情况, 第二次读到这个像素时, 值已经是253了, 则不会重复渲染.

    • 注意

      • 渲染Outline采用的是最通常的挤出法线方式, 此时当255的时候, 才会渲染.
      • 渲染Outline部分的StencilMask时, 要写ZFail : Repalce, 因为描边是Cull Front, 极大概率会被深度剔除.
      • 不能定为初始为1, 然后减为0, 因为Stencil默认值就会填充0, 会出现问题.
      • 由于无法确定是哪一层模型真正参与了渲染, 所以无法采用贴图, 只适用于纯色半透明物体.
      • 在物体上挂材质球的时候, Outline在前, TransparentOutline在后

02 Stencil相关参数及Tips

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    Stencil
    {
        Ref <ref>
        ReadMask <readMask>
        WriteMask <writeMask>
        Comp <comparisonOperation>
        Pass <passOperation>
        Fail <failOperation>
        ZFail <zFailOperation>
        CompBack <comparisonOperationBack>
        PassBack <passOperationBack>
        FailBack <failOperationBack>
        ZFailBack <zFailOperationBack>
        CompFront <comparisonOperationFront>
        PassFront <passOperationFront>
        FailFront <failOperationFront>
        ZFailFront <zFailOperationFront>
    }
    
  • 模板方程

    1
    2
    3
    4
    5
    6
    7
    8
    
    if((ref & readMask) comparisonFunction (stencilBufferValue & readMask))
    {
        // 通过
    }
    else
    {
    	// 跳过    
    }
    
  • Ref

    • 作用: 指定”参考值”(reference value), 用作模板测试时的基准.
    • 类型: 整数(0-255).
    • 默认: 0
  • ReadMask

    • 作用: GPU 在执行模板测试时使用此值作为遮罩.

    • 类型: 整数(0-255)

    • 默认: 255(1111 1111)

      • 举例1

        如果Ref为171(1010 1011), ReadMask为15(0000 1111), 原StencilBuffer值为18(0001 0010) ref & readMask = = 10101011 & 00001111 = 11(00001011),

        stencilBufferValue & readMask = 00010010 & 00001111 = 2(00000010)

        如果 comparisonFunction = Equal, 就比较 11 == 2? → 失败; 如果 comparisonFunction = Greater, 就比较 11 > 2? → 通过.

      • 举例2

        如果Ref为171(1010 1011), ReadMask为240(1111 0000), 原StencilBuffer值为242(1111 0010) ref & readMask = = 10101011 & 11110000 = 160(10100000),

        stencilBufferValue & readMask = 11110010 & 11110000 = 240(11110000)

        如果 comparisonFunction = Equal, 就比较 160 == 240? → 失败; 如果 comparisonFunction = Greater, 就比较 160 > 240? → 失败; 如果 comparisonFunction = LessEqual, 就比较 160≤ 240? →通过.

  • WriteMask

    • 作用: 在GPU 在写入模板缓冲区时使用此值作为遮罩.
    • 类型: 整数(0-255)
    • 默认: 255(1111 1111)
    • 注意:
      • WriteMask是否会影响IncrSat需要验证 比如: 当WriteMask1110 0000时, IncrSat到底是增加’0000 0001’(直接加1)还是’0010 0000’(在写入掩码的最低位+1), 需要验证
  • Comp

    • 作用: 比较操作, GPU 为所有像素的模板测试执行的操作

    • 类型: 枚举, Rendering.CompareFunction

    • 值:

      Rendering.CompareFunction 枚举中的对应整数值功能
      Never1从不渲染像素。
      Less2在参考值小于模板缓冲区中的当前值时渲染像素。
      Equal3在参考值等于模板缓冲区中的当前值时渲染像素。
      LEqual4在参考值小于或等于模板缓冲区中的当前值时渲染像素。
      Greater5在参考值大于模板缓冲区中的当前值时渲染像素。
      NotEqual6在参考值与模板缓冲区中的当前值不同时渲染像素。
      GEqual7在参考值大于或等于模板缓冲区中的当前值时渲染像素。
      Always8始终渲染像素。
  • Pass

    • 作用: 模板操作. 当像素通过模板测试和深度测试时, GPU 对模板缓冲区执行的操作.

    • 类型: 枚举, Rendering.Rendering.StencilOp

    • 默认值: keep

    • 值:

      Rendering.StencilOp 枚举中的对应整数值功能
      Keep0保持模板缓冲区的当前内容。
      Zero1将 0 写入模板缓冲区。
      Replace2将参考值写入缓冲区。
      IncrSat3递增缓冲区中的当前值。如果该值已经是 255,则保持为 255。
      DecrSat4递减缓冲区中的当前值。如果该值已经是 0,则保持为 0。
      Invert5将缓冲区中当前值的所有位求反。
      IncrWrap6递增缓冲区中的当前值。如果该值已经是 255,则变为 0。
      DecrWrap7递减缓冲区中的当前值。如果该值已经是 0,则变为 255。
  • Fail

    • 作用: 模板操作. 当像素未通过模板测试时, GPU 对模板缓冲区执行的操作.
    • 类型: 枚举, 同Rendering.Rendering.StencilOp
    • 默认值: keep
  • ZFail

    • 作用: 模板操作. 当像素通过模板测试, 但未通过深度测试时, GPU 对模板缓冲区执行的操作.
    • 类型: 枚举, 同Rendering.Rendering.StencilOp
    • 默认值: keep
  • CompBack/ComFront

    • 作用: Comp针对背面/正面的比较操作, 如果已经定义了Comp, 则无效果
    • 类型: 枚举, 同Rendering.CompareFunction
    • 默认值: keep
  • PassBack/PassFront

    • 作用: Pass针对背面/正面的模板操作, 如果已经定义了Pass, 则无效果
    • 类型: 枚举, 同Rendering.Rendering.StencilOp
    • 默认值: keep
  • FailBack/FailFront

    • 作用: Fail针对背面/正面的模板操作, 如果已经定义了Fail, 则无效果
    • 类型: 枚举, 同Rendering.Rendering.StencilOp
    • 默认值: keep
  • ZFailBack/ZFailFront

    • 作用: ZFail针对背面/正面的模板操作, 如果已经定义了ZFail, 则无效果
    • 类型: 枚举, 同Rendering.Rendering.StencilOp
    • 默认值: keep

03 源码

Outline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
Shader "UnlitOutline"
{
    Properties
    {
        [HDR]_OutlineColor ("Outline Color", Color) = (0,1,1,1)
        _OutlineWidth ("Outline Width", Range(0,10)) = 5
        [Enum(UnityEngine.Rendering.BlendMode)]_OutlineSrcBlend("Outline SrcBlend Mode", Int) = 5
        [Enum(UnityEngine.Rendering.BlendMode)]_OutlineDstBlend("Outline DstBlend Mode", Int) = 10
        [Enum(UnityEngine.Rendering.BlendOp)]_OutlineBlendOp("Outline Blend Op", Int) = 0
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType"     = "Transparent"
            "Queue"          = "Transparent"
        }
        LOD 100

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
        float4 _OutlineColor;
        float _OutlineWidth;
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "StencilMask"
            Tags { "LightMode" = "StencilMask" }
            Cull Front
            ZWrite Off
            Blend Off
            ColorMask 0

            Stencil
            {
                Ref 255             // 标记值
                Comp Always         // 总是通过
                Pass Replace        // 写入 Ref
                ZFail Replace       // 因为描边是Cull Front的, 所以必然通不过深度测试, 这个时候还是需要Replace
                ReadMask  255
                WriteMask 255
            }

            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };

            Varyings Vert(Attributes IN)
            {
                Varyings OUT;
                float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);
                float viewZ     = abs(mul(UNITY_MATRIX_MV, IN.positionOS).z);
                float tanHFov   = 1.0 / UNITY_MATRIX_P._m11;
                _OutlineWidth  = _OutlineWidth * (2 * viewZ * tanHFov) / _ScreenParams.y;
                float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz) + normalWS * _OutlineWidth;
                OUT.positionCS = TransformWorldToHClip(positionWS);
                return OUT;
            }

            float4 Frag(Varyings IN) : SV_Target
            {
                return 0;
            }
            ENDHLSL
        }

        Pass
        {
            Name "Outline"
            Tags { "LightMode" = "SRPDefaultUnlit" }
            Cull Front
            ZWrite Off
            Blend [_OutlineSrcBlend] [_OutlineDstBlend]
            BlendOp [_OutlineBlendOp]

            Stencil
            {
                Ref 255
                Comp Equal        // 仅在未被 Base 标记的像素处绘制
                Pass Keep
            }

            HLSLPROGRAM
            #pragma vertex VertOutline
            #pragma fragment FragOutline
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };

            Varyings VertOutline(Attributes IN)
            {
                Varyings OUT;
                float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);
                float viewZ     = abs(mul(UNITY_MATRIX_MV, IN.positionOS).z);
                float tanHFov   = 1.0 / UNITY_MATRIX_P._m11;
                _OutlineWidth  = _OutlineWidth * (2 * viewZ * tanHFov) / _ScreenParams.y;
                float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz) + normalWS * _OutlineWidth;
                OUT.positionCS = TransformWorldToHClip(positionWS);
                return OUT;
            }

            float4 FragOutline(Varyings IN) : SV_Target
            {
                return _OutlineColor;
            }
            ENDHLSL
        }
    }
}

TransparentOutline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
Shader "UnlitTransparentOutline"
{
    Properties
    {
        [HDR]_Color   ("Color Tint", Color) = (0.2,0.8,0.8,0.3)
        [HideInInspector][HDR]_OutlineColor ("Outline Color", Color) = (0,1,1,1)
        [HideInInspector]_OutlineWidth ("Outline Width", Range(0,10)) = 5
        [Enum(UnityEngine.Rendering.CullMode)]_Cull("Cull Mode", Int) = 2
        [Enum(UnityEngine.Rendering.BlendMode)]_SrcBlend("SrcBlend Mode", Int) = 5
        [Enum(UnityEngine.Rendering.BlendMode)]_DstBlend("DstBlend Mode", Int) = 10
        [Enum(UnityEngine.Rendering.BlendOp)]_BlendOp("Blend Op", Int) = 0
        [HideInInspector][Enum(UnityEngine.Rendering.BlendMode)]_OutlineSrcBlend("Outline SrcBlend Mode", Int) = 5
        [HideInInspector][Enum(UnityEngine.Rendering.BlendMode)]_OutlineDstBlend("Outline DstBlend Mode", Int) = 10
        [HideInInspector][Enum(UnityEngine.Rendering.BlendOp)]_OutlineBlendOp("Outline Blend Op", Int) = 0
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType"     = "Transparent"
            "Queue"          = "Transparent"
        }
        LOD 100

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
        float4 _Color;
        float4 _OutlineColor;
        float _OutlineWidth;
        CBUFFER_END

        ENDHLSL

        Pass
        {
            Name "StencilMask"
            Tags { "LightMode" = "StencilMask" }
            Cull [_Cull]
            ZWrite Off
            Blend Off
            ColorMask 0

            Stencil
            {
                Ref 255                // 标记值
                Comp Equal          // 相等
                Pass DecrSat         // 写入 Ref 变为254
                ReadMask  255
                WriteMask 255
            }

            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };

            Varyings Vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            float4 Frag(Varyings IN) : SV_Target
            {
                return 0;
            }
            ENDHLSL
        }

        Pass
        {
            Name "Effect"
            Tags { "LightMode" = "SRPDefaultUnlit" }
            Cull Back
            ZWrite Off
            Blend [_SrcBlend] [_DstBlend]
            BlendOp [_BlendOp]

            Stencil
            {
                Ref 254
                Comp Equal
                Pass DecrSat
                ReadMask  255
                WriteMask 255
            }

            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };

             TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);

            Varyings Vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            float4 Frag(Varyings IN) : SV_Target
            {
                return _Color;
            }
            ENDHLSL
        }
    }
}
参考网页
本文由作者按照 CC BY 4.0 进行授权