【Unity ImageEffect】一個用於角色遮擋透視輪廓的效果
阿新 • • 發佈:2019-02-01
前言
有些遊戲專案需要在角色被遮擋後仍然顯示出來,讓玩家不會迷失自我的感覺,但一般不會用原顏色渲染出來,一般會用另外的顏色表示。本文介紹一種當角色被遮擋後,用另外一種顏色並有輪廓漸變效果的渲染方法。
方案
- Shader方式:在角色Shader加一道Pass,在深度檢測時判斷被遮擋後渲染角色輪廓。
- ImageEffect方式:利用Camera的深度資訊,判斷角色被遮擋後渲染角色輪廓。
實現
- Shader方式:
在角色的Shader上加上一個 UsePass "Yogi/Occlusion/BASE" 就可以看到效果了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" }
- 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則沒有。