cocos2dx 3.x 之 字体是怎么渲染的

我们以Label为例,Cocos2d-x 提供的 Label 对象可以使用位图字体,TrueType 字体,系统字体创建。

tips:

enableWrap(true) 当文本长度大于Label设置width后会自动换行

BMFont

BMFont 是一个使用位图字体创建的标签类型,位图字体中的字符由点阵组成。标签中的每一个字符都是一个单独的 Sprite,也就是说精灵的属性(旋转,缩放,着色等)控制都适用于这里的每个字符。创建 BMFont 标签需要两个文件:.fnt 文件和 .png 文件。其中.png 文件是一张合图,你需要显示的字符都应该在这里找到,而.fnt 则是字体的描述文件,它还保存了各个字符的信息。cocos 的 tests 里有提供这种字体,其中bitmapFontTest2.png、bitmapFontTest2.fnt的内容预览如下:

bitmapFontTest2.png
在这里插入图片描述
在这里插入图片描述
描述文件是在BMFontConfiguration::parseConfigFile() 中解析的:

  • page id行指定了字体的.png文件
  • char行 是单个字符的信息
  • kerning行是first字符和second字符之间的水平间距调整的值

而纹理文件(png)加载后由FontAtlas维护。

下边理一下 BMFont的渲染流程:

  1. 加载字体文件,包括字体纹理和基本配置以及单个字符的信息(在纹理中的坐标位置、尺寸、布局时坐标偏移等)。
  2. 拆分字符串,计算每个字符的位置、大小等。这里的字符的大小调整是通过缩放纹理来实现的,所以 BMFont在设置非默认的字体大小会因为缩放导致失真。Label::multilineTextWrap() 函数里会进行自动换行(空白字符换行不拆分单词等)、特殊字符(\n,\b,\r等)的处理。
  3. 在函数 updateQuads() 中通过 SpriteBatchNode 收集所有字符纹理的渲染数据,在渲染流程中绘制出来

TTF

TTF是一种是矢量字体,并不会像BMF字体那样随着字号的调整而出现模糊失真,cocos 使用FreeType 来处理这一块的逻辑,关于矢量字体和FreeType 的介绍和使用可以参考网友的Freetype 介绍和使用
在使用TTF的Label 设置好字体大小(字号)后,会通过_fontAtlas->prepareLetterDefinitions(_utf32Text); 为将要显示的文本的各个字符生成纹理并缓存在_fontAtlas中。所以当字号修改了之后,就需要重新生成对应字号的纹理:

FontAtlas* FontAtlasCache::getFontAtlasTTF(const _ttfConfig* config)
{
	... ...
	
    std::string key;
    char keyPrefix[ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE];
    snprintf(keyPrefix, ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE, useDistanceField ? "df %.2f %d " : "%.2f %d ", config->fontSize, config->outlineSize);
    std::string atlasName(keyPrefix);
    atlasName += realFontFilename;

    auto it = _atlasMap.find(atlasName);	

	... ...
}

可以看到 TTF 的 FontAtlas 缓存的key跟 文件名、字号、描边宽度是相关的

TTF 的渲染流程:
1.通过FontFreeType加载ttf字体
2.根据当前文本、文本的字号、描边的宽度为所有字符生成纹理,后边就跟BMF相同了
3. 拆分字符串,计算每个字符的位置、大小等
4. 在函数 updateQuads() 中通过 SpriteBatchNode 收集所有字符纹理的渲染数据,在渲染流程中绘制出来

TTF 还额外支持描边(outline)和发光效果(glow) 。
描边效果是通过FreeType 重新生成带描边的纹理。发光效果就比较有意思了,用到了距离场(DistanceField)来描述字体,它预处理字体的图像来产生一个有符号距离场。比如字体图像为:在这里插入图片描述
这是一个位图,每个像素的值在0-1之间,其中0代表黑色,1代表白色。我们把它转换成距离场表示,黑色区域中与白色相邻的地方称为边界,记为0.5,黑色的其他像素则根据自身距最近边界距离计算一个值 [0-0.5) 之间,距离越远,值越大,比如距最近边界超过10像素的记为0,小于10的则按照距离计算:0.5-distance/10;白色区域同理,根据自身像素距边界最近的距离计算一个值 (0.5-1] 之间,距最近边界超过10像素的记为0,小于10的则按照距离计算:0.5+distance/10。如果我们将这个距离场直接渲染出来(1表示无色:黑,0表示白色,其他则表现为不同的灰色)则是类似于这样的一张图:在这里插入图片描述
如果我们把小于0.5的像全部丢弃,大于0.5的绘制成白色能得到原始的图形;如果我们将0.5-0.8的像素渲染成指定的颜色,就可以得到指定颜色的外发光效果了。

这里有篇文件讲的比较清楚 讲一讲一种新型的字体渲染方式

系统字体

采用系统字体的Label整体渲染流程与TTF字体比较类似,提供文本,然后通过原生接口生成纹理,再渲染到屏幕上。
生成纹理的接口是:

bool Texture2D::initWithString(const char *text, const FontDefinition& textDefinition)

通用的特殊效果的实现

阴影的实现比较简单,先将自身设置为阴影的颜色、加上阴影的位置偏移 绘制一遍(先绘制所以垫在原字形的下边,代码位于Label::onDrawShadow()内),然后恢复之前的状态,再绘制原文本(代码位于Label::onDraw()内)。粗体的实现与阴影相同:

void Label::enableBold()
{
    if (!_boldEnabled)
    {
        // bold is implemented with outline
        enableShadow(Color4B::WHITE, Size(0.9f, 0), 0);
        // add one to kerning
        setAdditionalKerning(_additionalKerning+1);
        _boldEnabled = true;
    }
}

删除线和下划线是通过一个 _underlineNode = DrawNode::create(); 子节点来绘制。

倾斜 则是通过设置 SkewX 来修改绘制时的投影矩阵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值