|
最近在写一些关于Unity Text的shader,有一些自己的发现所以想和大家分享一下。
先会简单写一个默认的UI shader。然后观察Text与Image组件的UV在屏幕上是如何显示的,最后通过提取周围像素alpha值的方式完成Text的描边,做一个简单的文字描边的替换效果。
注:通过模板测试和imge也可以做到类似效果。
Simple UI
实验用的是URP渲染管线,但是在写UI shader的时候发现也可以使用CG来编写。
主要是实现文字的描边填充效果,所以我将模板测试舍去了。
Shader "Unlit/UItest"
{
Properties{}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
return 1;
}
ENDCG
}
}
}注意事项,需要使用Transparent的RenderType
如果不开半透明渲染的话无法获得alpha通道,Text组件就会出现一片白的情况。
我这里创建了一个text组件一个image组件,为了后面方便观察UV。
从图中我们可以看出,Text组件出现的一个一个的四边形方框,这是因为Text中每一个文字都相当于一个image。
每一个文字都有自己的uv值,这如果用来做MASK遮罩显然是不行的,所以后面我采用的方式是重新建立一个屏幕空间的UV,用屏幕空间的UV来做遮罩。
现在虽然用了半透明的渲染方式,但是我们输出的结果为1 所以整个框选文字的image都会为白色。
必须利用_MainTex这个RT来获取文字的alpha值,使用其他命名的RT没有任何作用。是Unity内部规定的RT名字。
half4 var_Main = tex2D(_MainTex, i.uv);
return var_Main;
可以看见现在文字已经显示出来了。
现在想要实现的效果是将文字的颜色通过Text组件中的颜色值进行控制。那么要获取组件中的颜色需要写C#脚本来传进shader中么?
答案是NO!
经过我的查找发现默认的shader中都会在传入参数中获取顶点颜色,
float4 color : COLOR;这个顶点颜色便是我们组件中的Color属性。
在片元着色器中添加这个属性可以直接控制颜色和alpha。
half4 var_Main = tex2D(_MainTex, i.uv);
half3 color = i.color.rgb;
half alpha = var_Main.a * i.color.a;
return half4(color,alpha);
由于暂时不需要模板测试所以简单的Test shader就结束了。
接下来是介绍如何对文字进行描边操作和用计算的mask来进行文字从填充到描边形式的过渡。
文字描边效果
缺点
- 无法大范围地控制描边粗细,有溢出情况。
- 由于采样的边界太小,有锯齿。
描边思路
图中的0、1就是在每个image中像素的alpha值。
现在需要的是在文字边缘的一圈0,做上标记。采用获取周围像素alpha的方法来进行描边。
有一个自己的小trick就是,在计算周围9个像素后,我会判断总值是否为9,如果为9也将它标记为0,这样文字内部的1也会变成0,只有最靠近边缘的一圈1会保留,相当于将描边扩大了一个像素的距离(增加描边粗细的可控范围),并且可以直接得到描边。
//检查以自身为中心的九个像素点
static const float2 dirList[9] = {
float2(-_Bias,-_Bias),float2(0,-_Bias),float2(_Bias,-_Bias),
float2(-_Bias,0),float2(0,0),float2(_Bias,0),
float2(-_Bias,_Bias),float2(0,_Bias),float2(_Bias,_Bias)
};
//得到每个像素点的alpha值
half getDirPosAlpha(float index, float2 xy) {
float2 curPos = xy;
float2 dir = dirList[index];
float2 dirPos = curPos + dir * _MainTex_TexelSize.xy * 0.6;
return tex2D(_MainTex, dirPos).a;
};
//进行描边的mask标记
half getShadowAlpha(float2 xy) {
float a = 0;
float index = 0;
a += getDirPosAlpha(index, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
a += getDirPosAlpha(index++, xy);
if(a==9)
a = 0;
a = clamp(0,1,a);
return a;
}返回的a就是描边。这里的_Bias是为了控制描边的粗细(需要注意像素溢出)
如果_Bias值设过大就会造成下面的效果。
再加上一个_Outline_color参数来单独控制描边的颜色作区分。将_Color的alpha值单独控制字体,描边采用_Outline_color,描边的alpha值用_Outline_color的a通道控制。
half4 var_Main = tex2D(_MainTex, i.uv);
half outline = getShadowAlpha(i.uv.xy);
half outalpha = _Outline_color.a * outline;
half3 outcolor = _Outline_color.rgb * outline;
half inalpha = var_Main.a * i.color.a;
inalpha = saturate(inalpha - outline);
half3 incolor = i.color.rgb * inalpha; 这里用inalpha-outline是为了取消那一个像素的影响,可以根据需求选择是否添加
未添加
添加
最后使用lerp就可以进行填充与描边的过渡
color = lerp(incolor, outcolor, mask);
alpha = lerp(inalpha, outalpha, mask)* var_Main.a;根据屏幕空间创建遮罩mask
首先观察uv。
可以看见Text组件的UV十分零乱,这是没法使用的,我的思路是通过世界空间或者屏幕空间,在原点或屏幕中点和text的顶点位置.x做distance,得到的值为0到正无穷,接着用1-distance,得到负无穷到1的值,用Step(0.5,1-distance(原点.x , vertex.x))做限制,得到一个0、1的遮罩。
图中白色为1,其余为0。通过改变原点的位置来控制mask的位置。
得到的mask
half2 ScreenUV = i.vertex_CS.xy;//获取屏幕空间的uv
half2 center_CS = half2(_mask_bias * 0.1,ScreenUV.y);
half mask = distance(ScreenUV, center_CS);
mask = 1-step(_mask_width*0.1,mask);
half3 color = lerp(incolor, outcolor, mask);
half alpha = lerp(inalpha, outalpha, mask)* var_Main.a;
return half4(color,alpha);让mask进行旋转
可以用算法直接得到平行四边形的面积,我这里取巧对uv进行旋转,达到一样的效果。
half2 rotateUV(float2 uv)
{
uv = uv - half2(0.5, 0.5);
uv = half2( uv.x * cos(_rotate) - uv.y * sin(_rotate)
,uv.x * sin(_rotate) + uv.y * cos(_rotate));
uv += half2(0.5, 0.5);
return uv;
}
完成。
可能有更好的方案也在学习中,希望能一起学习交流。
参考:unity ugui自定义Text字体描边优化性能 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|