在使用TextMeshPro的时候遇到了字体黑底的问题,类似下图这样

Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 当字体较大的时候表现正常,当缩小到一定程度就会出现黑底。这个情况让人第一时间就是怀疑SDF计算缩放的时候存在问题。在我们重新导出字体,调整图集字体大小以及Padding后,让SP/PD值差不多大于3%就能解决这个问题

Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 这个SP/PD就是Padding值/Samping Point Size

上面说到SP/PD值应该低于10%,这个是从节省图集大小来考虑的,当SP/PD过大,每个字符间隙很大,对于我们描边也没意义,10%就是差不多很够用的一个值。但是并没有说到需要一个下限,如果低于这个下限会出什么问题,文档没有说明,很多博客也没说到过。没错,如果过低就会出现上面看到的黑底问题,具体是为什么我们稍后讨论,这里先讲讲如何解决这个问题。

解决方案

我们的目标是控制SP/PD值在一个区间值内,低于某个值会出现黑底问题,高于会存在浪费空间的问题。10%为一个比较合理的上限,那个这个下限是多少呢?官方没有规定,那也只有我们自己看着合适就好,这充分贯彻了那句话”计算机图形学的第一定律:如果它看起来是对的,那么它就是对的“

Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 在我调整Padding将SP/PD控制在4%以上之后,肉眼看过去基本就感觉不出来了。

想要调整SP/PD值的方法如下:

  1. 调整Padding
  2. 调整采样字体大小Samping Point Size
  3. 调整字体图集分辨率Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 了解更多

了解了如何解决这个问题,但是这是为什么呢?我脑子里面冒出了下面几个问题

  1. 为什么只有在缩放的时候或者字体很小的时候会出现黑底?
  2. 是不是只有使用outlie的时候才会出现黑底?
  3. 不使用也出现黑底,那个这个黑底出现的原因是什么?

浅浅的了解SDF

要想解决上面的问题,我们首先要浅浅的了解下什么是SDF,为什么SDF可以无限放大。

SDF(Signed Distance Field)是一种用于将矢量图形转换为比特图的技术,我们生成的图集其实就是一张单通道(Alpha)的纹理,这个alpha值就是存储的距离或者说是梯度。

Unity TextMeshPro文本存在背景框的问题研究-LMLPHPUnity TextMeshPro文本存在背景框的问题研究-LMLPHP

通过这张纹理,我们可以知道字体的边界在哪里,有点类似边缘检测的卷积,也是知道边界梯度,我们可以拿这这个值无限放大字体,也可以在边界添加各种效果,其中当然就包括我们今天讨论的描边。

当然,纹理存储的信息是 [0,1]范围的值,在计算中需要-0.5映射到[-0.5,0.5]区间,0表示字体边界,大于表示在字体边界内,小于0表示在字体边界外。我们可以从TMP_SDF-Mobile.shader这个文件中窥探一二 

float bias = (0.5 - weight) * scale - 0.5;

 寻找线索

那个值是关键?

知道了解决问题的关键在于shader中,我们就需要研究下一个字体的渲染过程,我们测试发现,只有在缩放或字体极小的时候会出现黑底,那这个问题的关键肯定于缩放相关,Distance Field中很关键的scale值就与缩放相关,下面我们先看看代码

float2 pixelSize = vPosition.w;
pixelSize /= float2(_ScaleX, _ScaleY) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

float scale = rsqrt(dot(pixelSize, pixelSize));
scale *= abs(input.texcoord1.y) * _GradientScale * (_Sharpness + 1);

这个scale计算的是当前分辨率下,顶点的物理尺寸,当我们缩放字体的时候scale会变得很小,这个时候就出现了黑底。

接下来我们就是需要找到为什么scale值小的时候会出现黑底?

为什么scale值变小后会出问题?

half d = tex2D(_MainTex, input.texcoord0.xy).a * input.param.x;
half4 c = input.faceColor * saturate(d - input.param.w);

理论上c的值在边界外a通道应该小于裁剪阈值0.01,但是没有,就可以推断上面代码d是大于input.param.w(bias),可以推断下面几种可能性

  1. d计算错误
    1. 采样得到alpha通道的梯度值错误
    2. 计算所得scale值错误
  2. bias计算错误

其中bias如果错误也可以转换到scale计算的错误。现在把范围缩小为到底是scale计算错误还是采样错误。由于我的目前比较菜,也只能猜测。我认为scale由于采用的是变换矩阵计算而得,是不会出现问题,如果出问题,都出问题了,能出问题的一定是我们导出的设置相关引起的问题。那必然就是我们导出的纹理有问题

Unity TextMeshPro文本存在背景框的问题研究-LMLPHP

 我们通过上面图对比,相同PointSize与TextureSize下,Padding更大的那个纹理,alpha通道表示得到的梯度更加的光滑,这个就可以解释文本在缩小的时候导致物理尺寸变小,导致纹理采样的时候不能准确计算边界导致了黑底的存在。

以上都是自己根据看现成代码推理而得,个人不针对推理负责。不过解决方案你了解即可。

04-28 16:59