找回密码
 立即注册
查看: 634|回复: 0

Unity Text的填充过渡描边 文字替换效果Shader

[复制链接]
发表于 2021-12-7 11:57 | 显示全部楼层 |阅读模式
最近在写一些关于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字体描边优化性能

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 04:33 , Processed in 0.090918 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表