Text和TextMeshPro

发布时间 2023-12-26 20:11:21作者: mc宇少

参考文章:

1.关于TextMeshPro | (ttthyy.com)

2.[UGUI图文混排一]TextMehPro(TMP)使用手册 - 知乎 (zhihu.com)

1.Text

1.原理:

Text会根据所给定的字符串生成相关的图集,然后对图集进行采样就可以渲染出文字了。文本字形是作为独立的面片(quad)进行渲染的,每个字符都是一个面片。

OnPopulateMesh(会在Rebuild时调用)

为CanvasRender的Mesh提供顶点位置、顶点颜色、UV和三角形信息

1.根据组件的配置生成一个TextGenerationSettings,用来生成后面的信息。

2.调用TextGenerator.PopulateWithErrors生成Mesh的顶点、顶点颜色、UV和三角形信息。

3.计算偏移量(例如左对齐需要紧靠左边),最后遍历TextGenerator的顶点数组,将它们的位置除以pixelsPerUnit(每单元像素)并加上偏移量(如果有的话),得到的结果填到VertexHelper(可以拿到顶点信息)

2.字符图集

被加载的每个不同的Font对象都会维护自己的纹理集。在Unity的实现中,这些字体在运行时根据Text组件中出现的字符构建一个字形图集(glyph atlas)。

动态字体为每种不同的结合(尺寸、样式、字符)在其纹理集中维护了一个字形。也就是说,如果一个UI中有两个Text组件,都显示了字符“A”,那么

  • 如果两个Text组件尺寸相同,那么字体图集中会有一个字形。
  • 如果两个Text组件尺寸不同,那么字体图集中会有两个不同尺寸的字母“A”。
  • 如果一个Text组件的样式是粗体而另一个不是,那么字体图集中会含有一个粗体的“A”和一个普通的“A”。  

当使用动态字体的Text对象遇到了字体纹理集(对应字符图集)中没有的情况,必须重建字体纹理集

如果新的字形能够加入当前图集,那么将其加入图集并上传到图形设备。

但是如果当前的图集放不下,那么系统会尝试重建图集。重建图集的过程:

1.尝试回收掉不用的字符:以相同的大小重建图集,只使用当前活动的Text组件上显示的字形。

2.回收后还是放不下就要扩容:例如,一个512x512的图集或被扩充到512x1024的图集。

所以,为了避免频繁的重建字符图集,Text使用的字符最好在使用前就填充到字符图集中

分为两种情况:

1.如果使用的字体仅需要支持部分字符集(比如只需要几个固定的字),那就可以使用非动态字体并预先配置对想要使用的字体集的支持。

2.字符集不固定或者很多(比如整个Unicode集合),那么字体必须设为动态,可以将出现频率高的字符在游戏运行时填充(使用Font.RequestCharactersInTexture)。

3.使用静态字体集

https://blog.csdn.net/qq_29867963/article/details/77680033

4.支持回滚系统字库

在遇到选择的字体字库没有的字的情况,可以自动使用系统的字库,可以避免生僻字不渲染的情况。

5.缺点

1.由于使用的是点阵字贴图(贴图存的是颜色值),所以放大缩小文本(更改scale)会使字体编的模糊。

 

2.前面提到了Text的动态图集是增量式扩大,这种方式在文本量很小的情况下贴图很小,很舒服,但随着文本量的增加,会频繁的发生重建以及扩容,可能会引起卡顿。

3.前面提到了字符图集是按Text组件的设置存储的,所以会出现下面的情况:

 这些字块贴图会占用整张文本贴图的空间,导致过快的撑满一张贴图,引起扩大重建。而且字号很大的Text所生成的贴图也很大,很占空间,这也是提倡不要使用Text组件BestFit功能的一个主要原因,BestFit功能会造成文本字号的不可控,导致创建出许多不同字号的字块贴图。。

4.功能限制:不支持富文本标签的全部功能、也不支持所有的文本效果,而且一些文本效果实现很费(描边、阴影等都是通过增加顶点并偏移的方式,正常项目中很少会用到)。

2.TextMeshPro

作为Unity的最终文本解决方案,除了支持图文混排,它还支持矢量字体,可以很好的替代旧版的Text组件。

针对Text的缺点,做出了如下改进

1.使用矢量字(SDF算法)。

这种实现方式不会出现缩放时的失真并且不会根据不同的字号创建不同的贴图(毕竟不会失真)

SDF算法简述

对于字块贴图上记录的不是像素的颜色信息,而是每个像素到字形边缘的距离,当文本放大缩小时,TextMeshPro对像素到字形边缘的距离进行插值,而不是对颜色插值,也就是说此处得到的一个字形的轮廓区域,并没有马上得到每个像素的颜色,如果用01来表示的话,相当于就是把落在字块上的像素全部标1,然后再进行上色。这样就不存在对颜色插值不准确造成的失真问题,因为对距离的插值总是可以得到一个清晰的字形轮廓

2.解决字符贴图重建的问题

1.每个字体要创建对应的字体资源文件

这个文件可以是动态渲染模式或者是动态渲染模式(和Text差不多),在这个文件中配置生成的字符贴图的设置:
动态:

 静态:

 当创建的字符贴图尺寸超过配置的Atlas Width 与 Atlas Height时就不会再继续加入字符了(显示成方块),可以勾选Multi Atlas Textures来允许创建多张贴图(会打断合批)

 2.字体回滚

TextMeshPro字体允许设置一个回滚字体(注意此处的回滚与上面的Dynamic模式回滚ttf模式不同),当本字体中缺少字符时,可以从设置的回滚字体列表(同样都是TextMeshPro字体)中查找需要的字符。

 

基于这些特性,可以很好的解决Text中关于大贴图重建的问题。

可以创建一个包含常用文字的静态字体,并设置一个回滚TextMeshPro字体(动态字体、贴图尺寸不宜过大(降低重建贴图的开销),并勾选Multi Atlas Textures)。这样,一般使用的时候从静态字体中获取字块,若静态字体中缺少字块时,从动态字体中加载。 可以解决 静态字体字符数过多导致初始加载过大的问题,以及使用动态回滚字体时,重建贴图过大的问题,允许使用多张贴图,将重建一张大贴图的开销拆分到重建小图中(需要权衡一下是要内存小还是能合批)。

4.对富文本和文字效果很友好

1.支持图文混排

设置SpriteAsset,并用富文本引用

2.文字效果

描边、阴影等效果都是使用Shader实现的,相比于原生的Text组件通过增加顶点偏移的方式,渲染效果更好,并且效率也更高。但一般项目应该都自己实现过了,对新项目比较友好。

5.缺点

1.SDF算法的性能

生成SDF图的算法简单说就是求每个像素点到文字的最短路径的动态规划(不确定textmeshpro有没有做比较牛逼的优化,网上说是8ssedt)。

如果高分辨率下动态生成字符图集,像素点数量很多,并且一次要添加的字符很多,可能会卡(没测过),正常情况下不会。

2.DrawCall

文字效果是通过不同的Material实现的,添加一个新的特效文字,步骤:

先调整好需要的特效、然后点击Create Material Preset 创建一个新的Material。

 在实际开发中,可能会用到很多种不同效果的字体,这将会导致需要创建大量材质球,增加DrawCall

3.堆内存

创建100个Text(TMP),可以看到它的GC Alloc达到了1.8MB。不使用TMP,那创建100个Text呢?经过测试,产生的GC是1.0MB。

 其中TextMeshProUGUI的Awake、OnEnable和.ctor都产生了大量的GC。查看代码可知,主要原因是由于 TMP_Text 和 TMP_TextInfo 在创建时预创建了较多的数组变量,导致申请了较大的堆内存,另一方面,数组变量在中间使用的过程,存在Resize操作,又会产生新的堆内存申请。

为了优化这个问题,大家可以根据实际需要,将原先申请的内存缓存起来,在下一次创建的时候重新拿起来使用,避免每次都重新申请。