ShadersRoom - 遮挡透明

先上一张效果图:


OutLine.gif

物体在被遮挡后,被遮挡的部分边缘高亮显示,实现这效果大致思路是:
  使用2个pass块来渲染这个物体,没有被遮挡的部分正常显示,被遮挡的部分则使用边缘光显示。

这里需要引入一个知识点就是深度值深度缓存(z-buffer)
在场景中的每一个物体都有自己的深度值,距离摄像机越近深度越小,越远越大;
而在屏幕上显示的每个像素点都有一个深度缓存(z-buffer),会对当前需要渲染像素的深度值进行排序,默认情况下是深度值小的像素覆盖深度值大的像素;
所以说,在渲染队列一样的情况下,近距离的像素也就是深度值小的像素会被渲染。

Unity中提供了ZWrite 和 ZTest对应深度写入和深度测试:
  ZTest控制深度测试的模式,有如下参数,默认是LEqual:


ZTest

  ZWrite则用来控制,当测试通过时,是否需要写入到深度缓存(z-buffer)中,On为写入,off不写入,如果未通过测试的话也不会写入。


ZWrite

  好了,有上述的解释后,接下来实现一shader的两个pass块,第一个pass用来显示被遮挡的模型(注意代码中的注释解释),第二个pass则正常显示;

Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
    }
 SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {

            Name "Edge pass"      //显示被遮挡时的pass
            ZTest Greater         //Greater表示像素点深度值大的通过,这样就可以显示被遮挡住的模型了。

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _EdgeColor;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return _EdgeColor;
            }
            ENDCG
        }

        Pass
        {
        
            Name "Model pass"
            ZTest Less 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #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
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

完成上述功能后,显示被遮挡模型的功能就实现啦:

OutLine_2.gif

  然后在优化一下被遮挡的显示效果,单纯的显示这个颜色显然是不太好看的,这里我们使用到的是边缘光,同时和遮挡的物体做一个透明度混合。
  首先是透明度混合,在第一个pass块中加入blend one one:

    Pass
        {

            Name "Edge pass"
            ZTest Greater
            Blend One One

透明度混合常用的几种方式如下,可以自行测试一下:

          Blend SrcAlpha OneMinusSrcAlpha // 传统透明度
          Blend One OneMinusSrcAlpha // 预乘透明度
          Blend One One // 叠加
          Blend OneMinusDstColor One // 柔和叠加
          Blend DstColor Zero // 相乘——正片叠底
          Blend DstColor SrcColor // 两倍相乘

  接下来就是边缘光的实现:
  正常来说,物体法线与视线(从顶点至相机的方向)角度越一致,则表示越接近能被玩家看见的中间,物体的边缘一般与视线垂直,所以通过物体法线和视线的点乘即可计算轮廓光,即1-dot(normal,view).

dot(normal,view)的值越到中心越大,现在我们需要的是越到边缘越大,则使用1减去该值即可。

下面添加一些计算边缘光的代码参数:
  加入一个控制边缘光范围的参数,

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
        _OutLine("OutTime",range(0.5,2)) = 1   //在属性面板中加入一个控制边缘光范围的参数
    }

  加入法线和视线方向,

struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

                float3 normal : NORMAL;   //取得模型的法线

            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

                float3 normal : NORMAL;  //法线的方向
                float3 viewDir : TEXCOORD1;//视线的方向
            };

  在顶点函数中进行坐标系的转化以及计算视野方向,

  v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.normal = UnityObjectToWorldNormal(v.normal);   //将法线坐标转化到世界坐标
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);  //计算视野的方向
                return o;
            }

  最后在片元函数中计算边缘光的颜色,

fixed4 frag (v2f i) : SV_Target
            {
                float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine;  //计算边缘光,OutLine越大靠近边缘
                return _EdgeColor * NdotV;
            }

完成到这一步,遮挡透明的效果就和一开始的动图一样啦~~~

git仓库:https://github.com/Looooooong/ShadersRoom

最后附上完整的shader:

Shader "ShadersRoom/OutLine" {
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
        _OutLine("OutTime",range(0.5,2)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {

            Name "Edge pass"
            ZTest Greater
            Blend One One


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

                float3 normal : NORMAL;

            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

                float3 normal : NORMAL;
                float3 viewDir : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _EdgeColor;
            float _OutLine;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.normal = UnityObjectToWorldNormal(v.normal);   //将法线坐标转化到世界坐标
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);  //计算视野的方向

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine;  //计算边缘光,OutLine越大靠近边缘
                return _EdgeColor * NdotV;
            }
            ENDCG
        }

        Pass
        {
        
            Name "Model pass"
            ZTest Less 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #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
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

你可能感兴趣的:(ShadersRoom - 遮挡透明)