« 去掉 full userdata 的 GC 元方法 | 返回首页 | BT 下载器下载的安装文件被杀毒软件卡住的问题 »

字体勾边渲染的简单方法

我们的游戏中需要对渲染字体做勾边处理,有种简单的方法是将字体多画几遍,向各个方向偏移一两个像素用黑色各画一遍,然后再用需要的颜色画一遍覆盖上去。这个方法的缺点是一个字就要画多次,影响渲染效率。

前几年有人发明了另一种方法,google 一下 Signed Distance Field Font Rendering 就可以找到大量的资料。大体原理是把字体数据预处理一遍,把每个像素离笔画的距离用灰度的形式记录在贴图上,然后写一个专门的 shader 来渲染字体。好处是字体可以缩放而不产生锯齿,也比较容易缺点边界做勾边处理。缺点是字模数据需要离线预处理。

我们的手游项目以及 3d 端游项目都大量使用了勾边字体,我希望直接利用系统字体而不用离线预处理字体把字体文件打包到客户端中。前段时间还专门实现了一个动态字体的贴图管理模块 。btw, 苹果的平台提供了高层 API 可以直接生成带勾边效果的字模。

但是,勾过边的字模信息中同时包含了轮廓信息和字模主体信息,看起来似乎很难用单通道记录整个字模数据了。这给染色也带来了麻烦。

char_a_org.png

见这张图,是一张带勾边信息的字模。轮廓是黑色的,字体是白色的。从颜色通道上看,有黑白灰的过渡。但灰色部分 alpha 通道对应量却不相等。轮廓向字体主干过渡的地方,色彩是灰色的,但是 alpha 值为 1.0 。也就是说,alpha 通道是独立的。

我们需要两个通道,颜色通道和 alpha 通道,来保存完整的字模信息才能在最后正确的渲染到屏幕上。很多显卡硬件并不支持两通道贴图,所以要么我们用一个 RGBA 四通道贴图来保存,要么用两张单通道贴图。

我想了个简单的方法只用一个通道就可以保存全部信息,那就是把 alpha 为 1.0 的像素的灰度影射到 0.5 到 1 的区间;把 alpha < 1.0 的部分像素的 alpha 值影射到 0 到 0.5 的区间。这样做可行是因为,经过勾黑边的白字,其 alpha 小于 1.0 的像素一定都是黑色的,也就是 RGBA 都相等。所以信息只损失了一个 bit 就保存了下来。

char_a_trans.png

经过处理后的贴图是这样的。可以看到过渡是均匀的,所以并不会影响纹理采样。最终我们写一个简单的 shader 就可以对字体做染色了。最终效果是这样的:

char_a_color.png

Shader 也非常简单,只需要取出单通道贴图上的灰度值 G ,

Alpha := clamp(G * 2.0 , 0, 1.0)

Color := clamp((G-0.5) * 2.0, 0, 1.0)

就可以还原出原来的颜色通道和 alpha 通道的信息。

Comments

云风你好,你的文章很精彩,给了我很多启发。 你的做法我理解了。你确实可以通过这个做法将颜色的apha通道和颜色通道打包到一个字节,然后在shader中取出来。还原。实际上alpha通道只在字体最外部,与整个背景融合的部分才有意义对吧。其他地方都是1.0F。 但是我还是不能理解你的做法如何做到划分边框颜色和填充颜色的。 按我的理解,如果字体渲染不需要支持边框,情况会很简单,因为字模中的灰度只有一个,在具体渲染的时候,shader直接加载单通道纹理,外部传入字体颜色,将灰度按alpha通道处理即可。 但是你的做法,在生成字模的时候,将边框灰度和填充灰度融合到了一起,实际上给我的感觉似乎在这一步失去了边框和填充的度量。最后在Shader中, 取出的alpha值姑且不论,取出的color通道应该怎么处理呢,如何绘制出黑色的边框和红色的填充。 再比如如果同样的字模纹理。能否仅仅是给Shader传入不同的边框颜色和填充颜色就能绘制出不同的颜色效果。 我的想法是要达到这个效果,需要分别用两个gray8层来存储outline gray 和fill gray(或者用一个rgba纹理来存,但要浪费一半的空间)。然后shader接受outline_color 和 fill_color,取outline_gray 和 fill_gray进行颜色融合. 不知道我是不是没有读懂你的意思或者是忽略了什么。
学习了,[url]http://www.haobitou.com/#2[/url]
原来这样渲染。
这样看起来似乎还真是有点不同了!赞支持!!
有段时间仔细研究了下字体,GDI+和FT。发现共同抽象出来能实现市面上的所有效果设置更高级,应该是分层。 每层是个gray8的图,每层参数包括宽度-描边大小 是否内部填充 偏移-XY,是否高斯模糊-模糊系数, 然后叠加所有的层,可以实现任意效果。 字体本身对应用来讲又分像素字和矢量。描边方法稍微不同。不论GDI/GDI+和FT都需要做下区分,比如宋体12像素字以下。像素字GDI+方便点,描绘后,COPY HDC数据。FT,像素字需要MONO转GRAY然后再处理。矢量字GDI+直接DrawPath,FT FT_Outline_Render。 如果想优化速度,那就靠gray8层的缓存。 如果是3D绘制,只要提供颜色,合并这些层处理到真正贴图文件。 可以做到完美无缺。
在底层的freetype字体引擎就已带有了Glyph Stroker特性功能,参考http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/include/freetype/ftstroke.h
二值化处理的思想。

Post a comment

非这个主题相关的留言请到:留言本