← ALL ARTICLES
UI与文本5 MIN READ

文字渲染

全库目录221
本文目录12

字符串里的码点不总对应一张字形。连字、组合符号和从右到左文本先经过塑形,产生字形ID、前进量与偏移,渲染器再查询位图或距离场。排版与采样分开,才能定位缺字和边缘问题。

字符串与字形位置

字符、码点和字形

  • 字符是读者理解的文字单位。
  • Unicode 码点是字符编码空间中的编号。
  • 字形(Glyph)是字体中实际绘制的形状。
  • 字形 ID 是某个字体文件内部的索引。

一个码点可能对应多个字形,一个字形也可能由多个码点共同产生。连字、阿拉伯文连接形式、组合附加符号、Emoji 序列都需要文本塑形(Text Shaping)。

可靠流程是:

text
Unicode Text
  → 双向与脚本分析
  → 字体回退
  → Text Shaping
  → Glyph ID、Advance 和 Offset
  → 换行与对齐
  → Glyph Quad / Path

FreeType 负责读取字体和栅格化字形。HarfBuzz 等塑形库根据脚本规则、Kerning 和字体特性生成字形序列。只按码点查询 FreeType 可以完成简单拉丁文本,无法覆盖完整国际化排版。

字形度量

每个字形常见度量包括:

  • Advance:绘制后光标前进距离;
  • Bearing:字形位图相对基线和光标的偏移;
  • Width/Height:字形图像范围;
  • Ascender/Descender:基线上下的字体范围;
  • Line Gap:推荐行间距。

字形 Quad 的左上角不能只使用当前光标位置。它需要结合边距与基线。连续字形的位置由塑形结果中的前进量与偏移累积,不应自己再重复应用一遍 Kerning。

塑形位置与位图矩形

塑形器输出的位置必须先转换为同一像素尺度。以下约定布局屏幕坐标向右、向下为正,塑形器的y偏移和字体边距向上为正;基线光标为(penX, penY)。位图左上角为:

text
left = penX + xOffset + bitmapLeft
top  = penY - yOffset - bitmapTop
right = left + bitmapWidth
bottom = top + bitmapHeight
penX += xAdvance
penY -= yAdvance

若HarfBuzz字体缩放配置产生26.6定点数,先除以64再参与像素计算;若配置为字体单位,则按字号与units-per-em转换。不能仅凭库名假定输出单位。FreeType的轮廓度量与栅格化位图边距也要区分,最终画位图时使用与这张位图匹配的数据。

例如基线在(10,40),塑形偏移为(2,3),位图边距为(1,8),尺寸为6×9,矩形应为(13,29)到(19,38)。组合重音符号可以前进量为零,却仍有独立位图和偏移;把位图宽度当作前进量会把组合符号挤成两个字符。右到左段落按塑形后的顺序与位移排布,不能再把字形数组机械反转。

字体回退也要保持塑形上下文。遇到缺字时,按脚本段、字素簇和字体覆盖重新塑形适当范围;逐码点临时换字体可能破坏连字与组合。光标、选区和删除操作保留字形到原文本簇的映射,渲染四边形的位置不能直接充当文本索引。

字形的采样表示

位图字体与字形图集

最直接的方法是把字形栅格化成 透明度遮罩,再把许多字形放进 字形图集。每个字形记录图集UV、像素尺寸、Bearing 和 Advance。

图集能让多个字形共享纹理绑定并进入同一批次,但要处理:

  • 字形边缘 Padding,避免双线性过滤串色;
  • 动态加入新字形时的空间分配;
  • 图集满后的淘汰、扩容或分页;
  • 不同字号、Hinting 和语言字符集;
  • 图集更新期间的资源同步;
  • Mipmap 对小字边缘的影响。

CJK 字符集很大,通常按实际使用字形动态缓存,或按语言资源预烘焙多个 图集页。运行时缺字需要显示明确的替代字形,并记录缺失字体和码点。

SDF 和 MSDF

有符号距离场(Signed Distance Field,SDF)保存采样点到字形边界的带符号距离。下面的示例把零距离映射到 0.5,字形内部映射到更大的值。Shader 在这条等值线附近恢复覆盖率,因此同一张 Atlas 可以覆盖更大的缩放范围。

hlsl
float GlyphAlpha(float2 uv)
{
    float distance = GlyphAtlas.Sample(LinearClamp, uv).r;
    float smoothing = max(fwidth(distance) * SdfSoftness, 1e-4);
    return smoothstep(0.5 - smoothing,
                      0.5 + smoothing,
                      distance);
}

fwidth 根据屏幕导数调整过渡宽度,使缩放时边缘保持接近一个像素。若生成器使用相反的距离符号,smoothstep 的方向也要反转。SDF 分辨率、Distance Range 和生成时的归一化方式必须与 Shader 中的阈值一致。

普通单通道 SDF 在尖角处容易变圆。MSDF 把不同边的距离编码到 RGB,通过中值恢复距离,能保留更多尖锐转角。例如采样RGB为(0.2,0.7,0.6),中值为0.6;可用max(min(r,g),min(max(r,g),b))得到该值,再按同一符号和距离范围恢复覆盖。只读红通道会把这个样本判到轮廓外,而中值位于轮廓内,说明RGB保存的不是三种文字颜色。生成时的边着色决定每条边写到哪些通道,错误分组会破坏中值在转角处的重建,运行时扩大平滑宽度只能模糊错误。它仍需要正确的边着色、Padding和采样范围。

极小字号更依赖 Hinting 和像素对齐,位图字形可能比 SDF 清晰。超大标题、描边和发光适合 SDF/MSDF。一个项目可以按字号和平台组合使用。

颜色和混合

字形图集 通常提供 Coverage,文字颜色来自顶点或常量。使用预乘 Alpha 时:

Csrc=CtextαC_{src}=C_{text}\alpha

Blend State 使用 One, OneMinusSrcAlpha。图集覆盖率 属于线性覆盖数据,采样时不做 sRGB 解码;文字颜色则需要遵守 UI 颜色空间和最终显示变换。

LCD Subpixel Rendering 依赖显示器子像素排列和最终合成位置。旋转、缩放、HDR 合成和不同面板都会破坏假设,因此游戏 UI 常使用灰度抗锯齿。

验证方法

测试CJK、阿拉伯文、组合符号和Emoji,显示基线、前进量与字形边界。图集更新与布局失效同时检查,位图与SDF在小字号、旋转和缩放下分别对照,覆盖纹理不做sRGB解码。

编译记录:2026-10-22运行verify.py,从本页片段补最小入口,以DXC的ps_6_0编译GlyphPS通过。结果记录保存工具版本;命令为python 知识库/examples/revision_22_26/verify.py。编译检查不包含完整引擎或GPU输出。

参考实验与证据

CPU参考模型的glyph_quad检查偏移、bearing与屏幕y方向,包括零前进量组合符号的位置。2026-10-22执行python 知识库/examples/revision_22_26/test_reference.py,21项通过,其中2项属于字形矩形。这不是HarfBuzz国际化测试;真实字体塑形、回退、Shader像素结果与GPU时间尚未验证。

相关主题

参考资料

  • FreeType Glyph Conventions: Glyph Metrics,Baseline, pens and layouts与Typographic metrics and bounding boxes;2026-10-22核读,支持基线、bearing和advance的区分。
  • HarfBuzz Shaping and shape plans,Shaping and buffer output;2026-10-22核读,支持字形替换和定位由塑形器处理。
  • Viktor Chlumský,msdfgen,Using a multi-channel distance field一节;2026-10-22核读,支持RGB距离中值与距离范围的还原约定。