Unity PostProcess Shader - 13 - Outline - 基于后效的外轮廓效果,类似 PhotoShop 图层的外发光

141 篇文章 31 订阅
49 篇文章 0 订阅


之前写的一个类似Photoshop中的图层的 外发光 效果,其实准确的来说,我这个应该叫:基于像素颜色的外轮廓的描边,和描边的思路很像而已


管线:built-in 管线
unity 版本:2019.4.30f1


思路

只要某个像素的 kernal size 范围内的,如 5x5 的 kernal size,只要某个 pixel(x,y) 像素作为的 5x5 范围内的像素有一个不是为全黑的像素,都算是边缘


Shader

// jave.lin 2021/09/24
Shader "PP/OutlinePP"
{
    CGINCLUDE
        #include "UnityCG.cginc"

        // pure col
        float4 vert_pure_col(float4 vertex : POSITION) : SV_POSITION
        {
            return UnityObjectToClipPos(vertex);
        }
        fixed4 frag_pure_col() : SV_Target
        {
            return 1;
        }

        // expand
        Sampler2D _ExpandOrginTex;
        float4 _ExpandOrginTex_TexelSize;
        int _OutlineKernelSize;
        float _OutlineSize;
        struct a2v_expand
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };
        struct v2f_expand
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
        };
        v2f_expand vert_expand (a2v_expand v) {
            v2f_expand o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            return o;
        }
        fixed4 frag_expand(v2f_expand i) : SV_Target
        {
            fixed sum = 0;
            int start = -_OutlineKernelSize;
            int count = _OutlineKernelSize + 1;

            for (int x = start; x < count; x++)
            {
                for (int y = start; y < count; y++)
                {
                    float2 temp_uv = i.uv + float2(x, y) * _ExpandOrginTex_TexelSize.xy * _OutlineSize;
                    sum += tex2D(_ExpandOrginTex, temp_uv);
                }
            }
            
            return sum != 0 ? 1 : 0;
        }

        /// final /
        Sampler2D _ExpandTex;
        Sampler2D _SrcTex;
        fixed4 _OutlineColor;
        struct a2v_final
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };
        struct v2f_final
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
        };
        v2f_final vert_final (a2v_final v) {
            v2f_final o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            return o;
        }
        fixed4 frag_final (v2f_final i) : SV_Target {
            fixed expand_col = tex2D(_ExpandTex, i.uv).r;
            fixed origin_col = tex2D(_ExpandOrginTex, i.uv).r;
            fixed value = saturate(expand_col - origin_col);
            fixed4 combinedCol = saturate(tex2D(_SrcTex, i.uv));
            combinedCol.rgb += (value * _OutlineColor.a) * _OutlineColor.rgb;
            return combinedCol;
        }
    ENDCG
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always
        Pass // pure col 0
        {
            
            ColorMask R
            CGPROGRAM
            #pragma vertex vert_pure_col
            #pragma fragment frag_pure_col
            ENDCG
        }
        Pass // expand 1
        {
            ColorMask R
            CGPROGRAM
            #pragma vertex vert_expand
            #pragma fragment frag_expand
            ENDCG
        }
        Pass // final 2
        {
            CGPROGRAM
            #pragma vertex vert_final
            #pragma fragment frag_final
            ENDCG
        }
    }
}


CSharp


Camera PP 脚本

// jave.lin 2021/09/24
// 外轮廓 后效

using UnityEngine;
using UnityEngine.Rendering;

public class OutlinePP : PostEffectBasic
{
    private static int _ExpandOrginTex      = Shader.PropertyToID("_ExpandOrginTex");
    private static int _ExpandTex           = Shader.PropertyToID("_ExpandTex");
    private static int _SrcTex              = Shader.PropertyToID("_SrcTex");
    private static int _OutlineKernelSize   = Shader.PropertyToID("_OutlineKernelSize");
    private static int _OutlineSize         = Shader.PropertyToID("_OutlineSize");
    private static int _OutlineColor        = Shader.PropertyToID("_OutlineColor");

    [Header("材质")]
    public Material mat;

    [Header("轮廓核大小")]
    [Range(0.0f, 2.0f)]
    public int outlineKernelSize = 1;

    [Header("轮廓大小")]
    [Range(0.0f, 10.0f)]
    public float outlineSize = 5.0f;

    [Header("轮廓颜色")]
    public Color outline_color = Color.red;

    private CommandBuffer cmdBuffer;
    private Camera cam;

    protected override void Start()
    {
        base.Start();
        cmdBuffer = new CommandBuffer();
        cmdBuffer.name = "OutlinePPCmdBuffer";
        cam = GetComponent<Camera>();
    }

    private void OnDestroy()
    {
        if (cmdBuffer != null)
        {
            cmdBuffer.Clear();
            cmdBuffer.Dispose();
            cmdBuffer = null;
        }
        cam.targetTexture = null;
        cam = null;
    }

    protected override void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (!IsSupported || OutlineManager.instance.Count == 0)
        {
            GraphicsUtil.SkipOnImage(src, dest);
            return;
        }

        if (mat == null)
        {
            Debug.LogError("Outline.mat == null");
            GraphicsUtil.SkipOnImage(src, dest);
            return;
        }

        ShowOutline(src, dest, mat);
    }

    private void ShowOutline(RenderTexture src, RenderTexture dest, Material usingMat)
    {
        var curCamRT = cam.targetTexture;
        var rw = curCamRT == null ? Screen.width : curCamRT.width;
        var rh = curCamRT == null ? Screen.height : curCamRT.height;

        // create RT
        var expandOrginRT = RenderTexture.GetTemporary(rw, rh, 0, RenderTextureFormat.R8);
        expandOrginRT.filterMode = FilterMode.Bilinear;
        expandOrginRT.name = "OutlinePP.expandOrginRT";

        cmdBuffer.Clear();
        cmdBuffer.SetRenderTarget(expandOrginRT);
        cmdBuffer.ClearRenderTarget(false, true, Color.black);

        OutlineManager.instance.Update2CmdBuffer2Draw(cmdBuffer, usingMat, 0);
        // execute cmd buffer, Draw To RT
        Graphics.ExecuteCommandBuffer(cmdBuffer);

        // expand
        var expandRT = RenderTexture.GetTemporary(rw, rh, 0, RenderTextureFormat.R8);
        expandRT.filterMode = FilterMode.Bilinear;
        expandRT.name = "OutlinePP.expandRT";

        usingMat.SetInt(_OutlineKernelSize, outlineKernelSize);
        usingMat.SetFloat(_OutlineSize, outlineSize);
        usingMat.SetTexture(_ExpandOrginTex, expandOrginRT);
        Graphics.Blit(null, expandRT, usingMat, 1);

        // final
        usingMat.SetTexture(_ExpandTex, expandRT);
        usingMat.SetTexture(_SrcTex, src);
        usingMat.SetColor(_OutlineColor, outline_color);
        Graphics.Blit(null, dest, usingMat, 2);

        RenderTexture.ReleaseTemporary(expandOrginRT);
        RenderTexture.ReleaseTemporary(expandRT);
    }
}

Outline Renderer 提取器

挂载在对应的 Root 下,会自动提取底下所有的 renderer

// jave.lin 2021/09/24
// Outline 后效绘制的 Renderer 提取组件

using System.Collections.Generic;
using UnityEngine;

public class OutlineRendererExtractor : MonoBehaviour
{
    public bool extractEveryFrame = false;

    //private List<Renderer> renderers;
    public List<Renderer> renderers; // 暂时 public 便于 inspector 中查看
    private int instID;

    private void Awake()
    {
        instID = GetInstanceID();
        renderers = ListPoolUtil<Renderer>.FromPool();
    }
    private void Start()
    {
        UpdateRenderers();
    }
    private void Update()
    {
        if (extractEveryFrame)
        {
            UpdateRenderers();
        }
    }
    private void OnDestroy()
    {
        if (renderers != null)
        {
            ListPoolUtil<Renderer>.ToPool(renderers);
            renderers = null;
        }
    }
    private void UpdateRenderers()
    {
        var mrList = ListPoolUtil<MeshRenderer>.FromPool();
        gameObject.GetComponentsInChildren<MeshRenderer>(false, mrList);

        var smrList = ListPoolUtil<SkinnedMeshRenderer>.FromPool();
        gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(false, smrList);

        renderers.Clear();
        renderers.AddRange(mrList);
        renderers.AddRange(smrList);

        ListPoolUtil<MeshRenderer>.ToPool(mrList);
        ListPoolUtil<SkinnedMeshRenderer>.ToPool(smrList);

        OutlineManager.instance.Remove(instID);
        OutlineManager.instance.Add(instID, renderers);
    }
}


OutlineManager

// jave.lin 2021/02/25
// 外发光的管理
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class OutlineManager : MonoSingleton<OutlineManager>
{
    private Stack<List<Renderer>> listPool = new Stack<List<Renderer>>();
    private Stack<OutlineElement> outlineElementPool = new Stack<OutlineElement>();

    private List<OutlineElement> outlineElementList = new List<OutlineElement>();
    private Dictionary<int, OutlineElement> outlineElementDict_key_instid = new Dictionary<int, OutlineElement>();
    private Dictionary<GameObject, OutlineElement> outlineElementDict_key_go = new Dictionary<GameObject, OutlineElement>();


    public int Count => outlineElementList.Count;

    private void OnDestroy()
    {
        if (listPool != null)
        {
            foreach (var item in listPool)
            {
                item.Clear();
            }
            listPool.Clear();
            listPool = null;
        }
        if (outlineElementPool != null)
        {
            outlineElementPool.Clear();
            outlineElementPool = null;
        }
        if (listPool != null)
        {
            listPool.Clear();
            listPool = null;
        }
    }
    public bool Contains(GameObject go)
    {
        foreach (var glowElement in outlineElementList)
        {
            if (glowElement.go == go)
            {
                return true;
            }
        }
        return false;
    }
    public void Add(int instID, List<Renderer> renderers)
    {
        OutlineElement e = outlineElementPool.Count > 0 ? outlineElementPool.Pop() : null;

        if (e == null)
        {
            e = new OutlineElement 
            { 
                optType = eOutlineOptType.SpecialRenderers, 
                instID = instID, 
                go = null, 
                renderers = new List<Renderer>(renderers), 
                ignoreActive = false 
            };
        }
        else
        {
            e.optType = eOutlineOptType.SpecialRenderers;
            e.instID = instID;
            e.go = null;
            e.renderers.AddRange(renderers);
            e.ignoreActive = false;
        }

        outlineElementDict_key_instid[instID] = e;
        outlineElementList.Add(e);
        //Log.logError($"OutlineManager.Add instID:{instID}");
    }
    public void Add(GameObject go, bool ignoreActive = false, int ignoreLayer = 0)
    {
        if (ignoreLayer == -1)
        {
            // culling everything
            return;
        }
        if (go == null)
        {
            return;
        }

        if (Contains(go))
        {
            return;
        }

        var list = listPool.Count > 0 ? listPool.Pop() : new List<Renderer>();
        go.GetComponentsInChildren<Renderer>(false, list);

        OutlineElement e = outlineElementPool.Count > 0 ? outlineElementPool.Pop() : null;

        if (ignoreLayer != 0)
        {
            var count = list.Count;
            for (int i = 0; i < count; i++)
            {
                if (list[i].gameObject.layer == ignoreLayer)
                {
                    list.RemoveAt(i);
                    --i;
                    --count;
                    continue;
                }
            }
        }

        if (e == null)
        {
            e = new OutlineElement { optType = eOutlineOptType.SpecialGO, instID = -1, go = go, renderers = list, ignoreActive = ignoreActive };
        }
        else
        {
            e.optType = eOutlineOptType.SpecialGO;
            e.instID = -1;
            e.go = go;
            e.renderers = list;
            e.ignoreActive = ignoreActive;
        }
        outlineElementDict_key_go[go] = e;
        outlineElementList.Add(e);
    }
    public void Remove(GameObject go)
    {
        if (go == null)
        {
            return;
        }

        for (int i = 0; i < outlineElementList.Count; i++)
        {
            var e = outlineElementList[i];
            if (e.go == null)
            {
                _Reclyle(e);
                outlineElementList.RemoveAt(i);
                continue;
            }
            if (e.go == go)
            {
                _Reclyle(e);
                outlineElementList.RemoveAt(i);
                return;
            }
        }
        outlineElementDict_key_go.Remove(go);
    }
    public void Remove(int instID)
    {
        if (outlineElementDict_key_instid.Remove(instID))
        {
            for (int i = 0; i < outlineElementList.Count; i++)
            {
                var e = outlineElementList[i];
                if (e.instID == instID)
                {
                    _Reclyle(e);
                    outlineElementList.RemoveAt(i);
                    break;
                }
            }
        }
        
        //Log.logError($"OutlineManager.Remove instID:{instID}");
    }
    public void Clear()
    {
        if (outlineElementList.Count > 0)
        {
            foreach (var e in outlineElementList)
            {
                _Reclyle(e);
            }
            outlineElementList.Clear();
        }
        outlineElementList.Clear();
        outlineElementDict_key_go.Clear();
    }
    public void Update2CmdBuffer2Draw(CommandBuffer cmdBuffer, Material material, int pass = -1)
    {
        for (int i = outlineElementList.Count - 1; i > -1; i--)
        {
            var e = outlineElementList[i];
            switch (e.optType)
            {
                case eOutlineOptType.SpecialGO:
                    if (e.go == null)
                    {
                        _Reclyle(e);
                        outlineElementList.RemoveAt(i);
                        continue;
                    }
                    if (!e.ignoreActive)
                    {
                        if (!e.go.activeInHierarchy)
                        {
                            continue;
                        }
                    }

                    for (int j = 0; j < e.renderers.Count; j++)
                    {
                        var r = e.renderers[j];
                        var draw = r != null;
                        if (draw && !e.ignoreActive)
                        {
                            draw = r.enabled && r.gameObject.activeInHierarchy;
                        }
                        if (draw)
                        {
                            cmdBuffer.DrawRenderer(r, material, 0, pass);
                        }
                    }
                    break;
                case eOutlineOptType.SpecialRenderers:
                    for (int j = 0; j < e.renderers.Count; j++)
                    {
                        var r = e.renderers[j];
                        if (r == null || r.gameObject == null)
                        {
                            _Reclyle(e);
                            outlineElementList.RemoveAt(i);
                            break;
                        }
                        var draw = true;
                        if (!e.ignoreActive)
                        {
                            draw = r.enabled && r.gameObject.activeInHierarchy;
                        }
                        if (draw)
                        {
                            cmdBuffer.DrawRenderer(r, material, 0, pass);
                        }
                    }
                    break;
                default:
                    Debug.LogError($"Unimplements OutlineOptType : {e.optType}");
                    break;
            }
        }
    }
    private void _Reclyle(OutlineElement e)
    {
        if (e.optType == eOutlineOptType.SpecialGO)
        {
            outlineElementDict_key_go.Remove(e.go);
        }
        else
        {
            outlineElementDict_key_instid.Remove(e.instID);
        }
        e.renderers.Clear();
        listPool.Push(e.renderers);
        outlineElementPool.Push(e);
    }
}

public enum eOutlineOptType
{
    SpecialGO,
    SpecialRenderers,
}

public class OutlineElement
{
    public eOutlineOptType optType;
    public int instID;      // key, when optType == Special Instance ID
    public GameObject go;   // key, when optType == Special GO
    public List<Renderer> renderers;
    public bool ignoreActive;
}


效果

原图
在这里插入图片描述
加上外发光效果
在这里插入图片描述
GIF,颜色可以调整外发光大小,还有透明度
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值