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"
}

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中实现描边效果,可以通过编写Shader来实现。具体实现方法如下: 1. 首先,在Shader中需要先声明一个描边颜色的变量: ```csharp // 描边颜色 _Color ("Outline Color", Color) = (0,0,0,1) ``` 2. 接着,需要在Shader中将表面进行放大,然后将描边颜色填充到放大后的表面上: ```csharp // 放大表面,并填充描边颜色 float2 borderOffset[4] = {float2(1,0), float2(-1,0), float2(0,1), float2(0,-1)}; float border = _OutlineWidth * (1.0 / _ScreenParams.z); float4 borderColor = _Color; float4 c; for (int i = 0; i < 4; i++) { c = tex2D(_MainTex, i.uv + border * borderOffset[i]); borderColor.a *= c.a; o.Normal += borderColor.a * borderOffset[i]; } ``` 这里,`borderOffset`表示描边的偏移量,`border`表示描边的宽度,`borderColor`表示描边的颜色,`c`表示当前像素点的颜色值。在循环中,依次对当前像素点的上、下、左、右四个方向进行采样,并将采样到的颜色值和描边颜色进行叠加。 3. 最后,在Shader中将表面的颜色和描边颜色进行混合: ```csharp // 混合表面颜色和描边颜色 o.Albedo = lerp(_Color.rgb, o.Albedo, o.Normal); ``` 这里使用了`lerp`函数来对表面颜色和描边颜色进行混合,根据描边的程度不同,可以调整`_Color`的alpha值来控制描边的显示程度。 综上所述,完整的描边Shader代码如下: ```csharp Shader "Custom/Outline" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color ("Outline Color", Color) = (0,0,0,1) _OutlineWidth ("Outline Width", Range(0, 2)) = 1 _ScreenParams ("Screen Params", Vector) = (0,0,0,0) } SubShader { Tags {"Queue"="Transparent" "RenderType"="Transparent"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 Normal : TEXCOORD1; }; sampler2D _MainTex; float4 _Color; float _OutlineWidth; float4 _ScreenParams; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.Normal = float4(0,0,0,0); return o; } fixed4 frag (v2f i) : SV_Target { float2 borderOffset[4] = {float2(1,0), float2(-1,0), float2(0,1), float2(0,-1)}; float border = _OutlineWidth * (1.0 / _ScreenParams.z); float4 borderColor = _Color; float4 c; for (int j = 0; j < 4; j++) { c = tex2D(_MainTex, i.uv + border * borderOffset[j]); borderColor.a *= c.a; i.Normal += borderColor.a * borderOffset[j]; } i.Normal.a = 1.0; i.Normal = normalize(i.Normal); fixed4 col = tex2D(_MainTex, i.uv); col.rgb = lerp(_Color.rgb, col.rgb, i.Normal); col.a = col.a * i.Normal.a; return col; } ENDCG } } FallBack "Diffuse" } ``` 使用该Shader,你可以在游戏中对需要描边的物体进行材质替换,从而实现描边效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值