Unity 2D内描边Shader
- 一、前言
- 二、Shader内容
- 2.1 初版Shader
- 2.2 效果
- 2.3 分析
- 2.4 优化
- 2.4.1 解决_InlineWidth等于0时,也有描边的问题
- 2.4.2 增加亮度
- 三、完整代码
一、前言
今天,我们来实现一个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"
}