1. 程式人生 > >【Unity ImageEffect】一個用於角色遮擋透視輪廓的效果

【Unity ImageEffect】一個用於角色遮擋透視輪廓的效果

前言

        有些遊戲專案需要在角色被遮擋後仍然顯示出來,讓玩家不會迷失自我的感覺,但一般不會用原顏色渲染出來,一般會用另外的顏色表示。本文介紹一種當角色被遮擋後,用另外一種顏色並有輪廓漸變效果的渲染方法。

方案

  • Shader方式:在角色Shader加一道Pass,在深度檢測時判斷被遮擋後渲染角色輪廓。
  • ImageEffect方式:利用Camera的深度資訊,判斷角色被遮擋後渲染角色輪廓。

實現

  • Shader方式:
    Shader "Yogi/Occlusion"
    {
    	Properties
    	{
    		_BumpMap("Normalmap", 2D) = "bump" {}
    		_OccColor("Occlusion Color", Color) = (1, 1, 1, 1)
    		_OccPower("Occlusion Power", Range(0.0, 2.0)) = 0.5
    		_Alpha("Alpha", Range(0, 1)) = 1
    	}
    
    	SubShader
    	{
    		Tags
    		{
    			"Queue" = "Transparent+1"
    			"RenderType" = "Opaque"
    			"IgnoreProjector" = "True"
    		}
    
    		Pass
    		{
    			Name "BASE"
    
    			Blend SrcAlpha OneMinusSrcAlpha
    			Fog { Mode Off }
    			Lighting Off
    			ZWrite Off
    			ZTest Greater
    
    			CGPROGRAM
    			#include "UnityCG.cginc"
    			#pragma vertex vert
    			#pragma fragment frag
    			#pragma fragmentoption ARB_precision_hint_fastest
    
    			sampler2D _BumpMap;
    			fixed4 _OccColor;
    			fixed _OccPower;
    
    			struct a2v
    			{
    				fixed4 vertex : POSITION;
    				fixed3 normal : NORMAL;
    				fixed4 tangent : TANGENT;
    				fixed4 texcoord : TEXCOORD0;
    			};
    
    			struct v2f
    			{
    				fixed4 pos : SV_POSITION;
    				fixed2 uv : TEXCOORD0;
    				fixed3 viewDir : TEXCOORD1;
    			};
    
    			v2f vert(a2v v)
    			{
    				v2f o;
    				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    				o.uv = v.texcoord.xy;
    
    				TANGENT_SPACE_ROTATION;
    				o.viewDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)));
    
    				return o;
    			}
    
    			fixed4 frag(v2f i) : SV_Target
    			{
    				fixed3 n = UnpackNormal(tex2D(_BumpMap, i.uv));
    				fixed o = 1 - saturate(dot(n, i.viewDir));
    				fixed4 c = _OccColor * pow(o, _OccPower);
    
    				return c;
    			}
    
    			ENDCG
    		}
    	}
    
    	FallBack "Mobile/Diffuse"
    }
    在角色的Shader上加上一個 UsePass "Yogi/Occlusion/BASE" 就可以看到效果了

  • ImageEffect方式:
    Shader "Yogi/ImageEffect/Occlusion"
    {
    	Properties
    	{
    		_MainTex("Base (RGB)", 2D) = "white" {}
    		_DepthMap("DepthMap (RGB)", 2D) = "white" {}
    		_OcclusionMap("OcclusionMap (RGB)", 2D) = "white" {}
    		_Intensity("Intensity", Float) = 0.0
    		_Tiling("Tiling", Vector) = (1.0, 1.0, 0.0, 0.0)
    	}
    
    	SubShader
    	{
    		Pass
    		{
    			ZTest Always
    			ZWrite Off
    			Cull Off
    			Fog{ Mode Off }
    
    			CGPROGRAM
    			#include "UnityCG.cginc"
    			#pragma vertex vert
    			#pragma fragment frag
    			#pragma fragmentoption ARB_precision_hint_fastest
    
    			sampler2D _MainTex;
    			sampler2D _DepthMap;
    			sampler2D _OcclusionMap;
    			sampler2D _CameraDepthNormalsTexture;
    			fixed4 _MainTex_TexelSize;
    			fixed _Intensity;
    			fixed _Power;
    			fixed4 _Tiling;
    
    			struct a2v
    			{
    				fixed4 vertex : POSITION;
    				fixed2 texcoord : TEXCOORD0;
    			};
    
    			struct v2f
    			{
    				fixed4 vertex : SV_POSITION;
    				fixed2 uv : TEXCOORD0;
    			};
    
    			v2f vert(a2v v)
    			{
    				v2f o;
    				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    				o.uv = v.texcoord.xy;
    
    				return o;
    			}
    
    			fixed4 frag(v2f i) : SV_Target
    			{
    				fixed4 c = tex2D(_MainTex, i.uv);
    
    				fixed4 depthMap = tex2D(_DepthMap, i.uv);
    				fixed depth = DecodeFloatRG(depthMap.zw);
    				fixed3 normal = DecodeViewNormalStereo(depthMap);
    
    				fixed4 cameraDepthMap = tex2D(_CameraDepthNormalsTexture, i.uv);
    				fixed cameraDepth = DecodeFloatRG(cameraDepthMap.zw);
    
    				fixed4 o = c;
    				if (depth > 0
    					&& cameraDepth < depth)
    				{
    					fixed2 uv = i.uv * _Tiling.xy + _Tiling.zw;
    					fixed3 color = tex2D(_OcclusionMap, uv);
    					fixed nf = saturate(dot(normal, fixed3(0, 0, 1)));
    					nf = pow(nf, _Intensity);
    					o.rgb = lerp(color, c.rgb, nf);
    				}
    
    				return o;
    			}
    
    			ENDCG
    		}
    	}
    
    	Fallback off
    }

    using UnityEngine;
    
    [RequireComponent(typeof(Camera))]
    [ExecuteInEditMode]
    public class OcclusionEffect : MonoBehaviour
    {
    	private const string NODE = "Occlusion Camera";
    
    	[Range(0.0f, 10.0f)]
    	public float intensity = 1.0f;
    	public Vector4 tiling = new Vector4(1, 1, 0, 0);
    	public Texture2D occlusionMap;
    	public LayerMask cullingMask;
    
    	private Camera occlusionCamera
    	{
    		get
    		{
    			if (null == m_OcclusionCamera)
    			{
    				Transform node = transform.FindChild(NODE);
    				if (null == node)
    				{
    					node = new GameObject(NODE).transform;
    					node.parent = transform;
    					node.localPosition = Vector3.zero;
    					node.localRotation = Quaternion.identity;
    					node.localScale = Vector3.one;
    				}
    
    				m_OcclusionCamera = node.GetComponent<Camera>();
    				if (null == m_OcclusionCamera)
    				{
    					m_OcclusionCamera = node.gameObject.AddComponent<Camera>();
    				}
    
    				m_OcclusionCamera.enabled = false;
    				m_OcclusionCamera.clearFlags = CameraClearFlags.SolidColor;
    				m_OcclusionCamera.backgroundColor = new Color(0, 0, 0, 0);
    				m_OcclusionCamera.renderingPath = RenderingPath.VertexLit;
    				m_OcclusionCamera.hdr = false;
    				m_OcclusionCamera.useOcclusionCulling = false;
    				m_OcclusionCamera.gameObject.hideFlags = HideFlags.HideAndDontSave;
    			}
    
    			return m_OcclusionCamera;
    		}
    	}
    	private Camera m_OcclusionCamera;
    
    	private Shader depthShader
    	{
    		get
    		{
    			if (m_DepthShader == null)
    			{
    				m_DepthShader = Shader.Find("Hidden/Camera-DepthNormalTexture");
    			}
    
    			return m_DepthShader;
    		}
    	}
    	private Shader m_DepthShader = null;
    
    	private Material occlusionMaterial
    	{
    		get
    		{
    			if (m_OcclusionMaterial == null)
    			{
    				m_OcclusionMaterial = new Material(Shader.Find("Yogi/ImageEffect/Occlusion"));
    				m_OcclusionMaterial.hideFlags = HideFlags.HideAndDontSave;
    			}
    
    			return m_OcclusionMaterial;
    		}
    	}
    	private Material m_OcclusionMaterial = null;
    
    	private RenderTexture depthMap;
    
    	private void OnPreRender()
    	{
    		depthMap = RenderTexture.GetTemporary(Screen.width, Screen.height, 16);
    
    		camera.depthTextureMode = DepthTextureMode.DepthNormals;
    
    		occlusionCamera.fieldOfView = camera.fieldOfView;
    		occlusionCamera.isOrthoGraphic = camera.isOrthoGraphic;
    		occlusionCamera.nearClipPlane = camera.nearClipPlane;
    		occlusionCamera.farClipPlane = camera.farClipPlane;
    		occlusionCamera.cullingMask = cullingMask;
    		occlusionCamera.targetTexture = depthMap;
    		occlusionCamera.RenderWithShader(depthShader, string.Empty);
    	}
    
    	private void OnPostRender()
    	{
    		RenderTexture.ReleaseTemporary(depthMap);
    	}
    
    	private void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
    	{
    		occlusionMaterial.SetTexture("_DepthMap", depthMap);
    		occlusionMaterial.SetTexture("_OcclusionMap", occlusionMap);
    		occlusionMaterial.SetFloat("_Intensity", intensity);
    		occlusionMaterial.SetVector("_Tiling", tiling);
    		Graphics.Blit(sourceTexture, destTexture, occlusionMaterial);
    	}
    
    	private void OnDisable()
    	{
    		OnDestroy();
    	}
    
    	private void OnDestroy()
    	{
    		if (null != m_OcclusionCamera)
    		{
    			if (Application.isPlaying)
    			{
    				Destroy(m_OcclusionCamera.gameObject);
    			}
    			else
    			{
    				DestroyImmediate(m_OcclusionCamera.gameObject);
    			}
    		}
    	}
    }

最後

        兩種方式實現出來的效果是不一樣的,Shader方式能表現法線紋理細節,ImageEffect方式只能表現模型法線,因為是從攝像機深度法線貼圖而來的。但是Shader方式需要更高的渲染序列,並且無時無刻佔用多一個Pass也因此多了一個DC,而ImageEffect方式比Shader擁有更高的控制自由度,可以單獨設定給一個玩家或者某幾個玩家渲染。Shader的透視效果需要關閉ZWrite,因此會有疊面的效果,而ImageEffect則沒有。