Unity HilightingSystem屏幕后实现物体外发光描边效果

Unity实现物体外发光描边效果方式有好几种,如重叠放大模型描边Pass、卷积核描边、屏幕后处理等。

HIightingSytem使用了屏幕后期效果实现,效果如下:

在这里插入图片描述

整理出核心代码如下,主要分为4个步骤。

1 ,根据场景上所有需要描边的物体轮廓,将它们画到一张RenderTexture上,其中黑色部分A通道值为0。实现着色器为HighlightingOpaque.Shader。

在这里插入图片描述

2,将RenderTexture模糊化,通过偏移uv混合周围像素的值,包括混合A通道的值。实现着色器为HighlightingBlur.Shader。

混合后的RGB通道的值

在这里插入图片描述

混合后A通道的值:

在这里插入图片描述

3,通过模板测试,裁剪和场景中描边模型重叠的像素,即在着色器中通过设置重叠部分A通道的值为0。实现着色器为HighlightingCut.Shader。

在这里插入图片描述

4,根据RenderTexture的A通道的值,和屏幕后的Source纹理进行和RenderTexture的RGB值进行差值混合,得出描边效果。

实现着色器为HighlightingComposit.Shader。

在这里插入图片描述

整理出核心代码如下:

OutlineObject.cs

OutlineObjectRender.cs

HighlightingOpaque.shader

HighlightingBlur.shader

HighlightingCut.shader

HighlightingComposite.shader


OutlineObject.cs

一个场景可能挂多个,必须挂在场景物体的有MeshRender/SkinMeshRender的节点上!

OutlineObjectRender.cs

必须挂在有MainCamera的节点上!


OutlineObject.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OutlineObject : MonoBehaviour
{
    
    
    public Color color = Color.red;
    [HideInInspector]
    public Renderer render;
    [HideInInspector]
    public Material objMat;
    private void Start()
    {
    
    
        render = GetComponent<MeshRenderer>();
        if (render == null)
            render = GetComponent<SkinnedMeshRenderer>();
        objMat = new Material(Shader.Find("Hidden/Highlighted/Opaque"));
        OutlineObjectRender.Instance.AddOutlineObject(this);
    }
}

OutlineObjectRender.cs

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
public class OutlineObjectRender : MonoBehaviour
{
    
    
    public static OutlineObjectRender Instance;

    private List<OutlineObject> outlineObjs = new List<OutlineObject>();
    private CommandBuffer buf;

                             //1,渲染OutlineObject对象成平面纹理;
    private Material blurMat;//2,将步骤1纹理模糊化,模糊边缘向平面周边扩散;
    private Material maskMat;//3,通过模板测试,不渲染步骤2纹理中和模型重叠的片元部分,只渲染模糊边框;
    private Material combineMat;//4,将当前相机渲染的屏幕结果,与步骤3得出的轮廓纹理结合得出描边效果。

    private RenderTextureDescriptor rtft;
    private RenderTexture rt;
    private RenderTargetIdentifier rtID;

    private int blur0;
    private int blur1;

    private void Awake()
    {
    
    
        Instance = this;
        blurMat = new Material(Shader.Find("Hidden/Highlighted/Blur"));
        maskMat = new Material(Shader.Find("Hidden/Highlighted/Cut"));
        combineMat = new Material(Shader.Find("Hidden/Highlighted/Composite"));

        blur0 = Shader.PropertyToID("Blur0");
        blur1 = Shader.PropertyToID("Blur1");

        buf = new CommandBuffer();
        Camera.main.AddCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, buf);
    }
    public void AddOutlineObject(OutlineObject obj) {
    
    
        if (this.outlineObjs.Contains(obj) == false)
        {
    
    
            this.outlineObjs.Add(obj);
        }
    }
    public void RemoveOutlineObject(OutlineObject obj) {
    
    
        if (this.outlineObjs.Contains(obj))
            this.outlineObjs.Remove(obj);
    }
    private void OnPreRender()
    {
    
    
        if (rt == null)
        {
    
    
            Camera cam = GetComponent<Camera>();
            rtft = new RenderTextureDescriptor(cam.pixelWidth, cam.pixelHeight, RenderTextureFormat.ARGB32, 24);
            rtft.colorFormat = RenderTextureFormat.ARGB32;
            rtft.sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear;
            rtft.useMipMap = false;
            rtft.msaaSamples = 8;

            rt = new RenderTexture(rtft);
            rt.filterMode = FilterMode.Point;
            rt.wrapMode = TextureWrapMode.Clamp;

            if (!rt.Create())
            {
    
    
                Debug.LogError("Failed to create RenderTexture!");
            }
            rtID = new RenderTargetIdentifier(rt);
            maskMat.SetFloat("_HighlightingFillAlpha", 0);
        }
        buf.Clear();
        buf.SetRenderTarget(rtID);
        buf.ClearRenderTarget(true, true, new Color(0, 0, 0, 0));

        RenderTextureDescriptor desc = rtft;
        desc.width = rt.width;
        desc.height = rt.height;
        desc.depthBufferBits = 0;

        for (int i = 0; i < this.outlineObjs.Count; i++)
        {
    
    
            //第一步,将所有要描边的render GameObject用Opaque渲染成2D面颜色,Draw到buf里。
            //所有的需要描边的Render对象都要在这里处理一下,用CommandBuff统一画到同一个Texture上,交给下面的步骤处理。
            outlineObjs[i].objMat.SetColor("_HighlightingColor", outlineObjs[i].color);
            buf.DrawRenderer(outlineObjs[i].render, outlineObjs[i].objMat);
        }

        //第二步,对buf的texture进行模糊。
        //获取2张模板用于模糊处理。
        buf.GetTemporaryRT(blur0, desc, FilterMode.Bilinear);
        buf.GetTemporaryRT(blur1, desc, FilterMode.Bilinear);
        RenderTargetIdentifier blurID0 = new RenderTargetIdentifier(blur0);
        RenderTargetIdentifier blurID1 = new RenderTargetIdentifier(blur1);
        //开始模糊循环之前,将rtID传到blurID。
        buf.Blit(rtID, blurID0);
        bool oddEven = true;
        for (int i = 0; i < 4; i++)
        {
    
    
            float off = 1.5f + 0.5f * i;
            buf.SetGlobalFloat(HighlightingSystem.ShaderPropertyID._HighlightingBlurOffset, off);

            if (oddEven)
            {
    
    
                buf.Blit(blurID0, blurID1, blurMat);
            }
            else
            {
    
    
                buf.Blit(blurID1, blurID0, blurMat);
            }
            oddEven = !oddEven;
        }
        //第三步,对得到模糊纹理进行模板测试,去除和模型重叠的片元像素,通过
        buf.Blit(oddEven ? blurID0 : blurID1, rtID, maskMat);

        //
        buf.SetGlobalTexture("_HighlightingBuffer", rtID);

        buf.ReleaseTemporaryRT(blur0);
        buf.ReleaseTemporaryRT(blur1);
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
    
    
        //第四步,与当前屏幕效果混合
        Graphics.Blit(source, destination, combineMat);
    }
}


HighlightingOpaque.shader

用于将场景上需要描边的物体渲染到RenderTexture上。

Shader "Hidden/Highlighted/Opaque"
{
    
    
	Properties
	{
    
    
		[HideInInspector] _HighlightingColor ("", Color) = (1, 1, 1, 1)
	}
	
	SubShader
	{
    
    
		Lighting Off
		Fog {
    
     Mode Off }
		ZWrite Off			// Manual depth test
		ZTest Always		// Manual depth test

		Pass
		{
    
    
			Stencil
			{
    
    
				Ref 1
				Comp Always
				Pass Replace
				ZFail Keep
			}

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#pragma target 2.0
			#pragma multi_compile __ HIGHLIGHTING_OVERLAY
			#include "UnityCG.cginc"

			uniform fixed4 _HighlightingColor;

			#ifndef HIGHLIGHTING_OVERLAY
			UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
			#endif
			
			struct vs_input
			{
    
    
				float4 vertex : POSITION;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct ps_input
			{
    
    
				float4 pos : SV_POSITION;

				#ifndef HIGHLIGHTING_OVERLAY
				float4 screen : TEXCOORD0;
				#endif
			};
			
			ps_input vert(vs_input v)
			{
    
    
				ps_input o;

				UNITY_SETUP_INSTANCE_ID(v);
				o.pos = UnityObjectToClipPos(v.vertex);

				#ifndef HIGHLIGHTING_OVERLAY
				o.screen = ComputeScreenPos(o.pos);
				COMPUTE_EYEDEPTH(o.screen.z);
				#endif

				return o;
			}

			fixed4 frag(ps_input i) : SV_Target
			{
    
    
				#ifndef HIGHLIGHTING_OVERLAY
				float z = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screen));
				float perspZ = LinearEyeDepth(z);	// LinearEyeDepth automatically handles UNITY_REVERSED_Z case
				#if defined(UNITY_REVERSED_Z)
				z = 1 - z;
				#endif
				float orthoZ = _ProjectionParams.y + z * (_ProjectionParams.z - _ProjectionParams.y);	// near + z * (far - near)
				float sceneZ = lerp(perspZ, orthoZ, unity_OrthoParams.w);
				clip(sceneZ - i.screen.z + 0.01);
				#endif

				return _HighlightingColor;
			}
			ENDCG
		}
	}
	Fallback Off
}

HighlightingBlur.shader

将RenderTexture模糊化,并混合A通道的值。

Shader "Hidden/Highlighted/Blur"
{
    
    
	Properties
	{
    
    
		[HideInInspector] _MainTex ("", 2D) = "" {
    
    }
		[HideInInspector] _HighlightingIntensity ("", Range (0.25,0.5)) = 0.3
	}
	
	SubShader
	{
    
    
		Pass
		{
    
    
			ZTest Always
			Cull Off
			ZWrite Off
			Lighting Off
			Fog {
    
     Mode Off }
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#pragma target 2.0
			#pragma multi_compile DIAGONAL_DIRECTIONS STRAIGHT_DIRECTIONS ALL_DIRECTIONS
			
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform float4 _MainTex_ST;
			uniform float4 _MainTex_TexelSize;
			
			uniform float _HighlightingBlurOffset;
			uniform half _HighlightingIntensity;

			struct vs_input
			{
    
    
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
			};

			struct ps_input
			{
    
    
				float4 pos : SV_POSITION;

				#if defined(ALL_DIRECTIONS)
				float4 uv0 : TEXCOORD0;
				float4 uv1 : TEXCOORD1;
				float4 uv2 : TEXCOORD2;
				float4 uv3 : TEXCOORD3;
				#else
				float4 uv0 : TEXCOORD0;
				float4 uv1 : TEXCOORD1;
				#endif
			};
			
			ps_input vert(vs_input v)
			{
    
    
				ps_input o;
				o.pos = UnityObjectToClipPos(v.vertex);

				float2 uv = UnityStereoScreenSpaceUVAdjust(v.texcoord, _MainTex_ST);
				float2 offs = _HighlightingBlurOffset * _MainTex_TexelSize.xy;

				#if defined(ALL_DIRECTIONS)

				// Diagonal
				o.uv0.x = uv.x - offs.x;
				o.uv0.y = uv.y - offs.y;
				
				o.uv0.z = uv.x + offs.x;
				o.uv0.w = uv.y - offs.y;
				
				o.uv1.x = uv.x + offs.x;
				o.uv1.y = uv.y + offs.y;
				
				o.uv1.z = uv.x - offs.x;
				o.uv1.w = uv.y + offs.y;

				// Straight
				o.uv2.x = uv.x - offs.x;
				o.uv2.y = uv.y;
				
				o.uv2.z = uv.x + offs.x;
				o.uv2.w = uv.y;
				
				o.uv3.x = uv.x;
				o.uv3.y = uv.y - offs.y;
				
				o.uv3.z = uv.x;
				o.uv3.w = uv.y + offs.y;

				#elif defined(STRAIGHT_DIRECTIONS)

				// Straight
				o.uv0.x = uv.x - offs.x;
				o.uv0.y = uv.y;
				
				o.uv0.z = uv.x + offs.x;
				o.uv0.w = uv.y;
				
				o.uv1.x = uv.x;
				o.uv1.y = uv.y - offs.y;
				
				o.uv1.z = uv.x;
				o.uv1.w = uv.y + offs.y;

				#else 

				// Diagonal
				o.uv0.x = uv.x - offs.x;
				o.uv0.y = uv.y - offs.y;
				
				o.uv0.z = uv.x + offs.x;
				o.uv0.w = uv.y - offs.y;
				
				o.uv1.x = uv.x + offs.x;
				o.uv1.y = uv.y + offs.y;
				
				o.uv1.z = uv.x - offs.x;
				o.uv1.w = uv.y + offs.y;

				#endif

				return o;
			}
			
			half4 frag(ps_input i) : SV_Target
			{
    
    
				half4 color1 = tex2D(_MainTex, i.uv0.xy);
				fixed4 color2;

				// For straight or diagonal directions
				color2 = tex2D(_MainTex, i.uv0.zw);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				color2 = tex2D(_MainTex, i.uv1.xy);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				color2 = tex2D(_MainTex, i.uv1.zw);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				// For all directions
				#if defined(ALL_DIRECTIONS)
				color2 = tex2D(_MainTex, i.uv2.xy);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				color2 = tex2D(_MainTex, i.uv2.zw);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				color2 = tex2D(_MainTex, i.uv3.xy);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;

				color2 = tex2D(_MainTex, i.uv3.zw);
				color1.rgb = max(color1.rgb, color2.rgb);
				color1.a += color2.a;
				#endif
				
				color1.a *= _HighlightingIntensity;
				return color1;
			}
			ENDCG
		}
	}
	
	Fallback off
}

HighlightingCut.shader

通过模板测试,将RenderTexture中和场景中重叠的像素A通道设置为0.

Shader "Hidden/Highlighted/Cut"
{
    
    
	Properties
	{
    
    
		[HideInInspector] _MainTex ("", 2D) = "" {
    
    }
		[HideInInspector] _HighlightingFillAlpha ("", Range(0.0, 1.0)) = 1.0
	}

	SubShader
	{
    
    
		Lighting Off
		Fog {
    
     Mode off }
		ZWrite Off
		ZTest Always
		Cull Back

		Pass
		{
    
    
			Stencil
			{
    
    
				Ref 1
				Comp NotEqual
				Pass Keep
				ZFail Keep
			}

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#pragma target 2.0
			
			#include "UnityCG.cginc"
			
			struct vs_input
			{
    
    
				float4 vertex : POSITION;
				half2 texcoord : TEXCOORD0;
			};
			
			struct ps_input
			{
    
    
				float4 pos : SV_POSITION;
				half2 uv : TEXCOORD0;
			};

			uniform sampler2D _MainTex;
			uniform float4 _MainTex_ST;
			
			ps_input vert(vs_input v)
			{
    
    
				ps_input o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = UnityStereoScreenSpaceUVAdjust(v.texcoord, _MainTex_ST);
				return o;
			}
			
			fixed4 frag(ps_input i) : SV_Target
			{
    
    
				return tex2D(_MainTex, i.uv);
			}
			ENDCG
		}

		Pass
		{
    
    
			Stencil
			{
    
    
				Ref 1
				Comp Equal
				Pass Keep
				ZFail Keep
			}
			ColorMask A
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#pragma target 2.0

			#include "UnityCG.cginc"
			
			struct vs_input
			{
    
    
				float4 vertex : POSITION;
			};
			
			struct ps_input
			{
    
    
				float4 pos : SV_POSITION;
			};

			uniform float _HighlightingFillAlpha;

			ps_input vert(vs_input v)
			{
    
    
				ps_input o;
				o.pos = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 frag() : SV_Target
			{
    
    
				return fixed4(0, 0, 0, _HighlightingFillAlpha);
			}
			ENDCG
		}
	}
	FallBack Off
}

HighlightingComposite.shader

根据RenderTexture中A通道的值,将RenderTexture的RGB值和屏幕纹理插值叠加。得出最后的描边效果。

Shader "Hidden/Highlighted/Composite"
{
    
    
	Properties
	{
    
    
		[HideInInspector] _MainTex ("", 2D) = "" {
    
    }
		[HideInInspector] _HighlightingBuffer ("", 2D) = "" {
    
    }
	}
	
	SubShader
	{
    
    
		Pass
		{
    
    
			Lighting Off
			Fog {
    
     Mode off }
			ZWrite Off
			ZTest Always
			Cull Off
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#pragma target 2.0
			#include "UnityCG.cginc"

			struct vs_input
			{
    
    
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
			};

			struct ps_input
			{
    
    
				float4 pos : SV_POSITION;
				half2 uv0 : TEXCOORD0;
				half2 uv1 : TEXCOORD1;
			};

			uniform sampler2D _MainTex;
			uniform float4 _MainTex_ST;
			uniform float4 _MainTex_TexelSize;
			uniform sampler2D _HighlightingBuffer;

			ps_input vert(vs_input v)
			{
    
    
				ps_input o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv0 = UnityStereoScreenSpaceUVAdjust(v.texcoord, _MainTex_ST);
				o.uv1 = o.uv0;

				#if UNITY_UV_STARTS_AT_TOP
				if (_MainTex_TexelSize.y < 0)
				{
    
    
					o.uv1.y = 1-o.uv1.y;
				}
				#endif

				return o;
			}

			fixed4 frag(ps_input i) : SV_Target
			{
    
    
				fixed4 c1 = tex2D(_MainTex, i.uv0);
				fixed4 c2 = tex2D(_HighlightingBuffer, i.uv1);
				c1.rgb = lerp(c1.rgb, c2.rgb, c2.a);
				return c1;
			}
			ENDCG
		}
	}
	FallBack Off
}

猜你喜欢

转载自blog.csdn.net/u012740992/article/details/104110980