Unity後處理實現物體外描邊

效果

這裏寫圖片描述

原理

使用後處理,

  1. 在後處理階段先渲染產生一張RenderTexture,包含要被描邊的物體,使用描邊色渲染。
  2. 高斯模糊RenderTexture,會產生邊緣
  3. 用高斯模糊的圖片反向剔除未模糊的圖,這樣只保留模糊多出的部分。
  4. 此時RenderTexture上爲只有邊緣的圖,將此邊緣圖與渲染結果圖進行混合並輸出屏幕,即得到結果。

實現

OutLineCameraComponent.cs 掛載在攝像機下,賦值兩個shader。

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


[ExecuteInEditMode]
public class OutLineCameraComponent
{  
    private RenderTexture renderTexture = null;  
    private CommandBuffer commandBuffer = null;  

    [Header("Outline/OutLineEffect.shader")]
    public Shader preoutlineShader = null;  
   [Header("Outline/OutlinePrePass.shader")]
     public Shader shader = null;

    private Material _material = null;

    public Material mMaterial
    {
        get
        {
            if (_material == null)
                _material = GenerateMaterial(shader);
            return _material;
        }
    }



    [Header("採樣範圍")]
    public float samplerArea = 1;  


    [Header("降分辨率")]
    public int downSample = 1;  


    [Header("迭代次數")]
    public int iteration = 2;

    [Header("描邊強度")]
    [Range(0.0f, 10.0f)]  
    public float outLineStrength = 3.0f;  

    //根據shader創建用於屏幕特效的材質
        protected Material GenerateMaterial(Shader shader)
        {
            if (shader == null)
                return null;

            if (shader.isSupported == false)
                return null;
            Material material = new Material(shader);
            material.hideFlags = HideFlags.DontSave;

            if (material)
                return material;

            return null;
        }


    //目標對象  
    private List<OutLineTargetComponent> targetObjects = new List<OutLineTargetComponent>();

    public void AddTarget(OutLineTargetComponent target)
    {
        if(target.material==null)
            target.material = new Material(preoutlineShader);
        targetObjects.Add(target);

        RefreshCommandBuff();

    }
    public void RemoveTarget(OutLineTargetComponent target)
    {
        bool found = false;

        for (int i = 0; i < targetObjects.Count; i++)
        {
            if (targetObjects[i] == target)
            {
                targetObjects.Remove(target);
                DestroyImmediate(target.material);
                target.material = null;
                found = true;
                break;
            }
        }

        if(found)
            RefreshCommandBuff();
    }

    public void RefreshCommandBuff()
    {
        if (renderTexture)  
            RenderTexture.ReleaseTemporary(renderTexture);  
        renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);  

        commandBuffer = new CommandBuffer();  
        commandBuffer.SetRenderTarget(renderTexture);  
        commandBuffer.ClearRenderTarget(true, true, Color.black);
        for (int i = 0; i < targetObjects.Count; i++)
        {
            Renderer[] renderers = targetObjects[i].GetComponentsInChildren<Renderer>();  
            foreach (Renderer r in renderers)
                commandBuffer.DrawRenderer(r, targetObjects[i].material);
        }
    }


   void OnEnable()  
    {  
        if (preoutlineShader == null)  
            return;  
        RefreshCommandBuff();
    }  

    void OnDisable()  
    {  
        if (renderTexture)  
        {  
            RenderTexture.ReleaseTemporary(renderTexture);  
            renderTexture = null;  
        }  

        if (commandBuffer != null)  
        {  
            commandBuffer.Release();  
            commandBuffer = null;  
        }  

    }  

    void OnRenderImage(RenderTexture source, RenderTexture destination)  
    {  
        if (mMaterial && renderTexture && commandBuffer != null)  
        {  
            Graphics.ExecuteCommandBuffer(commandBuffer);  

            //對RT進行Blur處理  
            RenderTexture temp1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);  
            RenderTexture temp2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);  

            //高斯模糊,兩次模糊,橫向縱向,使用pass0進行高斯模糊  
            mMaterial.SetVector("_offsets", new Vector4(0, samplerArea, 0, 0));  
            Graphics.Blit(renderTexture, temp1, mMaterial, 0);  
            mMaterial.SetVector("_offsets", new Vector4(samplerArea, 0, 0, 0));  
            Graphics.Blit(temp1, temp2, mMaterial, 0);  

            //如果有疊加再進行迭代模糊處理  
            for(int i = 0; i < iteration; i++)  
            {  
                mMaterial.SetVector("_offsets", new Vector4(0, samplerArea, 0, 0));  
                Graphics.Blit(temp2, temp1, mMaterial, 0);  
                mMaterial.SetVector("_offsets", new Vector4(samplerArea, 0, 0, 0));  
                Graphics.Blit(temp1, temp2, mMaterial, 0);  
            }  

            //用模糊圖和原始圖計算出輪廓圖
            mMaterial.SetTexture("_BlurTex", temp2);  
            Graphics.Blit(renderTexture, temp1, mMaterial, 1);  

            //輪廓圖和場景圖疊加  
            mMaterial.SetTexture("_BlurTex", temp1);  
            mMaterial.SetFloat("_OutlineStrength", outLineStrength);  
            Graphics.Blit(source, destination, mMaterial, 2);  

            RenderTexture.ReleaseTemporary(temp1);  
            RenderTexture.ReleaseTemporary(temp2);  
        }  
        else  
        {  
            Graphics.Blit(source, destination);  
        }  
    }  
}  

OutLineTargetComponent.cs 掛載在要使用描邊的物體上

using UnityEngine;

[ExecuteInEditMode]
public class OutLineTargetComponent : MonoBehaviour
{
    public Color color = Color.green;

    public Material material { set; get; }

    void OnEnable()
    {
        Camera[] allCameras = Camera.allCameras;
        for (int i = 0; i < allCameras.Length; i++)
        {
            if (allCameras[i].GetComponent<OutLineCameraComponent>()!=null)
            {
                allCameras[i].GetComponent<OutLineCameraComponent>().AddTarget(this);
            }
        }
    }

    private void Update()
    {
        if(material!=null)
            material.SetColor("_OutLineColor", color);  
    }

    void OnDisable()
    {
        Camera[] allCameras = Camera.allCameras;
        for (int i = 0; i < allCameras.Length; i++)
        {
            if (allCameras[i].GetComponent<OutLineCameraComponent>()!=null)
            {
                allCameras[i].GetComponent<OutLineCameraComponent>().RemoveTarget(this);
            }
        }
    }
}

OutlinePrePass.shader

Shader "OutLine/OutlinePrePass"  
{
    SubShader  
    {
        Pass  
        {
            CGPROGRAM
            #include "UnityCG.cginc"  
            fixed4 _OutLineColor;

            struct v2f  
            {  
                float4 pos : SV_POSITION;  
            };  

            v2f vert(appdata_full v)  
            {  
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
                return o;  
            }

            fixed4 frag(v2f i) : SV_Target  
            {  
                return _OutLineColor;
            }  


            #pragma vertex vert  
            #pragma fragment frag  
            ENDCG  
        }  
    }  
}  

OutLineEffect.shader

Shader "OutLine/OutLineEffect" {  

    Properties{  
        _MainTex("Base (RGB)", 2D) = "white" {}  
        _BlurTex("Blur", 2D) = "white"{}  
    }  

    CGINCLUDE  
    #include "UnityCG.cginc"  

    //用於剔除中心留下輪廓  
    struct v2f_cull  
    {  
        float4 pos : SV_POSITION;  
        float2 uv : TEXCOORD0;  
    };  

    //用於模糊  
    struct v2f_blur  
    {  
        float4 pos : SV_POSITION;  
        float2 uv  : TEXCOORD0;  
        float4 uv01 : TEXCOORD1;  
        float4 uv23 : TEXCOORD2;  
        float4 uv45 : TEXCOORD3;  
    };  

    //用於最後疊加  
    struct v2f_add  
    {  
        float4 pos : SV_POSITION;  
        float2 uv  : TEXCOORD0;  
        float2 uv1 : TEXCOORD1;  
    };  

    sampler2D _MainTex;  
    float4 _MainTex_TexelSize;  
    sampler2D _BlurTex;  
    float4 _BlurTex_TexelSize;  
    float4 _offsets;  
    float _OutlineStrength;  

    //獲得輪廓 pass 1
    v2f_cull vert_cull(appdata_img v)  
    {  
        v2f_cull o;  
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
        o.uv = v.texcoord.xy;  
        //dx中紋理從左上角爲初始座標,需要反向  
#if UNITY_UV_STARTS_AT_TOP  
        if (_MainTex_TexelSize.y < 0)  
            o.uv.y = 1 - o.uv.y;  
#endif    
        return o;  
    }  

    fixed4 frag_cull(v2f_cull i) : SV_Target  
    {  
        fixed4 colorMain = tex2D(_MainTex, i.uv);  
        fixed4 colorBlur = tex2D(_BlurTex, i.uv);  
        return colorBlur - colorMain;  
    }  

    //高斯模糊 pass 0
    v2f_blur vert_blur(appdata_img v)  
    {  
        v2f_blur o;  
        _offsets *= _MainTex_TexelSize.xyxy;  
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
        o.uv = v.texcoord.xy;  

        o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
        o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
        o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;  

        return o;  
    }  
    fixed4 frag_blur(v2f_blur i) : SV_Target  
    {  
        fixed4 color = fixed4(0,0,0,0);  
        color += 0.40 * tex2D(_MainTex, i.uv);  
        color += 0.15 * tex2D(_MainTex, i.uv01.xy);  
        color += 0.15 * tex2D(_MainTex, i.uv01.zw);  
        color += 0.10 * tex2D(_MainTex, i.uv23.xy);  
        color += 0.10 * tex2D(_MainTex, i.uv23.zw);  
        color += 0.05 * tex2D(_MainTex, i.uv45.xy);  
        color += 0.05 * tex2D(_MainTex, i.uv45.zw);  
        return color;  
    }  



    //最終疊加 pass 2
    v2f_add vert_final(appdata_img v)  
    {  
        v2f_add o;  
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
        o.uv.xy = v.texcoord.xy;  
        o.uv1.xy = o.uv.xy;  
#if UNITY_UV_STARTS_AT_TOP  
        if (_MainTex_TexelSize.y < 0)  
            o.uv.y = 1 - o.uv.y;  
#endif    
        return o;  
    }  

    fixed4 frag_final(v2f_add i) : SV_Target  
    {  
        fixed4 ori = tex2D(_MainTex, i.uv1);  
        fixed4 blur = tex2D(_BlurTex, i.uv);  
        fixed4 final = ori + blur * _OutlineStrength;  
        return final;  
    }  

        ENDCG  

    SubShader  
    {  
        //pass 0: 高斯模糊  
        Pass  
        {  
            ZTest Off  
            Cull Off  
            ZWrite Off  
            Fog{ Mode Off }  

            CGPROGRAM  
            #pragma vertex vert_blur  
            #pragma fragment frag_blur  
            ENDCG  
        }  

        //pass 1: 剔除中心部分   
        Pass  
        {  
            ZTest Off  
            Cull Off  
            ZWrite Off  
            Fog{ Mode Off }  

            CGPROGRAM  
            #pragma vertex vert_cull
            #pragma fragment frag_cull
            ENDCG  
        }  


        //pass 2: 最終疊加  
        Pass  
        {  

            ZTest Off  
            Cull Off  
            ZWrite Off  
            Fog{ Mode Off }  

            CGPROGRAM  
            #pragma vertex vert_final
            #pragma fragment frag_final  
            ENDCG  
        }  

    }  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章