Unity 2D外描边Shader

一、前言

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

二、Shader内容

2.1 初版shader

好,我们根据思路来写代码

Shader "Custom/2DOutline"{
    
    Properties{
        _MainTex("Texture",2D) = "white" {}
        _OutlineWidth("OutlineWidth",Range(0,10)) = 0
        _OutlineColor("OutlineColor",Color) = (0,0,0,1)     
    }

    SubShader{
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent"}



        Pass{
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off
            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; //获取_MainTex纹理的Tiling和Offset,带入xyzw
            float4 _MainTex_TexelSize;//获取_MainTex纹理的宽高,4个分量如下:Vector4(1 / width, 1 / height, width, height)
            
            float _OutlineWidth;
            float4 _OutlineColor;

            
            //顶点着色器不做额外操作
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            
            //片元着色器
            half4 frag(v2f v) : SV_Target{
                fixed4 col = tex2D(_MainTex,v.uv);
                
                //获取自身alpha
                float pointAlpha = col.a;

                //获取周围上下左右4个点的UV
                float2 up_uv = v.uv + float2(0,_OutlineWidth * _MainTex_TexelSize.y);
                float2 down_uv = v.uv + float2(0,-_OutlineWidth * _MainTex_TexelSize.y);
                float2 left_uv = v.uv + float2(-_OutlineWidth * _MainTex_TexelSize.x,0);
                float2 right_uv = v.uv + float2(_OutlineWidth * _MainTex_TexelSize.x,0);

                //获取周围上下左右4个点的alpha总和
                float aroundAlpha = tex2D(_MainTex,up_uv).a +
                tex2D(_MainTex,down_uv).a + 
                tex2D(_MainTex,left_uv).a + 
                tex2D(_MainTex,right_uv).a;

                //自身alpha>0, 保持
                if(pointAlpha > 0){
                    return col;
                }

                //周围4个点的alpha>0,使用描边颜色
                if(aroundAlpha > 0){
                    return _OutlineColor;
                }
                return col;
                
            }
            ENDCG
        }
    }
    FallBack "Sprites/Default"
}

2.2 效果

在这里插入图片描述

2.3 分析

片元着色器按我们的思路来写,都加上了注释。
这里先讲一个有用的知识点:_MainTex_TexelSize是获取纹理宽高的内置变量。值为:Vector4(1 / width, 1 / height, width, height),我们使用了此变量来帮助我们获取到周围的uv

可以看到我们实现了描边的效果,但是可以看到在描边层和自身层之间好像还隔着一层透明的像素,如下图。这是因为在图片边缘的像素,他们本身的像素接近透明,alpha值比较小,但也大于0,因为我们做了if alpha>0,即保持自身像素的缘故。这一层也保持下来了。

	 //自身alpha>0, 保持
     if(pointAlpha > 0){
      	return col;
     }

在这里插入图片描述

2.4 优化

2.4.1 优化边缘接近透明的像素

从上面的分析中,我们可以把透明度的阈值提高一点,让边缘这层接近透明的像素也返回描边颜色;
我们可以定义一个透明度阈值变量: _AlphaThreshold(“AlphaThreshold”,Range(0,1)) = 0

 //自身alpha>_AlphaThreshold, 保持
 if(pointAlpha > _AlphaThreshold){
      return col;
  }

调整 _AlphaThreshold即可把这层接近透明的像素层也改成描边颜色
在这里插入图片描述

2.4.2 优化掉if

我们知道在shader里使用if会降低效率,所以我们接下来使用step和lerp函数来吧if替换掉
函数说明:
step(a,b) 当b>=a 返回1,否则返回0
lerp(a,b,progress) 在a~b之间进行插值,基于progress。我们可以传入step的值,因为step结果不是0就是1,所以返回的值不是a就是b

     			//周围的透明度step
                float arroundStep = step(0.01,aroundAlpha);
                //自身的透明度step
                float pointStep = step(_AlphaThreshold,pointAlpha);

                //先把周围和自身都改成描边颜色
                float4 result = lerp(col,_OutlineColor,arroundStep);
                //把自身原色还原
                result = lerp(result,col,pointStep);
                //返回
                return result;

效果
在这里插入图片描述

2.4.3 增加亮度

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

  				//周围的透明度step
                float arroundStep = step(0.01,aroundAlpha);
                //自身的透明度step
                float pointStep = step(_AlphaThreshold,pointAlpha);
                
                //乘上亮度值
                _OutlineColor.rgb = _OutlineColor.rgb * _Light;

                //先把周围和自身都改成描边颜色
                float4 result = lerp(col,_OutlineColor,arroundStep);
                //把自身原色还远
                result = lerp(result,col,pointStep);
                //返回
                return result;

效果
在这里插入图片描述

三、完整代码

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

Shader "Custom/2DOutline"{
    
    Properties{
        _MainTex("Texture",2D) = "white" {}
        _OutlineWidth("OutlineWidth",Range(0,10)) = 0
        _OutlineColor("OutlineColor",Color) = (0,0,0,1)   
        _AlphaThreshold("AlphaThreshold",Range(0,1)) = 0  
        _Light("Light",Range(1,5)) =1
    }

    SubShader{
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent"}



        Pass{
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off
            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; //获取_MainTex纹理的Tiling和Offset,带入xyzw
            float4 _MainTex_TexelSize;//获取_MainTex纹理的宽高,4个分量如下:Vector4(1 / width, 1 / height, width, height)
            
            float _OutlineWidth;
            float4 _OutlineColor;
            float _AlphaThreshold;
            float _Light;
            
            //顶点着色器不做额外操作
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            
            //片元着色器
            half4 frag(v2f v) : SV_Target{
                fixed4 col = tex2D(_MainTex,v.uv);
                
                //获取自身alpha
                float pointAlpha = col.a;

                //获取周围上下左右4个点的UV
                float2 up_uv = v.uv + float2(0,_OutlineWidth * _MainTex_TexelSize.y);
                float2 down_uv = v.uv + float2(0,-_OutlineWidth * _MainTex_TexelSize.y);
                float2 left_uv = v.uv + float2(-_OutlineWidth * _MainTex_TexelSize.x,0);
                float2 right_uv = v.uv + float2(_OutlineWidth * _MainTex_TexelSize.x,0);

                //获取周围上下左右4个点的alpha总和
                float aroundAlpha = tex2D(_MainTex,up_uv).a +
                tex2D(_MainTex,down_uv).a + 
                tex2D(_MainTex,left_uv).a + 
                tex2D(_MainTex,right_uv).a;
            
                //周围的透明度step
                float arroundStep = step(0.01,aroundAlpha);
                //自身的透明度step
                float pointStep = step(_AlphaThreshold,pointAlpha);
                
                //乘上亮度值
                _OutlineColor.rgb = _OutlineColor.rgb * _Light;

                //先把周围和自身都改成描边颜色
                float4 result = lerp(col,_OutlineColor,arroundStep);
                //把自身原色还远
                result = lerp(result,col,pointStep);
                //返回
                return result;
                
            }
            ENDCG
        }
    }
    FallBack "Sprites/Default"
}

猜你喜欢

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