Unity 2D内描边Shader

一、前言

今天,我们来实现一个2D内描边的效果。内描边:即把边缘像素变成描边的颜色,占用原来的像素。
思路:我们可以在片元着色器实现此效果:当一个像素本身不是透明的(alpha>0),而且它上下左右4个像素的alpha值的乘积等于0,那么我们可以判定该像素处于边缘,让它变成描边颜色就可以。

二、Shader内容

2.1 初版Shader

Shader "Custom/2DInline"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white"{}
        _InlineColor("InlineColor",Color) = (0,0,0,1)
        _InlineWidth("InlineWidth",Range(0,10)) = 1
    }

    SubShader{
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha

        Pass{
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"                                                                                                                        

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            float4 _InlineColor;
            float _InlineWidth;
            
            v2f vert(appdata a){
                v2f v;
                v.pos = UnityObjectToClipPos(a.vertex);
                v.uv = TRANSFORM_TEX(a.uv,_MainTex);
                return v;
            }

            fixed4 frag(v2f v) : SV_TARGET{
                 
                 float4 col = tex2D(_MainTex,v.uv);
				
				 //获取周围上下左右4个点的uv
                 float2 up_uv = v.uv + float2(0,_InlineWidth * _MainTex_TexelSize.y);
                 float2 down_uv = v.uv + float2(0,-_InlineWidth * _MainTex_TexelSize.y);
                 float2 left_uv = v.uv + float2(-_InlineWidth * _MainTex_TexelSize.x,0);
                 float2 right_uv = v.uv + float2(_InlineWidth * _MainTex_TexelSize.x,0);
                 
                 //根据uv,获取周围上下左右4个点的alpha乘积
                 float arroundAlpha = tex2D(_MainTex,up_uv).a *
                 tex2D(_MainTex,down_uv).a *
                 tex2D(_MainTex,left_uv).a *
                 tex2D(_MainTex,right_uv).a;
                 
                 //让描边变色
                 float4 result = lerp(_InlineColor,col,arroundAlpha);
                 //使用原来的透明度
                 result.a = col.a;
                                 
                 return result;               
            }

            ENDCG
        }

    }
    FallBack "Sprites/Default"
}

2.2 效果

在这里插入图片描述

2.3 分析

这里没有完全按照思路那样去写,因为那样写的话,就要加上if语句,加上if之后我们还是要优化掉的。
这里先讲一个有用的知识点:_MainTex_TexelSize是获取纹理宽高的内置变量。值为:Vector4(1 / width, 1 / height, width, height),我们使用了此变量来帮助我们获取到周围的uv

//我们用了此句代码,让描边变色。但是除此之外,所有的透明像素也会因此变成描边的颜色
float4 result = lerp(_InlineColor,col,arroundAlpha);

//接着我们使用原来的alpha,这样就可以让上句代码多覆盖的像素继续变成透明
result.a = col.a;

2.4 优化

2.4.1 解决_InlineWidth等于0时,也有描边的问题

以上基本就完成了效果,但是有个小问题就是:_InlineWidth = 0时,我们是不希望有描边效果的,但是可以看到依然有描边效果。这是因为周围的像素透明度比较低,乘积接近0,所以变成了描边颜色。
我们可以用step函数来解决此问题,当_InlineWidth很小的时候,使用原颜色

				 //让描边变色
                 float4 result = lerp(_InlineColor,col,arroundAlpha);
                 //使用原来的透明度
                 result.a = col.a;
                                 
                 //上面的代码在_InlineWidth = 0时,仍然有一丝描边的颜色。这里控制一下
                 float threshold  = step(0.000001,_InlineWidth);
                 result = lerp(col,result,threshold);

                 return result; 

这样_InlineWidth = 0,就可以没有描边效果了
在这里插入图片描述

2.4.2 增加亮度

因为描边最好是布灵布灵bulingbuling的,所以可以调节亮度会更好一点
接下来我们简单加下变量:_Light(“Light”,Range(1,5)) = 1

 				//让描边变色
                 float4 result = lerp(_InlineColor * _Light,col,arroundAlpha);
                 //使用原来的透明度
                 result.a = col.a;

效果
可以看到描边更明显了
在这里插入图片描述

三、完整代码

到此,我们的代码就写完了。接下来附上完整的代码

Shader "Custom/2DInline"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white"{}
        _InlineColor("InlineColor",Color) = (0,0,0,1)
        _InlineWidth("InlineWidth",Range(0,10)) = 1
        _Light("Light",Range(1,5)) = 1
    }

    SubShader{
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha

        Pass{
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"                                                                                                                        

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            float4 _InlineColor;
            float _InlineWidth;
            float _Light;
            
            v2f vert(appdata a){
                v2f v;
                v.pos = UnityObjectToClipPos(a.vertex);
                v.uv = TRANSFORM_TEX(a.uv,_MainTex);
                return v;
            }

            fixed4 frag(v2f v) : SV_TARGET{
                 
                 float4 col = tex2D(_MainTex,v.uv);

				//获取周围上下左右4个点的uv
                 float2 up_uv = v.uv + float2(0,_InlineWidth * _MainTex_TexelSize.y);
                 float2 down_uv = v.uv + float2(0,-_InlineWidth * _MainTex_TexelSize.y);
                 float2 left_uv = v.uv + float2(-_InlineWidth * _MainTex_TexelSize.x,0);
                 float2 right_uv = v.uv + float2(_InlineWidth * _MainTex_TexelSize.x,0);
                 
                  //根据uv,获取周围上下左右4个点的alpha乘积
                 float arroundAlpha = tex2D(_MainTex,up_uv).a *
                 tex2D(_MainTex,down_uv).a *
                 tex2D(_MainTex,left_uv).a *
                 tex2D(_MainTex,right_uv).a;
                 
                 //让描边变色
                 float4 result = lerp(_InlineColor * _Light,col,arroundAlpha);
                 //使用原来的透明度
                 result.a = col.a;
                                 
                 //上面的代码在_InlineWidth = 0时,仍然有一丝描边的颜色。这里控制一下
                 float threshold  = step(0.000001,_InlineWidth);
                 result = lerp(col,result,threshold);

                 return result;               
            }

            ENDCG
        }

    }
    FallBack "Sprites/Default"
}


猜你喜欢

转载自blog.csdn.net/aaa27987/article/details/131229746