一、前言
今天,我们来实现一个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;
效果
文章来源:https://www.toymoban.com/news/detail-757623.html
三、完整代码
到此,我们的代码就写完了。接下来附上完整的代码文章来源地址https://www.toymoban.com/news/detail-757623.html
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"
}
到了这里,关于Unity 2D外描边Shader的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!