精华内容
下载资源
问答
  • shader2d描边 unity
    千次阅读
    2022-03-01 12:35:26

    Shader "Custom/ShaderForTest2"
    {
        Properties
        {
            _MainTex("Main Texture", 2D) = "white"{}      
            _EdgeAlphaThreshold("Edge Alpha Threshold", Float) = 1.0       
            _EdgeColor("Edge Color", Color) = (0,0,0,1)              
            _EdgeDampRate("Edge Damp Rate", Float) = 2              
            _OriginAlphaThreshold("OriginAlphaThreshold", range(0.1, 1)) = 0.2   
            [Toggle(_ShowOutline)] _ShowOutline ("Show Outline", Int) = 0    
     
            _InnerGlowWidth("Inner Glow Width", Float) = 0.1         
            _InnerGlowColor("Inner Glow Color", Color) = (0,0,0,1)       
            _InnerGlowAccuracy("Inner Glow Accuracy", Int) = 2           
            _InnerGlowAlphaSumThreshold("Inner Glow Alpha Sum Threshold", Float) = 0.5       
            _InnerGlowLerpRate("Inner Glow Lerp Rate", range(0, 1)) = 0.8           
     
            [Toggle(_ShowInnerGlow)] _ShowInnerGlow ("Show Inner Glow", Int) = 0       
        }
     
        SubShader
        {
            Tags{ "RenderType"="Transparent" "Queue"="Transparent" }
            Blend SrcAlpha OneMinusSrcAlpha
     
            Pass
            {
                Ztest Always Cull Off ZWrite Off
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma shader_feature _ShowOutline
                #pragma shader_feature _ShowInnerGlow
                #include "UnityCG.cginc"
                sampler2D _MainTex;
                half4 _MainTex_TexelSize;
                fixed _EdgeAlphaThreshold;
                fixed4 _EdgeColor;
                float _EdgeDampRate;
                float _OriginAlphaThreshold;
     
                float _InnerGlowWidth;
                fixed4 _InnerGlowColor;
                int _InnerGlowAccuracy;
                float _InnerGlowAlphaSumThreshold;
                float _InnerGlowLerpRate;
     
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float2 uv[9] : TEXCOORD0;
                };
     
                half CalculateAlphaSumAround(v2f i)
                {
                    half texAlpha;
                    half alphaSum = 0;
                    for(int it = 0; it < 9; it ++)
                    {
                        texAlpha = tex2D(_MainTex, i.uv[it]).w;
                        alphaSum += texAlpha;
                    }
                    return alphaSum;
                }
     
                float CalculateCircleSumAlpha(float2 orign, float radiu, int time)
                {
                    float sum = 0;
                    float perAngle = 360 / time;
                    for(int i = 0; i < time; i ++)
                    {
                        float2 newUV = orign + radiu * float2(cos(perAngle * i), sin(perAngle * i));
                        sum += tex2D(_MainTex, newUV).a;
                    }
                    return sum;
                }
     
                v2f vert(appdata_img v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    half2 uv = v.texcoord;
                    o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
                    o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
                    o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
                    o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
                    o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
                    o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
                    o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
                    o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
                    o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
                    return o;
                }
     
                fixed4 frag(v2f i) : SV_Target
                {
                    fixed4 innerGlow = fixed4(0,0,0,0);
                    fixed4 outline = fixed4(0,0,0,0);
                    fixed4 orignColor = tex2D(_MainTex, i.uv[4]);
                    #if defined(_ShowOutline)
                        half alphaSum = CalculateAlphaSumAround(i);
                        float isNeedShow = alphaSum > _EdgeAlphaThreshold;
                        float damp = saturate((alphaSum - _EdgeAlphaThreshold) * _EdgeDampRate);
                        float isOrigon = orignColor.a > _OriginAlphaThreshold;
                        fixed3 finalColor = lerp(_EdgeColor.rgb, fixed3(0,0,0), isOrigon);
     
                        float finalAlpha = isNeedShow * damp * (1 - isOrigon);
                        outline = fixed4(finalColor.rgb, finalAlpha);
                    #endif
                    #if defined(_ShowInnerGlow)
                        float alphaCircleSum = CalculateCircleSumAlpha(i.uv[4], _InnerGlowWidth, _InnerGlowAccuracy) / _InnerGlowAccuracy;
                        float innerColorAlpha = 0;
                        innerColorAlpha = 1 - saturate(alphaCircleSum - _InnerGlowAlphaSumThreshold) / (1 - _InnerGlowAlphaSumThreshold);
                        if(orignColor.a <= _OriginAlphaThreshold)
                        {
                            innerColorAlpha = 0;
                        }
     
                        fixed3 innerColor = _InnerGlowColor.rgb * innerColorAlpha;
                        innerGlow = fixed4(innerColor.rgb, innerColorAlpha);
                        //return innerGlow;
                    #endif

                    #if defined(_ShowOutline)
                        float outlineAlphaDiscard = orignColor.a > _OriginAlphaThreshold;
                        orignColor = outlineAlphaDiscard * orignColor;
                        return lerp(orignColor ,innerGlow * 2, _InnerGlowLerpRate * innerGlow.a) + outline;
                    #endif
     
                    return lerp(orignColor ,innerGlow * 2, _InnerGlowLerpRate * innerGlow.a);
                    //return tex2D(_MainTex, i.uv[4]) + innerGlow + outline;
                }
     
                ENDCG
            }
        }
    }

    更多相关内容
  • 主要介绍了Unity3D中shader 轮廓描边效果的相关资料,需要的朋友可以参考下
  • 本文实例为大家分享了Unity Shader实现描边OutLine效果的具体代码,供大家参考,具体内容如下 Shader实现描边流程大致为:对模型进行2遍(2个pass)绘制,第一遍(描边pass)在vertex shader中对模型沿顶点法线方向...
  • 本文介绍使用UnityShaderGraph实现2D描边效果

    一、前言

    我之前写了一篇文章:ShaderGraph使用教程与各种特效案例:Unity2020(持续更新)

    又有小伙伴私信我给我提出了难题。
    在这里插入图片描述
    这个真的难到我了,如此艺术性的描边效果,以我目前的水平,实在无法使用ShaderGraphUnlit Graph做出来。
    不过,关于2D描边效果,我觉得可以讲一下。
    本文的最终效果:
    在这里插入图片描述
    本文的Demo工程已上传GitHub,地址:https://github.com/linxinfa/UnityShaderGraphOutlineEffect
    感兴趣的同学可以自行下载学习。
    在这里插入图片描述

    二、2D描边效果

    1、导入一张png素材图片

    导入一张png素材图片到Unity工程中。
    在这里插入图片描述
    如下,注意勾选上Alpha Is Transparency,不要勾选Generate Mip Maps
    在这里插入图片描述

    2、创建一个Unlit Graph

    由于我们要处理的是2D图片,所以使用Unlit Graph,在Project文件夹中右键鼠标点击菜单Create - Shader - Unlit Graph,创建一个Unlit Graph
    在这里插入图片描述
    如下:
    在这里插入图片描述

    3、使用Sample Texture 2D采样图片

    我们使用Sample Texture 2D对图片进行采样,显示出来的效果,会看到是下面这样子:
    在这里插入图片描述
    我们看到Alpha通道的显示有问题。
    首先,点击Master节点右上角的小齿轮,将Surface设置为Transparent
    在这里插入图片描述
    然后将贴图的A通道作为Alpha输入,如下:
    在这里插入图片描述

    4、显示描边的思路

    我们可以把图片朝各个方向分别平移n个像素,然后叠加。
    在这里插入图片描述
    然后再减去原图,这样就得到了描边轮廓了。
    在这里插入图片描述

    5、使用Tilling And Offset节点控制UV,实现图片平移

    根据上面的思路,我们现在要做的是从各个方向平移图片,而平移图片,就需要用到Tilling And Offset节点了。
    在这里插入图片描述
    因为朝各个方向平移,需要用到一些共用的变量:原图、偏移值。所以我们先定义两个变量。
    在这里插入图片描述
    如下,就是对应朝左上角平移。
    在这里插入图片描述

    以此类推,得出各个方向的平移。
    在这里插入图片描述

    6、得到轮廓描边

    做种叠加后取alpha通道,再与原图的alpha通道做相减,这样就得出了边界轮廓了。
    在这里插入图片描述

    7、描边与原图相加

    有了描边,就可以与原图相加了。
    在这里插入图片描述

    8、最终显示

    最后输入到Master节点中,效果如下:
    在这里插入图片描述

    三、描边升级版

    1、带颜色的描边

    描边与一个颜色相乘,最后再作用到原图上。
    在这里插入图片描述

    2、描边加噪声

    通过两个Simple Noise分别做Step在相乘,得到一个混合的噪声。
    在这里插入图片描述

    把噪声与描边相乘,则得到一个带噪声的描边效果了。
    在这里插入图片描述
    最终显示
    在这里插入图片描述

    3、再加点变化

    我们可以再加点动态的变化,效果如下。感兴趣的同学可以下载我的Demo学习下。
    在这里插入图片描述

    最终效果:
    在这里插入图片描述

    展开全文
  • 主要为大家详细介绍了UnityShader3实现2D描边效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Unity3D Shader系列之描边

    千次阅读 2022-04-08 23:23:03
    Unity Shader-描边效果 Outline Shader offset的作用 Unity Shader 中 Offset 设置的作用 Shader Depth Offset [Polygon Offset] [OpenGL][SharpGL]用Polygon Offset解决z-fighting和stitching问题


    1 引言

    总结下描边效果的实现方式,主要有以下几种:
    ①法线外拓+ZTest Always
    ②法线外拓+Cull Front
    ③法线外拓+ZWrite Off
    ④法线外拓+模板测试
    ⑤基于屏幕后处理

    2 顶点沿法线外拓方式

    法线外拓的原理如下:
    基本原理还是很简单的:模型渲染两次,第一次渲染时将模型的顶点沿法线方向外拓,然后绘制描边颜色,第二次渲染按正常的渲染即可。也就是用第二次渲染去覆盖掉第一次的渲染,由于第二次没有法线外拓,所以只会覆盖掉中间的部分,从而实现描边。
    这个过程就像画家绘画样,对于同一个位置,用后画的颜色去覆盖掉先画的颜色。
    法线外拓实现描边原理
    所以这里最主要的问题在于,如何保证第二个Pass一定能覆盖第一个Pass。以下几种方法都可实现,一般方法②和方法③用得多一点:
    方法①第二个Pass开启ZTest Always
    方法②第一个Pass使用Cull Front
    方法③第一个Pass使用ZWrite Off
    方法④使用模板测试

    2.1 法线外拓+ZTest Always

    2.1.1 代码

    要保证第二个Pass一定能覆盖掉第一个Pass,最简单的方法就是让深度测试一直通过,即使用ZTest Always。但是使用ZTest Always问题是非常多的,我们下一节再详说。

    伪代码如下:

    // 先用描边颜色渲染
    Pass
    {
    	...
    	// 顶点着色器:顶点沿着法线外拓
    	v2f vert (appdata v)
        {
            v2f o;
    		v.vertex.xy += normalize(v.normal) * _OutlineWidth;
    		o.vertex = UnityObjectToClipPos(v.vertex);
            return o;
        }
    	
    	// 片元着色器:直接绘制描边颜色
    	fixed4 frag (v2f i) : SV_Target
    	{
    		return _OutlineColor;
    	}
    }
    
    // 再正常渲染
    Pass
    {
    	// 保证此Pass一定会渲染
    	ZTest Always
    	// ...
    }
    

    完整代码如下:

    Shader "LaoWang/Outline_Example01"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
    		_OutlineWidth ("Outline width", Range(0.01, 4)) = 0.01
    		_OutlineColor ("Outline Color", color) = (1.0, 1.0, 1.0, 1.0)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" "Queue"="Geometry"}
            LOD 100
    
    		Pass
    		{
    			CGPROGRAM
    
    			#pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
    				float3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                };
    
    			float _OutlineWidth;
    			fixed4 _OutlineColor;
    
                v2f vert (appdata v)
                {
                    v2f o;
    				v.vertex.xy += normalize(v.normal) * _OutlineWidth;
    				o.vertex = UnityObjectToClipPos(v.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return _OutlineColor;
                }
    
    			ENDCG
    		}
    
            Pass
            {
            	ZTest Always
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    			#include "Lighting.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    fixed4 color = tex2D(_MainTex, i.uv);
                    return fixed4(color.rgb, 1.0);
                }
                ENDCG
            }
        }
    }
    
    

    2.1.2 问题点

    由于第二个Pass使用了ZTest Always,会导致两个问题。
    ①模型自身会穿透自身
    但是我们会发现只有部分网格会穿透自身。为什么只有部分网格会穿透呢?我也没弄清楚。
    GPU绘制某一模型时,同一模型中的各个三角面的渲染顺序是如何控制的呢?希望知道的同学解答一下,在此谢过。
    ZTest Always的问题01
    ②物体将会永远再最前面
    ZTest Always的问题02
    所以这种方式基本没人使用。

    2.2 法线外拓+Cull Front

    2.2.1 代码

    原理和上一节类似,只不过不是用ZTest Always来保证第二个Pass覆盖第一个Pass,而是在第一个Pass中使用Cull Front,即第一个Pass只渲染模型的背面,然后让背面向外拓展一下,因为一般背面都在正面的后面(即背面的深度值比正面的深度值大),所以第二个Pass就会覆盖掉中间部分。

    Shader "LaoWang/Outline_CullFront"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
    		_OutlineWidth ("Outline width", Range(0.01, 4)) = 0.01
    		_OutlineColor ("Outline Color", color) = (1.0, 1.0, 1.0, 1.0)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" "Queue"="Geometry"}
            LOD 100
    
    		Pass
    		{
    			Cull Front
    			CGPROGRAM
    
    			#pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
    				float3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                };
    
    			float _OutlineWidth;
    			fixed4 _OutlineColor;
    
                v2f vert (appdata v)
                {
                    v2f o;
    				v.vertex.xy += normalize(v.normal) * _OutlineWidth;
    				o.vertex = UnityObjectToClipPos(v.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return _OutlineColor;
                }
    
    			ENDCG
    		}
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    			#include "Lighting.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    fixed4 color = tex2D(_MainTex, i.uv);
                    return fixed4(color.rgb, 1.0);
                }
                ENDCG
            }
        }
    }
    
    

    效果是这样的。
    描边效果
    虽然Robot Kyle这个模型使用这种方式效果不好,但是对于其他绝大部分模型还是够用了。
    一般的描边就是使用这种方式。

    2.2.2 改进点

    ①无论相机距离物体多远或者观察视角的变化,都让描边的宽度保持等比例。
    如图。
    各处描边厚度不一样的问题
    出现这样问题的原因在于我们是在模型空间对顶点进行外拓的,外拓的距离是一样的。但是由于是透视相机,模型上离相机近的地方描边效果较粗,而远的地方描边效果较细。
    解决这个问题的方法是,我们不在模型空间外拓,而在齐次裁剪空间将顶点沿法线方向进行外拓。
    而这个方法最大的问题在于,如何才能求到齐次裁剪空间中的法线方向?
    顶点从模型空间变换到齐次裁剪空间的变换矩阵是MVP,那法线的变换能否直接使用MVP矩阵呢?答案是不行。法线的变换应该是变换矩阵的逆转置矩阵,即我们这里将使用(MVP)-1T来进行法线变换。
    为什么法线变换不能直接使用变换矩阵,而要使用逆转置矩阵呢?主要是为了保证存在非等比缩放时,变换后的法线方向依然是垂直与表面的。如果不存在非等比缩放,即只存在旋转,那么法线的变换是可以直接使用变换矩阵的。(具体描述详见《Unity Shader入门精要》4.7节 法线变换)
    法线变换问题
    那MVP矩阵是只有旋转吗?不是的。P矩阵即从观察空间到齐次裁剪空间的变换矩阵一定是存在非等比缩放的。所以,我们这里需要用到MVP的逆转置矩阵。
    两种相机的P矩阵
    回到最初的问题,MVP的逆转置矩阵该怎么求?很遗憾Unity的Shader中并没有直接提供相应的变量,要真正得到这个逆转置矩阵需要从C#端计算然后传递到shader。但其实我们并不需要那么高的精度,近似即可。有两种近似方式。
    一是直接使用MVP矩阵来近似。

    v2f vert (appdata v)
    {
    	o.vertex = UnityObjectToClipPos(v.vertex);
    	float3 clipNormal = mul((float3x3) UNITY_MATRIX_VP, mul((float3x3) UNITY_MATRIX_M, v.normal));
    	o.vertex.xy += normalize(clipNormal).xy * _OutlineWidth;
    }
    

    二是使用(MV)的逆转置矩阵* P来近似。为什么使用MV的逆转置矩阵呢,是因为Unity刚好提供了这个变量,UNITY_MATRIX_IT_MV。

    v2f vert (appdata v)
    {
    	o.vertex = UnityObjectToClipPos(v.vertex);
    	float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
    	float2 clipNormal = mul((float2x2)UNITY_MATRIX_P, viewNormal.xy);
    	o.vertex.xy += normalize(clipNormal) * _OutlineWidth;
    }
    

    效果对比如下,可以看到上面两种方式的效果其实差不多。
    三种外拓方式效果对比

    2.3 法线外拓+ZWrite Off

    2.3.1 代码

    逻辑也很简单,第一个Pass由于关闭了深度写入,那么第二个Pass肯定能够通过深度测试,所以第二个Pass会覆盖掉第一个Pass。

    Shader "LaoWang/Outline_ZWriteOff"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
    		_OutlineWidth ("Outline width", Range(0.01, 4)) = 0.01
    		_OutlineColor ("Outline Color", color) = (1.0, 1.0, 1.0, 1.0)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" "Queue"="Geometry"}
            LOD 100
    
    		Pass
    		{
    			ZWrite Off
    
    			CGPROGRAM
    
    			#pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
    				float3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                };
    
    			float _OutlineWidth;
    			fixed4 _OutlineColor;
    
                v2f vert (appdata v)
                {
                    v2f o;
    				//v.vertex.xy += normalize(v.normal) * _OutlineWidth;
    				//o.vertex = UnityObjectToClipPos(v.vertex);
    
    				o.vertex = UnityObjectToClipPos(v.vertex);
    				float3 clipNormal = mul((float3x3) UNITY_MATRIX_VP, mul((float3x3) UNITY_MATRIX_M, v.normal));
    				o.vertex.xy += normalize(clipNormal).xy * _OutlineWidth;
    
    				//o.vertex = UnityObjectToClipPos(v.vertex);
    				//float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
    				//float2 clipNormal = mul((float2x2)UNITY_MATRIX_P, viewNormal.xy);
    				//o.vertex.xy += normalize(clipNormal) * _OutlineWidth;
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return _OutlineColor;
                }
    
    			ENDCG
    		}
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    			#include "Lighting.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    fixed4 color = tex2D(_MainTex, i.uv);
                    return fixed4(color.rgb, 1.0);
                }
                ENDCG
            }
        }
    }
    
    

    效果如下,可以看到Robot Kyle这个模型使用这种方式效果是最好的。
    ZWrite Off效果对比

    2.3.2 问题点

    ZWrite Off关闭后会有两个问题。具体如下。
    我们在场景中加上一个Ground,新建一个标准的材质球。
    标准材质球
    然后我们就会发现有地板的部分描边就消失了。
    ZWrite Off的问题
    我们从Frame Debugger中可以看出地板是最后绘制的。而绘制描边时没有开启深度写入,这就导致地板的深度测试会通过。所以地板的颜色会覆盖掉描边部分的颜色。
    ZWrite Off的问题测试
    要解决这个问题,其实也很简单,最后渲染我们的模型就行了。用什么方法控制我们的模型最后渲染呢?当然是控制渲染队列啦,这点我们在《Unity3D Shader系列之透视效果XRay》中讲过,就不再多说了。
    我们将渲染队列设置为“Geometry+1”,即在所有不透明物体渲染后再渲染我们的模型。
    调整渲染队列之后的效果
    调整之后,描边效果就正常了。
    但是这样调整之后依然还有问题,比如我们再复制一个描边模型,然后一个在前一个在后。此时,我们将会发现两个模型重叠的部分没有描边了。
    重叠部分的描边没有了
    同样,我们去Frame Debugger中看看原因。
    ZWrite Off导致的问题
    从上图我们可以看到,是先绘制的前面的物体,再绘制后面的物体,就导致绘制后面物体时将前面物体的描边给覆盖掉了。
    要解决这个问题,我们得有一个储备知识:Unity在渲染不透明物体时,如果这两个物体的渲染队列一样(Render Queue的值一样),则按距离摄像机由近到远的顺序依次渲染。在渲染半透明物体时,如果这两个物体的渲染队列一样(Render Queue的值一样),则按距离摄像机由远到近的顺序依次渲染。
    为什么要这样做?对于不透明物体,先渲染近的再渲染远的,由于硬件的Early-Z等技术,可以减少Over Draw。对于半透明物体,由于需要关闭深度写入,所以必须先渲染远的再渲染近的,这样才能保证混合后的颜色是正确的。
    所以我们这里要想让两个模型重叠的部分也能绘制出描边效果,就得先渲染后面的再渲染前面的,那把渲染队列改为Transparent就可以了。但是这个办法也不是完全能解决问题的,因为我们上面给出的距离摄像机的远近其实很模糊,这个远近到底是取哪个值?是取物体的世界坐标与相机的距离呢还是物体的某个顶点距离相机的距离呢?Unity官方也没给出说明。
    改为Transparent后的效果

    2.4 法线外拓+模板测试

    先正常渲染物体,将模板缓冲区写为1。然后再法线外拓进行描边,当模板缓冲区值为0时绘制描边。
    伪代码。

    Pass
    {
    	// 将模板缓冲区写为1
    	Stencil
    	{
    		Ref 1
    		Comp Always
    		Pass Replace
    	}
    
    	// 正常渲染
    	...
    }
    
    Pass
    {
    	Stencil
    	{
    		Ref 0
    		Comp Equal
    	}
    	ZWrite Off
    
    	// 渲染描边
    	// 顶点着色器法线外拓
    	...
    }
    
    

    完整代码。

    Shader "LaoWang/Outline_StencilTest"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
    		_OutlineWidth ("Outline width", Range(0.01, 4)) = 0.01
    		_OutlineColor ("Outline Color", color) = (1.0, 1.0, 1.0, 1.0)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
            LOD 100
    
    		Pass
            {
    			Stencil
    			{
    				Ref 1
    				Comp Always
    				Pass Replace
    			}
    
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    			#include "Lighting.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    fixed4 color = tex2D(_MainTex, i.uv);
                    return fixed4(color.rgb, 1.0);
                }
                ENDCG
            }
    
    		Pass
    		{
    			Stencil
    			{
    				Ref 0
    				Comp Equal
    			}
    
    			ZWrite Off
    
    			CGPROGRAM
    
    			#pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
    				float3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                };
    
    			float _OutlineWidth;
    			fixed4 _OutlineColor;
    
                v2f vert (appdata v)
                {
                    v2f o;
    				//v.vertex.xy += normalize(v.normal) * _OutlineWidth;
    				//o.vertex = UnityObjectToClipPos(v.vertex);
    
    				o.vertex = UnityObjectToClipPos(v.vertex);
    				float3 clipNormal = mul((float3x3) UNITY_MATRIX_VP, mul((float3x3) UNITY_MATRIX_M, v.normal));
    				o.vertex.xy += normalize(clipNormal).xy * _OutlineWidth;
    
    				//o.vertex = UnityObjectToClipPos(v.vertex);
    				//float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
    				//float2 clipNormal = mul((float2x2)UNITY_MATRIX_P, viewNormal.xy);
    				//o.vertex.xy += normalize(clipNormal) * _OutlineWidth;
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return _OutlineColor;
                }
    
    			ENDCG
    		}
        }
    }
    
    

    效果如下。
    模板测试描边效果
    这种方式也会有两个模型重叠部分没有描边的问题,但是由于使用的是模板测试,这个问题是无解的了。

    2.5 法线外拓实现描边的问题

    使用法线外拓实现描边都存在下图这样的问题,即法线不是连续的时候,描边就会中断。
    法线外拓实现描边的问题
    要解决这个问题需要写一个工具将顶点的法线平滑一下,并将其保存在顶点的颜色数据中,然后在外拓时使用平滑后的法线来外拓。
    可以参考这篇文章,里面实现了法线平滑工具。

    3 屏幕后处理的方式

    使用屏幕后处理实现描边一般有两种方式,一是使用Unity中Camera的着色器替代技术,二是使用Render Command。

    3.1 使用Camera的着色器替代技术

    这种方式我没有去具体实现,但是去了解了下,这里总结下大概的思路。

    3.1.1 着色器替代技术

    什么是Camera的着色器替代技术?
    说起来高大上,其实本质的东西并不复杂,就是用相机重新渲染一遍场景,但是本次渲染的过程中,场景中的物体(不一定是全部物体,我们可以用代码控制只渲染某一部分物体)不再使用它自身的Shader进行着色,而是使用特定的Shader(所有要渲染的物体都是用同一个Shader)来着色。
    从代码上来说,就是下面Camera类中的两个方法。

    public void RenderWithShader(Shader shader, string replacementTag);
    public void SetReplacementShader(Shader shader, string replacementTag);
    

    RenderWithShader只有调用时的那一帧有效。
    SetReplacementShader是调用之后Camera渲染都一直使用指定的Shader,直到代码主动调用ResetReplacementShader方法。

    public void ResetReplacementShader();
    

    说一下两个方法的参数。
    第一个参数为相机渲染时将使用的Shader。
    第二参数为相机查找的标签,一般指定为RenderType。
    举个例子,相信大家一看就明白了。
    我们这样调用。

    Camera.main.SetReplacementShader(Shader.Find("LaoWang/CameraReplace"), "RenderType");
    

    对应的Shader代码如下:

    Shader "LaoWang/CameraReplace"
    {
    	SubShader
        {
    		// 渲染不透明物体时用此Pass替代
            Tags { "RenderType" = "Opaque" }
    
    		Pass
    		{
    			// ...
    		}
        }
    
    	SubShader
        {
    		// 渲染半透明物体时用此Pass替代
            Tags { "RenderType" = "Transparent" }
    
    		Pass
    		{
    			// ...
    		}
        }
    }
    

    那么我们的主摄像机在渲染时,将会去遍历场景中的所有物体,如果物体使用的Shader的RenderType标签为Opaque,那么相机将使用“LaoWang/CameraReplace”中的第一个Pass渲染该物体;如果物体使用的Shader的RenderType标签为Transparent,那么相机将使用“LaoWang/CameraReplace”中的第二个Pass渲染该物体;如果物体使用的Shader的RenderType标签既不为Opaque也不为Transparent,那该物体将不会渲染。
    查找的标签是否一定要指定为RenderType呢?不是的。只是因为Unity中内置的着色器都有这个标签,所以我们一般就指定为它。
    Unity中相机输出法线纹理、深度纹理其实就是使用了这种技术,如果某天你发现用相机输出深度纹理时发现不包含某个物体,很有可能就是那个物体的Shader的RenderType标签没有设置或者没有设置正确。

    3.1.2 描边思路

    ①创建一个额外的相机,使用Camera.CopyFrom拷贝主摄像机的参数,并将位置、旋转设置成一样
    ②设置额外相机的Culling Mask
    ③创建一个Renderer Texture,假设名为rt,其长宽可以设置为屏幕大小,但是屏幕大小这么大的纹理内存占用比较大,没必要的话建议使用较小的分辨率
    ④额外相机的Target Texture设置为步骤③创建的Renderer Texture
    ⑤创建一个脚本,继承自MonoBehaviour,实现OnRenderImage(RenderTexture source, RenderTexture destination)方法,并挂载到主摄像机上
    ⑥新建一个额外相机渲染的Shader,RenderType标签设置为Opaque,片元着色中只输出纯色;该Shader用于额外相机的着色器替换
    ⑦额外相机调用SetReplacementShader,第一个参数为步骤⑥创建的Shader,第二个参数为RenderType

    SetReplacementShader(Shader.Find(""), "RenderType");
    

    ⑧在OnRenerImage方法中,先对rt进行高斯模糊,高斯模糊会让额外相机看到的物体的轮廓往外扩,高斯模糊后的图像再与rt做差得到描边,然后再与主摄像机看到的画面叠加即可

    3.1.3 问题点

    这种做法有几个问题,首先是需要额外创建出一个摄像机并对其进行管理,Camera本身属于Unity3D场景管理中比较重的对象,他的背后应该还涉及视锥切割,排序等一系列复杂的操作,对于仅需要绘制几个简单物体的操作来说太浪费计算资源了。另外需要绘制的对象需要有单独的层,如果本身已经由其他需要跟其他同类物体指定一个layer的话就不太方便操作了。最后渲染的第一步与后几步分开了,由于最终需要将结果输出到主摄像机上,这意味着两个摄像机上都有一些需要维护的脚本。

    3.2 Render Command

    3.2.1 思路

    使用Render Command实现描边的思路与使用相机的着色器替代技术的原理是一样的,只是不再是用一个额外的相机去渲染,而是直接使用Render Command来处理额外渲染的这一步骤了。
    Command Buffer实现描边的过程

    3.2.2 Command Buffer

    Command Render主要使用Command Buffer来实现,其就是对OpenGL\DirectX这些底层渲染接口的API进行了封装,其内部预定义一系列的渲染指令,我们使用起来非常方便。
    官方文档详细API
    解释下我们描边用到的代码。
    先实例化一个CommandBuffer,名字设置为“Render Outline”,这里设置名字主要是方便我们在Frame Debugger中定位到对应的渲染流程。

    m_RenderCommand = new CommandBuffer
    {
        name = "Render Outline"
    };
    

    CommandBuffer命名的作用
    创建一个材质球,用于CommandBuffer的渲染。

    m_OutlineMaterial = new Material(Shader.Find(OutlineShader));
    

    然后先将CommandBuffer的背景画面给清空。
    使用DrawRenderer将需要额外渲染的物体添加到CommandBuffer的队列中。
    下面的示例代码表示在绘制CommandBuffer中的几个物体时,使用的材质为m_OutlineMaterial中的第一个Pass(参数中的第二个0)。

    // 顺序将渲染任务加入RenderCommand中
    m_RenderCommand.ClearRenderTarget(true, true, Color.clear);
    for (int i = 0; i < OutlineObjects.Length; ++i)
    {
        m_RenderCommand.DrawRenderer(OutlineObjects[i], m_OutlineMaterial, 0, 0);
    }
    

    然后创建一个渲染纹理,并使用Graphics.SetRenderTarget将渲染目标设置为刚创建的渲染纹理。
    再调用Graphics.ExecuteCommandBuffer。
    即按照CommandBuffer的设置进行渲染,渲染之后的结果即为刚创建的渲染纹理。

    m_OutlineMaterial.SetColor("_OutlineColor", outlineColor);
    RenderTexture outlineColorRt = RenderTexture.GetTemporary(Screen.width, Screen.height);
    Graphics.SetRenderTarget(outlineColorRt);
    Graphics.ExecuteCommandBuffer(m_RenderCommand);
    

    CommandBuffer不再使用时,一定要释放。

    m_RenderCommand.Clear();
    

    3.2.3 完整代码

    PostEffectOutline.cs

    using UnityEngine;
    using UnityEngine.Rendering;
    
    [DisallowMultipleComponent]
    [RequireComponent(typeof(Camera))]
    public class PostEffectOutline : MonoBehaviour
    {
        private const string OutlineShader = "LaoWang/PostEffect/Outline";
    
        private Material m_OutlineMaterial;
        private CommandBuffer m_RenderCommand;
        public Renderer[] OutlineObjects;
    
        public Color outlineColor = Color.red;
    
        [Range(1, 8)]
        public int downSampleScale = 2;                 // 降采样比例
        [Range(0, 4)]
        public int blurIterations = 1;                  // 高斯模糊迭代次数
        [Range(0.2f, 3.0f)]
        public float blurSpread = 0.6f;                 // 高斯模糊
    
        private void Awake()
        {
            m_RenderCommand = new CommandBuffer
            {
                name = "Render Outline"
            };
    
            m_OutlineMaterial = new Material(Shader.Find(OutlineShader));
        }
    
        void OnEnable()
        {
            // 顺序将渲染任务加入RenderCommand中
            m_RenderCommand.ClearRenderTarget(true, true, Color.clear);
            for (int i = 0; i < OutlineObjects.Length; ++i)
            {
                m_RenderCommand.DrawRenderer(OutlineObjects[i], m_OutlineMaterial, 0, 0);
            }
        }
    
        void OnDisable()
        {
            m_RenderCommand.Clear();
        }
    
        void OnDestroy()
        {
            m_RenderCommand.Clear();
        }
    
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            //1. 绘制颜色
            m_OutlineMaterial.SetColor("_OutlineColor", outlineColor);
            RenderTexture outlineColorRt = RenderTexture.GetTemporary(Screen.width, Screen.height);
            Graphics.SetRenderTarget(outlineColorRt);
            Graphics.ExecuteCommandBuffer(m_RenderCommand);
            // 用于测试
            //Graphics.Blit(outlineColorRt, destination);
            //RenderTexture.ReleaseTemporary(outlineColorRt);
    
            //2. 降采样
            int rtW = Screen.width >> downSampleScale;
            int rtH = Screen.height >> downSampleScale;
            RenderTexture blurRt = RenderTexture.GetTemporary(rtW, rtH);
            blurRt.filterMode = FilterMode.Bilinear;
            Graphics.Blit(outlineColorRt, blurRt);
    
            //3. 高斯模糊
            RenderTexture blurTemp = RenderTexture.GetTemporary(rtW, rtH);
            for (int i = 0; i < blurIterations; ++i)
            {
                m_OutlineMaterial.SetFloat("_BlurSize", 1.0f + i * blurSpread);
                // 水平模糊
                Graphics.Blit(blurRt, blurTemp, m_OutlineMaterial, 1);
                // 垂直模糊
                Graphics.Blit(blurTemp, blurRt, m_OutlineMaterial, 2);
            }
    
            // 用于测试
            //Graphics.Blit(blurRt, destination);
    
            //4. 叠加
            m_OutlineMaterial.SetTexture("_OutlineColorTex", outlineColorRt);
            m_OutlineMaterial.SetTexture("_BlurTex", blurRt);
            Graphics.Blit(source, destination, m_OutlineMaterial, 3);
    
            RenderTexture.ReleaseTemporary(outlineColorRt);
            RenderTexture.ReleaseTemporary(blurRt);
            RenderTexture.ReleaseTemporary(blurTemp);
        }
    }
    
    

    PostEffect_Outline.shader

    Shader "LaoWang/PostEffect/Outline"
    {
        Properties
        {
    		_OutlineColor ("Outline Color", color) = (1.0, 0, 0, 1.0)
            _MainTex ("Texture", 2D) = "white" {}
    		_BlurSize ("Blur Size", float) = 1.0
        }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
    
    		CGINCLUDE
    
    		#include "UnityCG.cginc"
    
    		fixed4 _OutlineColor;
    		sampler2D _MainTex;
            half4 _MainTex_TexelSize;
    		float _BlurSize;
    
    		struct v2f
    		{
    			float4 pos : SV_POSITION;
    			half2 uv[5] : TEXCOORD0;
    		};
    
    		v2f vertBlurVertical(appdata_img v)
    		{
    			v2f o;
    			o.pos = UnityObjectToClipPos(v.vertex);
    
    			half2 uv = v.texcoord;
    
    			o.uv[0] = uv;
    			o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
    			o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
    			o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
    			o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
    
    			return o;
    		}
    
    		v2f vertBlurHorizontal(appdata_img v)
    		{
    			v2f o;
    			o.pos = UnityObjectToClipPos(v.vertex);
    
    			half2 uv = v.texcoord;
    
    			o.uv[0] = uv;
    			o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
    			o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
    			o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
    			o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
    
    			return o;
    		}
    
    		fixed4 fragBlur(v2f i) : SV_Target
    		{
    			float weight[3] = {0.4026, 0.2442, 0.0545};
    			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
    
    			for(int it = 1; it < 3; it++)
    			{
    				sum += tex2D(_MainTex, i.uv[it]).rgb * weight[it];
    				sum += tex2D(_MainTex, i.uv[2*it]).rgb * weight[it];
    			}
    
    			return fixed4(sum, 1.0);
    		}
    
    		ENDCG
    
    		ZTest Always 
    		Cull Off 
    		ZWrite Off
    
    		pass
    		{
    			CGPROGRAM
    
    			#pragma vertex vert
    			#pragma fragment frag
    
                v2f_img vert (appdata_img v)
                {
                    v2f_img o;
                    o.pos = UnityObjectToClipPos(v.vertex);
    				o.uv = v.texcoord;
                    return o;
                }
    
                fixed4 frag (v2f_img i) : SV_Target
                {
                    return _OutlineColor;
                }
    
    			ENDCG
    		}
    
    		pass
    		{
    			NAME "GAUSSIAN_BLUR_VERTICAL"
    
    			CGPROGRAM
    
    			#pragma vertex vertBlurVertical
    			#pragma fragment fragBlur
    
    			ENDCG
    		}
    
    		pass
    		{
    			NAME "GAUSSIAN_BLUR_HORIZONTAL"
    
    			CGPROGRAM
    
    			#pragma vertex vertBlurHorizontal
    			#pragma fragment fragBlur
    
    			ENDCG
    		}
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment fragOutline
    
    			sampler2D _BlurTex, _OutlineColorTex;
    			half4 _BlurTex_TexelSize, _OutlineColorTex_TexelSize;
    
                v2f_img vert (appdata_img v)
                {
                    v2f_img o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.uv = v.texcoord;
                    return o;
                }
    
    			fixed4 fragOutline(v2f_img i) : SV_Target
    			{
    				fixed4 scene = tex2D(_MainTex, i.uv);
    				fixed4 blur = tex2D(_BlurTex, i.uv);
    				fixed4 outlieColor = tex2D(_OutlineColorTex, i.uv);
    				fixed4 outline = blur - outlieColor;
    		
    				fixed4 final = scene * (1 - all(outline.rgb)) + _OutlineColor * any(outline.rgb);
    				return final;
    			}
    
    			ENDCG
    		}
        }
    	FallBack off
    }
    

    4 完整工程

    博主个人博客本文链接。
    链接:https://pan.baidu.com/s/1AhKWJxMaQI89vAwv8RE-vQ
    提取码:xaaz

    5 参考文章

    展开全文
  • 附上源码: Shader脚本 漫反射+描边 Shader "Unlit/ModelOutLine" { Properties { _MainTex ("Texture", 2D) = "white" {} _Diffuse("Color",Color) =(1,1,1,1) _OutLineColor("OutlineColor",Color)=(1,1,1,1) _...

    描边效果

    关于描边效果有很多种,单顶点Shader比较常见了就有三种,分别是视角空间法线外拓,裁剪空间法线外拓,以及模型空间法线外拓,这里只详细介绍一种性能相较最好的一种描边效果,模型法线外拓描边效果

    描边原理:

    通过两个Pass通道,先渲染模型的背面,可以绘制偏大一些,然后在进行正常的模型绘制,这样正常的模型就会覆盖在事先画好的背面颜色上,通过调整渲染的背面图形大小,来实现描边的粗细。

    下图为效果图:
    在这里插入图片描述
    1.Cull Front 剔除模型正面

    至于为什么只渲染背面,不对正面进行渲染,下面两张图可以给出最好的解释。

    下图是不渲染正面,只渲染背面的效果。

    可以看到当有另一个模型穿插进去的时候,该模型属于正常状态。

    在这里插入图片描述
    2.下图是正反两面都渲染的的效果。

    可以看到,当有另一个模型穿插进去的时候,该模型的描边,就暴露的出来,这显然是不正确的,所以为了避免不必要的问题, 我们需要在描边的一开始就正确的使用渲染方式 Cull Front 剔除正面 。
    在这里插入图片描述
    核心算法:

          //物体顶点法线外拓
          v.vertex.xyz+=normalize(v.normal)*_Outline/10;
    ;
    

    附上源码:
    Shader脚本 漫反射+描边

    Shader "Unlit/ModelOutLine"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
            _Diffuse("Color",Color) =(1,1,1,1)
            _OutLineColor("OutlineColor",Color)=(1,1,1,1)
            _Outline("Outline",Range(0,1))=1
        }
        SubShader
        {
            Tags {"Queue"= "Geometry+1000" "RenderType"="Opaque" }
            Pass
            {
               //不渲染正面 防止模型穿插导致 描边暴露
                Cull Front 
    
                CGPROGRAM
                #pragma vertex vert 
                #pragma fragment frag 
                #include "UnityCG.cginc"
    
                float4 _OutLineColor;
                float _Outline;
                
                struct v2f
                {
                    float4 vertex:SV_POSITION;
                };
    
                v2f vert(appdata_base v)
                {
                    //物体顶点法线外拓
                    v.vertex.xyz+=normalize(v.normal)*_Outline/10;
                    
                    v2f o;
                    o.vertex=UnityObjectToClipPos(v.vertex);
                    return o;
                };
    
                fixed4  frag(v2f i):SV_TARGET
                {
                    return fixed4(_OutLineColor);
                };
    
                ENDCG
            }
    
            Pass
            {
                Tags { "LightModel" = "ForwardBase" }
                CGPROGRAM 
                #pragma vertex vert
                #pragma fragment frag 
                #include "UnityCG.cginc"
                #include "Lighting.cginc"
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                float4 _Diffuse;
    
                struct v2f
                {
                    float4 vertex:SV_POSITION;
                    float2 uv:TEXCOORD0;
                    float3 worldLightNormal:TEXCOORD1;
                    float3 worldNormal:TEXCOORD2;
    
    
                };
    
                v2f vert(appdata_base v)
                {
                    v2f o;
                    o.vertex=UnityObjectToClipPos(v.vertex);
                    o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
                    float3 worldPos=mul(unity_ObjectToWorld,v.vertex);
                    o.worldLightNormal=UnityWorldSpaceLightDir(worldPos);
                    o.worldNormal=UnityObjectToWorldNormal(v.normal);
                    return o;
                };
    
                fixed4 frag(v2f i):SV_TARGET
                {
                    float3 worldNormalDir=normalize(i.worldNormal);
                    float3 WorldLightDir=normalize(i.worldLightNormal);
    
                    float3 texColor=tex2D(_MainTex,i.uv);
    
                    float ambient=UNITY_LIGHTMODEL_AMBIENT.rgb*texColor.rgb;
    
                    float3 diffuse=_LightColor0.rgb*_Diffuse.rgb*texColor.rgb*(dot(worldNormalDir,WorldLightDir)*0.5+0.5);
                    float3 color=diffuse+ambient;
                    
                    return fixed4(color,1);
                };
    
                ENDCG
    
            }
            
        }
        Fallback "Diffuse"
    }
    
    

    下面列出三种顶点着色器描边方式:

    物体空间法线外拓 性能最好

     			v2f vert(appdata_base v)
                {
                    //物体顶点法线外拓
                    v.vertex.xyz+=normalize(v.normal)*_Outline/10;
                    v2f o;
                    o.vertex=UnityObjectToClipPos(v.vertex);
                    return o;
                };
    

    视角空间法线外拓

    			v2f vert(appdata_base v)
                {
                    //视角空间法线外拓
    				float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, v.vertex));
    				float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
    				pos = pos + float4(normal,0) * _Outline;
    				o.vertex =  mul(UNITY_MATRIX_P, pos);
    				o.vertex=UnityObjectToClipPos(v.vertex);
                    return o;
                };
    

    裁剪空间法线外拓

    			v2f vert(appdata_base v)
                {
                   //裁剪空间法线外拓
    				o.vertex = UnityObjectToClipPos(v.vertex);
    				float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
    				float2 viewNoraml = TransformViewToProjection(normal.xy);
    				o.vertex.xy += viewNoraml * _Outline;
    				return o;
                };
    
    展开全文
  • 内置管线下2D图片的描边已经有很多文章了,在此实现下URP下的2D图片描边效果。 由于代码相对简单,比较好理解,直接贴代码了: Shader "outline" { Properties { _MainTex("Texture", 2D) = "white" {} _...
  • UnityShader描边效果

    2022-04-29 11:28:56
    知识补充 Subshader 可以在subshader下定义多个Tags...Geometry,渲染非透明的几何体所采用的队列,没有声明渲染队列的时候,unity默认使用此队列,队列号2000 AlphaTest,Alpha测试的几何体使用的队列,在所有几何体
  • Unity Shader-描边效果

    万次阅读 多人点赞 2017-01-15 16:00:58
    描边效果是游戏里面非常常用的一种效果,一般在选中物体或者NPC的时候,被选中的对象就会显示描边效果。比如最近又跑回去玩了玩《剑灵》,虽然出了三年了,在现在的网游里面画面仍然算很好的。 还有就是最常见的...
  • //-----------------------------------------------【Shader说明】--------------------------------------------------------//Shader功能: 2D图片描边 核心思路:检测某个图元的alpha值是否在某个阀值之间//----...
  • shader 物体描边

    2020-01-23 01:14:54
    描边的方式有三种: 1.模型空间法线外拓 2.视角空间法线外拓 3.裁剪空间法线外拓 但我感觉效果都差不多,所以用哪种都可以。 Shader "MyShader/NewShader" { Properties { _MainTex ("Texture", 2D) = "white" {}...
  • unity简单shader实现物体描边效果

    千次阅读 2021-11-09 10:30:29
    最近工作中突然有描边的小功能,就写了一个shader收藏,顺便分享下,不喜勿喷,欢迎建议,谢谢! Shader "Unlit/TraceOutLine" { Properties { //属性 _MainTex("Texture", 2D) = "white" {} _Diffuse("Color...
  • Unity光晕剑效果的Shader简单实现 【Unity Shader编程】之十四 边缘发光Shader(Rim Shader)的两种实现形态 [Unity3D][NGUI]选中某个图标后高亮 高亮效果,使用Shader可以轻松实现 【Unity ImageEffect】一个用于...
  • Unity使用Shader实现3D模型外描边效果

    千次阅读 2020-09-02 11:12:41
    文章目录一、3D模型外描边效果二、如何使用三、shader代码 一、3D模型外描边效果 二、如何使用 将最下面的shader代码保存为SilhouettedDiffuse.shader文件,创建一个material材质球:Outline.mat,将...
  • unity找到的最好的描边着色器shader,平常的描边shader加上去后物体没了高光效果,里面的Standard那一款完美解决此问题.
  • unity shader遮挡边缘光描边

    千次阅读 2022-02-18 00:54:48
    Shader "Unlit/遮挡边缘光描边" { Properties { _MainTex("Texture", 2D) = "white" {} _Diffuse("Color",Color) = (1,1,1,1) _Outline("Outline",Range(0,1)) = 0.1 // 描边参数控制 _OutlineColor("Outline Color...
  • 2D图片描边2D图片纯色填充,二合一Shader。支持UGUI UI Mask。 Shader "Sprites/SpriteOutline" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint Color", Col....
  • 说道卡通渲染,应该都会想到描边: 我所学的描边有三种: 一种是计算边缘深度检测描边 一种是色差检测描边 一种是利用顶点法线向外扩展返回单色pass,使用正面裁剪 我用的第三种: pass { //剔除前面 (朝向摄像机...
  • Unity Shader -描边(后期处理)

    千次阅读 2020-01-17 00:33:23
    上一篇文章中我们通过两种方式分别实现了描边效果,他们各有优缺点,也比较简单,今天我们来通过后期处理这种方式来实现描边效果,相对于之前两种实现方式要稍微复杂一点。 最终效果图如下: 该思路主要还是参考 ...
  • unity-shader 2D精灵图描边效果

    千次阅读 2019-11-05 19:27:54
    今天我们来实现一个简单的2D精灵图描边效果,效果图如下: 准备工作: 首先我们先打开unity新建一个场景,导入一个图片,并把该图片设置为Sprite类型,如图: ...
  • 描边区别只显示最外层描边线,中间的不显示. Shader "Custom/Outline" { Properties { _MainTex ("Texture", 2D) = "white" {} _Diffuse ("Diffuse", COLOR) = (1,1,1,1) _OutlineColor ("Outline ...
  • Shader Graph制作2D Sprite描边

    千次阅读 2019-03-28 01:17:29
    主要是参考GUI的Text的描边原理。实际上就是复制四个纹理在上下左右方向各平移N个像素 2、制作流程 因为不需要光照效果,所以使用Unlit Master节点输出。整体结构如下: 设置了3个参数,分别控制图片纹理,...
  • Unity Shader - 2D描边效果

    千次阅读 2018-11-30 12:12:18
    2D描边效果: 上面是实现2D描边效果的3种不同效果,左边为原图,右边为效果图 方式一:平均透明度法 原理:取每个像素及其周围的Alpha值进行平分,边界处因为对边界外也进行了采样,所以叠加后透明度...
  • 描边 1、基于观察角度和表面法线的轮廓线渲染 基本思路:以归一化的顶点到摄像机的向量和归一化的顶点法线向量的点乘为参考值,该值越小(越接近于0),该像素就越解决于边缘。 优点:简单快速,只需要一个Pass。 ...
  • 描边和阴影,Unity本来是由自带的组件的(Outline和Shadow)。Unity自己的实现方式如下: Outline:把原文字/图片以往的网格复制4份,然后上下左右各偏移一点距离(相当于多绘制了4遍)。 Shadow:把原文字/图片的...
  • URP/LWRP Shader实现描边效果

    千次阅读 多人点赞 2020-06-16 09:27:58
    ——我在URP基础的Lit.shader上添加了描边属性(描边颜色和宽度),并且重新定义了Priority区间(从±50扩展为±1000),显示材质球RenderQueue的大小(省去了你切换到Debug模式查看CustomRenderQueue的功夫) ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 802
精华内容 320
关键字:

shader2d描边 unity