unity shader基于后处理的硬描边

       网上的描边实现大部分都是法线外扩、模板、后处理这种的,我想实现一个类似U3D编辑界面那种硬质描边,法线外扩的缺点大部分人都知道:遇到“硬边”,法线外扩就会造成描边断裂,非常影响美观。模板又要修改模型材质操作有点复杂。而后处理就挺简单修改模型的显示层单独渲染一遍就ok了,但是目前网上描边都做成了“自发光”,边不够“硬”,而且由于模型上需要绑碰撞,换层有点不“合理”;了解到CommandBuffer可以解决这个顾虑,需要操作的仅是把模型添加入CommandBuffer单独绘制即可,很满足我的需求,那就改一下吧。

        首先需要软描边(类自发光)的大佬可以参考这篇文章,我也是参考这个大佬改的:传送门

        具体操作是首先新建脚本OutLineCameraComponent.cs,拷贝上代码并挂载到主相机上。

/****************************************************
   文件:OutLineCameraComponent.cs
   名称:后处理描边相机组件
   说明:使用时需要将脚本挂载到三维模型相机上。主要参考文章:https://blog.csdn.net/vikingsc2007_1/article/details/76570931
   
   by--Sep
*****************************************************/
using System;
using UnityEngine;  
using System.Collections.Generic;
using UnityEngine.Rendering;  


// [ExecuteInEditMode]
public class OutLineCameraComponent : MonoBehaviour
{  
    private RenderTexture renderTexture = null;  
    private CommandBuffer commandBuffer = null;  
    
    private RenderTexture temp1 = null;  
    private RenderTexture temp2 = null;  

    [Header("单色shader")]
    public Shader preoutlineShader = null;  
   [Header("描边shader")]
     public Shader shader = null;

     private Material _preoutlineMaterial = null;
    private Material _material = null;
    public Color outLineColor;

    public Material mMaterial
    {
        get
        {
            if (_material == null)
            {
                _material = GenerateMaterial(shader);
                _material.SetColor("_OutlineColor", outLineColor);  
            }
            return _material;
        }
    }
    [Header("降分辨率,与描边宽度对应")]
    public int downSample = 2;  

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

    //目标对象  
    private BaseModel targetModel;

    private void Awake()
    {
        _preoutlineMaterial = GenerateMaterial(preoutlineShader);
        _preoutlineMaterial.SetColor("_MainColor",outLineColor);
    }

    //根据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;
    }
    public void AddTarget(BaseModel target)
    {
        targetModel = target;
        RefreshCommandBuff();

    }
    public void RemoveTarget(BaseModel target)
    {
            RefreshCommandBuff();
    }

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

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

    /// <summary>
    /// 绘制描边
    /// </summary>
    /// <param name="source"></param>
    /// <param name="destination"></param>
    void OnRenderImage(RenderTexture source, RenderTexture destination)  
    {  
        if (mMaterial && renderTexture && commandBuffer != null)  
        {  
            Graphics.ExecuteCommandBuffer(commandBuffer);  
            

            //查找边界
            Graphics.Blit(renderTexture, temp1, mMaterial, 0);  
            
            //用模糊图和原始图计算出轮廓图
            mMaterial.SetTexture("_BlurTex", temp1);  
            Graphics.Blit(renderTexture, temp2, mMaterial, 1);  

            //轮廓图和场景图叠加  
            mMaterial.SetTexture("_BlurTex", temp2);  
            mMaterial.SetFloat("_OutlineStrength", outLineStrength);  
            Graphics.Blit(source, destination, mMaterial, 2);  
            
            RenderTexture.ReleaseTemporary(temp1);  
            RenderTexture.ReleaseTemporary(temp2);  
        }  
        else  
        {  
            Graphics.Blit(source, destination);  
        }  
    }

    private void OnDestroy()
    {
        if (renderTexture)  
        {  
            RenderTexture.ReleaseTemporary(renderTexture);  
            renderTexture = null;  
        }  
        if (temp1)  
        {  
            RenderTexture.ReleaseTemporary(temp1);  
            temp1 = null;  
        }  
        if (temp2)  
        {  
            RenderTexture.ReleaseTemporary(temp2);  
            temp2 = null;  
        }  

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

        然后新建OutLineEffect.shader,再把shader代码怼进去:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "SepShader/PostEffect/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[9] : TEXCOORD0;
    };  

    //用于最后叠加  
    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;
    fixed4 _OutlineColor;

    //获得轮廓 pass 1
    v2f_cull vert_cull(appdata_img v)  
    {  
        v2f_cull o;  
        o.pos = UnityObjectToClipPos(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;
        float2 width = _MainTex_TexelSize.xyxy;
        o.pos = UnityObjectToClipPos(v.vertex);
        
        o.uv[0] = v.texcoord + half2(-1, -1) * width;
        o.uv[1] = v.texcoord + half2(0, -1) * width;
        o.uv[2] = v.texcoord + half2(1, -1) * width;
        o.uv[3] = v.texcoord + half2(-1, 0) * width;
        o.uv[4] = v.texcoord + half2(0, 0) * width;
        o.uv[5] = v.texcoord + half2(1, 0) * width;
        o.uv[6] = v.texcoord + half2(-1, 1) * width;
        o.uv[7] = v.texcoord + half2(0, 1) * width;
        o.uv[8] = v.texcoord + half2(1, 1) * width;
        return o;  
    }  
    fixed4 frag_blur(v2f_blur i) : SV_Target  
    {
        half outValue = 0;
        outValue += tex2D(_MainTex, i.uv[0]).b;  
        outValue += tex2D(_MainTex, i.uv[1]).b;  
        outValue += tex2D(_MainTex, i.uv[2]).b;  
        outValue += tex2D(_MainTex, i.uv[3]).b;  
        outValue += tex2D(_MainTex, i.uv[4]).b;  
        outValue += tex2D(_MainTex, i.uv[5]).b;  
        outValue += tex2D(_MainTex, i.uv[6]).b;  
        outValue += tex2D(_MainTex, i.uv[7]).b;  
        outValue += tex2D(_MainTex, i.uv[8]).b;  
        outValue = saturate(outValue);
        return outValue * _OutlineColor;  
    }  



    //最终叠加 pass 2
    v2f_add vert_final(appdata_img v)  
    {  
        v2f_add o;  
        o.pos = UnityObjectToClipPos(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  
        }  

    }  
}

        对了,还需要一个纯色的shader作减法抠出来显示本来的纹理:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "SepShader/S_SingleColor"
{
    Properties
    {
        _MainColor ("Main Color", Color) = (1,0,0,1)
        _Width ("width",int) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Cull Back
            CGPROGRAM
            #include "UnityCG.cginc"  
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            fixed4 _MainColor;
            int _Width;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

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

        然后你要做的就是把这两个shader拖到OutLineCameraComponent脚本中对应的shader位置,然后运行时就把模型的meshrenderer通过AddTarget(BaseModel target)的方法传进来就可以在这个模型周围产生描边了,BaseModel类是我自定义的类,存了一堆模型的参数,需要你们自己改一小点就可以用了。

        注意一点我这个描边颜色是青色,若要其他颜色可以改脚本里的颜色,但是我描边的shader里判断的是颜色的b通道的值,你若改其他颜色比如红色就要改成R通道了。另外增加downSample可以增加描边的宽度。

在然后就是描边的效果图了:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值