unity-shader模板测试-描边

unity-shader模板测试,描边效果,常用于 rpg 项目中 主角 被遮挡的情况,将被遮挡的部分的轮廓描边绘制出来,这样可以在任何情况都能知道 主角 在哪里。(还有另外一种就是使用 X光 效果,但这种效果不需要用到模板测试,所以这里用 描边效果 举栗子)


效果

这里写图片描述

思路

实现这种效果需要两个pass

  1. 正常绘制,即深度测试 LEqual,正常遮挡,同时往 模板缓冲区 写入一个 参考值 Ref=1(即使深度测试失败的值也要写入,保证 角色所有像素在模板缓冲区的值 都是 参考值 Ref=1)
  2. 只绘制被遮挡部分的描边
    1. 先把模型的 顶点 往 法线方向 偏移一个值(这个就是描边的宽度值,可以理解为将模型放大了),可以在观察空间 或者 世界空间、模型空间 偏移,只要和 法线 在同一空间下
    2. 对比 模板缓冲区 的参考值 Ref=1,因为第一个 pass 写入的参考值是 1,所以这个pass中就要不等1才让它通过,这样就能得到一个 差值区域,即描边的区域
    3. 深度测试,让被遮挡部分才让它通过,即 ZTest Greater,done!

shader代码

Shader "Custom/Unlit-Texture-Outline" {
    Properties{
        _MainTex("Base (RGB)", 2D) = "white" {}
        _OutlineColor("Outline Color", Color) = (1,1,0,1) //描边颜色
        _Outline("Outline width", Range(0.0, 0.5)) = 0.03 // 描边宽度
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    struct appdata_t {
        float4 vertex : POSITION;
        float2 texcoord : TEXCOORD0;
        float3 normal : NORMAL;
    };

    struct v2f {
        float4 vertex : SV_POSITION;
        half2 texcoord : TEXCOORD0;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;
    float _Outline;
    float4 _OutlineColor;

    v2f vert(appdata_t v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

        return o;
    }

    v2f vert_outline(appdata_t v)
    {
        v2f o;
        // 方式一,观察空间 下往法线偏移顶点
        float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
        //float3 viewNorm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
        float3 viewNorm = mul(v.normal, (float3x3)UNITY_MATRIX_T_MV);
        float3 offset = normalize(viewNorm) * _Outline;
        viewPos.xyz += offset;
        o.vertex = mul(UNITY_MATRIX_P, viewPos);

        //方式二,世界空间 下往法线偏移顶点
        //float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
        //float3 worldNormal = UnityObjectToWorldNormal(v.normal);
        //float3 offset = normalize(worldNormal) * _Outline;
        //worldPos.xyz += offset;
        //o.vertex = mul(UNITY_MATRIX_VP, worldPos);
        return o;
    }

    ENDCG

    SubShader{
        Tags{ "Queue" = "Transparent" "RenderType" = "Opaque" }

        Pass{ // 正常绘制
            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
                ZFail Replace
            }

            ZTest LEqual
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.texcoord);
                return col;
            }

            ENDCG
        }

        Pass{ // 遮挡部分绘制描边
            ZTest Greater
            ZWrite Off
            //Blend DstAlpha OneMinusDstAlpha

            Stencil{
                Ref 1
                Comp NotEqual

            }
            CGPROGRAM
            #pragma vertex vert_outline
            #pragma fragment frag
            half4 frag(v2f i) :COLOR
            {
                return _OutlineColor;
            }
            ENDCG
        }
    }
}

使用 剔除Cull 的方式 描边

这种方式的描边不适合做遮挡部分描边,且不遮挡部分的效果也没有 模板测试 那种方式好,他的原理也是使用两个pass,一个pass正常渲染,剔除背面 (Cull Back),另外一个pass 也需要顶点外拉,然后 剔除正面(Cull Front),偏移深度。这种方式会在人体内也有描边,不像 模板测试 那种方式在人体完全没有描边。

  • 效果

    这里写图片描述

  • shader代码

    // 这种方式的描边不适合做遮挡部分描边,且不遮挡部分的效果也没有 模板测试 那种方式好
    
    Shader "ITS/test/testOutline_cull" {
    Properties{
        _MainTex("Base (RGB)", 2D) = "white" {}
        _OutlineColor("Outline Color", Color) = (1,1,0,1)
        _Outline("Outline width", Range(0.0, 0.5)) = 0.03
    }
    
    CGINCLUDE
    
    #include "UnityCG.cginc"
    
    struct appdata_t {
        float4 vertex : POSITION;
        float2 texcoord : TEXCOORD0;
        float3 normal : NORMAL;
    };
    
    struct v2f {
        float4 vertex : SV_POSITION;
        half2 texcoord : TEXCOORD0;
    };
    
    sampler2D _MainTex;
    float4 _MainTex_ST;
    float _Outline;
    float4 _OutlineColor;
    
    v2f vert(appdata_t v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
        return o;
    }
    
    v2f vert_outline(appdata_t v)
    {
        v2f o;
        // 方式一,观察空间 下往法线偏移顶点
        float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
        //float3 viewNorm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
        float3 viewNorm = mul(v.normal, (float3x3)UNITY_MATRIX_T_MV);
        float3 offset = normalize(viewNorm) * _Outline;
        viewPos.xyz += offset;
        o.vertex = mul(UNITY_MATRIX_P, viewPos);
    
        //方式二,世界空间 下往法线偏移顶点
        //float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
        //float3 worldNormal = UnityObjectToWorldNormal(v.normal);
        //float3 offset = normalize(worldNormal) * _Outline;
        //worldPos.xyz += offset;
        //o.vertex = mul(UNITY_MATRIX_VP, worldPos);
        return o;
    }
    
    ENDCG
    
    SubShader{
        Tags{ "Queue" = "Transparent" "RenderType" = "Opaque" }
    
        Pass{
            ZTest LEqual
            Cull Back
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
    
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.texcoord);
                return col;
            }
    
            ENDCG
        }
    
        Pass{
            // ZTest Greater
            ZWrite Off
            Cull Front
            Offset 100,0
    
            CGPROGRAM
            #pragma vertex vert_outline
            #pragma fragment frag
            half4 frag(v2f i) : COLOR
            {
                return _OutlineColor;
            }
            ENDCG
        }
    }
    }

ps: 还有其他描边处理的方式,比如 后处理

可以参考 浅墨 大神 的 《Real-Time Rendering 3rd》提炼总结 里的 10.3 轮廓描边的渲染方法小结 这个章节的介绍

猜你喜欢

转载自blog.csdn.net/yangxuan0261/article/details/79686192