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  
        }  

    }  
}
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值