0
0

一种 16 倍抗锯齿字体渲染的方法

云风 发表于 2019年01月08日 18:01 | Hits: 818
Tag: 算法 | 游戏开发

昨天读了几篇文章,讲解了一种新的抗锯齿字体渲染的方法

我觉得颇有意思,就试着实现了一版 CPU 版本,想看看针对中文的效果。虽然最后觉得这个算法对游戏领域的实用性不大,不过还是挺有启发的。这里写写我对这个算法的理解,以及我所理解的算法局限性。

原文讲解的非常细致,还配了不少图片,我就不再重复了,只简单说两句。

我认为发明这个算法的动机是 “Our UI has a lot of smooth animation, text should be able to move smoothly across the screen.” 这是过去很多传统字体渲染算法很难解决的。

当我们输出的文字略微偏移 1/4 个屏幕像素时,通常会糊掉。但是这个算法则可以尽可能地保留信息。

因为这个算法的本质是把 16 倍大的字形,以黑白二值位图的形式保存在一个每像素 16bit 的贴图中。和传统的保存字形位图的方式不同,它不储存字体抗锯齿后的灰阶图片,而是一个 bit 保存一个像素。贴图的一个图素保存了 4x4 共 16 个像素。把 16 个点阵像素压缩到贴图的一个图素中带来的并不仅仅是压缩的好处,更重要的是,可以让 gpu 做采样时,可以一次拿出更多的字形信息,方便做抗锯齿处理。

我们在纹理采样时,从贴图上采到的一个图素其实包含了 16 个字形像素的信息。如果我们采样 4 次,就可以拿到 16x16 = 256 像素的位图。然后,计算出真正采样的范围,统计这个采样区域 bit 1 的个数,就可以精确的算出灰阶。

相比传统储存方法,偏移部分像素,就只能通过简单的混合算法求出平均灰阶,这种储存方法相当于推迟了字型抗锯齿计算灰阶的时机。

我做了一个简单的动画,让一个 23 像素高的 “好”字在一个像素范围内轻微位移转圈,我们可以看到,好字的包围盒并没有扩大,但肉眼明显能感知到字的移动。

我们把好字的半像素位移展开, X Y 方向各移动 1/4 1/2 3/4 像素,得到 16 个字,是这样的:

放大点可能看的更清楚:

如果我们把灰阶改成类似微软 ClearType 的方法,利用液晶屏 RGB 三个子像素,可以做到更清晰。这部分工作我懒得做了。

但我认为,这个方法对于目前游戏并没有太大意义。因为用 GPU 实现过于复杂,而收益(文字移动平滑这个特性)并不大。

而且算法本身有个弱点,原文并没有解决。那就是当字体放大或缩小时,其实贴图到帧缓冲的算法就不再是 1:1 的关系。如果按原文的方法 4x4=16 像素的窗口去采样,结果是不对的。如果最终显示的文字比原始字型点阵要小,就应该用大于 4x4 的采样窗口;如果要放大原始点阵,则应该用更小的采样窗口。

这个方法的极限可以把字形放大 4x4 倍而不失真。当你需要输出 4x4 倍大的文字时,其实每次只应该在贴图上采样一个 bit (1x1 窗口)。

用 CPU 可以勉强解决这个缩放问题,GPU 来做也未尝不可。我构想的方法是另外做一种带 mipmap 的纯色贴图,不同的 mipmap 层填充不同的颜色通道,比如第一层用纯红,第二层用纯绿,然后根据采样的颜色结果的红绿比例,可以推测出贴图到帧缓冲的映射比。

对英文来说,缩放问题并不麻烦。只要可以快速生成需要的大小的贴图(这也是原文第三篇讨论的话题),加上合适的 cache 即可;但是中文字形太多,我们就需要太大的 cache 。这也是我认为该算法(对中文)并不实用的原因之一。

原文链接: https://blog.codingnow.com/2019/01/font_16aa.html

0     0

评价列表(0)