205

关于 OpenGL 中的文本渲染已经有很多问题了,比如:

但主要讨论的是使用固定功能管道渲染纹理四边形。当然,着色器必须做出更好的方法。

我并不真正关心国际化,我的大部分字符串都是绘图刻度标签(日期和时间或纯数字)。但是绘图将以屏幕刷新率重新渲染,并且可能会有相当多的文本(屏幕上不超过几千个字形,但足以让硬件加速布局很好)。

使用现代 OpenGL 进行文本渲染的推荐方法是什么?(使用该方法引用现有软件很好地证明了它运行良好)

  • 接受例如位置和方向以及字符序列并发出纹理四边形的几何着色器
  • 渲染矢量字体的几何着色器
  • 如上所述,但使用曲面细分着色器
  • 用于进行字体光栅化的计算着色器
4

5 回答 5

209

除非您总共只渲染十几个字符,否则渲染轮廓仍然是“不行的”,因为每个字符需要顶点数来近似曲率。尽管已经有一些方法可以在像素着色器中评估贝塞尔曲线,但这些方法不容易抗锯齿,使用距离贴图纹理四边形是微不足道的,并且在着色器中评估曲线的计算成本仍然比必要的高得多。

“快速”和“质量”之间的最佳权衡仍然是具有带符号距离场纹理的纹理四边形。它使用普通的普通纹理四边形要慢得多,但不会慢很多。另一方面,质量则完全不同。结果确实令人惊叹,它以最快的速度获得,并且发光等效果也很容易添加。此外,如果需要,该技术可以很好地降级到旧硬件。

有关该技术,请参阅著名的Valve 论文

该技术在概念上类似于隐式曲面(元球等)的工作方式,尽管它不会生成多边形。它完全在像素着色器中运行,并将从纹理中采样的距离作为距离函数。高于所选阈值(通常为 0.5)的所有内容都是“输入”,其他所有内容都是“输出”。在最简单的情况下,在使用 10 年且不支持着色器的硬件上,将 alpha 测试阈值设置为 0.5 就可以做到这一点(尽管没有特殊效果和抗锯齿)。
如果想给字体增加一点权重(假粗体),稍微小一点的阈值就可以解决问题,而无需修改一行代码(只需更改您的“font_weight”制服)。对于发光效果,人们只需将高于一个阈值的所有内容视为“入”,将高于另一个(较小)阈值的所有内容视为“出,但在发光”,以及两者之间的 LERP。抗锯齿的工作原理类似。

通过使用 8 位有符号距离值而不是单个位,该技术将纹理贴图在每个维度上的有效分辨率提高了 16 倍(而不是使用黑色和白色,而是使用所有可能的阴影,因此我们有 256 倍的信息使用相同的存储)。但即使你放大到远远超过 16 倍,结果看起来仍然可以接受。长直线最终会变得有点摇摆不定,但不会出现典型的“块状”采样伪影。

您可以使用几何着色器从点生成四边形(减少总线带宽),但老实说,收益相当微不足道。GPG8 中描述的实例化字符渲染也是如此。仅当您要绘制大量文本时,才会分摊实例化的开销。在我看来,这些收益与增加的复杂性和不可降级性无关。另外,您要么受到常量寄存器数量的限制,要么必须从纹理缓冲区对象中读取,这对于缓存一致性来说不是最佳的(并且目的是从一开始就进行优化!)。
如果您提前一点时间安排上传,一个简单、普通的旧顶点缓冲区同样快(可能更快),并且将在过去 15 年构建的每个硬件上运行。而且,它不限于字体中任何特定数量的字符,也不限于要呈现的特定数量的字符。

如果您确定您的字体中没有超过 256 个字符,则可能值得考虑使用纹理数组以类似于从几何着色器中的点生成四边形的方式剥离总线带宽。使用数组纹理时,所有四边形的纹理坐标具有相同、常数st坐标,只是坐标不同r,等于要渲染的字符索引。
但与其他技术一样,预期收益是微不足道的,代价是与上一代硬件不兼容。

Jonathan Dummer 有一个方便的工具用于生成距离纹理:描述页面

更新:
正如最近在可编程顶点拉取(D. Rákos,“OpenGL Insights”,第 239 页)中指出的那样,在最新一代 GPU 上以编程方式从着色器中拉取顶点数据并没有显着的额外延迟或开销,与使用标准固定功能做同样的事情相比。
此外,最新一代的 GPU 具有越来越多的合理大小的通用 L2 缓存(例如,在 nvidia Kepler 上为 1536kiB),因此当从缓冲区纹理中提取四角的随机偏移量时,可能会出现不连贯的访问问题。问题。

这使得从缓冲区纹理中提取恒定数据(例如四边形大小)的想法更具吸引力。因此,假设实现可以通过以下方法将 PCIe 和内存传输以及 GPU 内存减少到最低限度:

  • 只上传一个字符索引(每个要显示的字符一个)作为唯一输入到传递该索引和的顶点着色器,并将其gl_VertexID放大到几何着色器中的 4 个点,仍然具有字符索引和顶点 id(这将“gl_primitiveID 在顶点着色器中可用”)作为唯一属性,并通过变换反馈捕获它。
  • 这会很快,因为只有两个输出属性(GS 中的主要瓶颈),否则在两个阶段都接近“无操作”。
  • 绑定一个缓冲区纹理,其中包含字体中每个字符的纹理四边形相对于基点的顶点位置(这些基本上是“字体度量”)。通过仅存储左下角顶点的偏移量并编码轴对齐框的宽度和高度(假设半浮点数,这将是每个字符 8 字节的常量缓冲区,此数据可以压缩为每个四边形 4 个数字——一个典型的 256 个字符的字体可以完全适应 2kiB 的 L1 缓存)。
  • 为基线设置制服
  • 绑定具有水平偏移的缓冲区纹理。这些甚至可以在 GPU 上计算,但在 CPU 上这种事情更容易和更有效,因为它是一个严格的顺序操作,而且一点也不微不足道(想想字距调整)。此外,它还需要另一个反馈通道,这将是另一个同步点。
  • 从反馈缓冲区渲染之前生成的数据,顶点着色器从缓冲区对象中提取基点的水平偏移和角顶点的偏移(使用图元 id 和字符索引)。提交顶点的原始顶点 ID 现在是我们的“原始 ID”(记住 GS 将顶点变成了四边形)。

像这样,理想情况下可以将所需的顶点带宽减少 75%(摊销),尽管它只能渲染一条线。如果希望能够在一次绘制调用中渲染多条线,则需要将基线添加到缓冲区纹理,而不是使用统一(使带宽增益更小)。

然而,即使假设减少了 75%——因为显示“合理”文本数量的顶点数据只有 50-100kiB 左右(实际上为零到 GPU 或 PCIe 总线)——我仍然怀疑增加的复杂性和失去向后兼容性是否值得麻烦。将零减少 75% 仍然只是零。诚然,我没有尝试过上述方法,需要更多的研究才能做出真正合格的陈述。但是,除非有人能够展示真正惊人的性能差异(使用“正常”数量的文本,而不是数十亿个字符!),我的观点仍然是对于顶点数据,一个简单的、普通的旧顶点缓冲区是有理由的足够好被视为“最先进的解决方案”的一部分。它简单明了,有效,而且效果很好。

已经参考了上面的“ OpenGL Insights ”,还值得指出的是 Stefan Gustavson 的“2D Shape Rendering by Distance Fields”一章,它非常详细地解释了距离场渲染。

2016 年更新:

同时,存在一些旨在去除在极端放大倍率下变得令人不安的圆角伪影的附加技术。

一种方法简单地使用伪距离场而不是距离场(不同之处在于距离不是到实际轮廓的最短距离,而是到轮廓或从边缘突出的假想线的最短距离)。这稍微好一些,并且使用相同数量的纹理内存以相同的速度(相同的着色器)运行。

另一种方法在三通道纹理细节中使用三的中位数,并在 github 上提供实现。这旨在改进以前用于解决该问题的 and-or hack。质量好,稍慢,几乎不明显,速度较慢,但​​使用的纹理内存是原来的三倍。此外,额外的效果(例如发光)更难正确处理。

最后,存储构成字符的实际贝塞尔曲线,并在片段着色器中评估它们已变得实用,性能稍差(但不是问题),即使在最高放大倍率下也能获得惊人的结果。
使用此技术实时呈现大型 PDF 的 WebGL 演示可在此处获得。

于 2011-03-11T21:16:40.840 回答
15

http://code.google.com/p/glyphy/

GLyphy 和其他基于 SDF 的 OpenGL 渲染器之间的主要区别在于大多数其他项目将 SDF 采样到纹理中。这具有采样所具有的所有常见问题。IE。它扭曲了轮廓并且质量低下。GLyphy 使用提交给 GPU 的实际向量来表示 SDF。这导致非常高质量的渲染。

缺点是代码适用于带有 OpenGL ES 的 iOS。我可能会制作一个 Windows/Linux OpenGL 4.x 端口(但希望作者会添加一些真实的文档)。

于 2013-11-08T19:11:46.143 回答
14

最普遍的技术仍然是纹理四边形。然而,在 2005 年,LORIA 开发了一种称为矢量纹理的东西,即将矢量图形渲染为基元上的纹理。如果使用它来将 TrueType 或 OpenType 字体转换为矢量纹理,您会得到:

http://alice.loria.fr/index.php/publications.html?Paper=VTM@2005

于 2011-03-10T17:02:53.480 回答
10

我很惊讶 Mark Kilgard 的宝贝NV_path_rendering (NVpr) 没有被上述任何人提及。虽然它的目标比字体渲染更通用,但它也可以从字体和字距渲染文本。它甚至不需要 OpenGL 4.1,但目前它只是供应商/Nvidia 的扩展。它基本上将字体转换为路径,使用glPathGlyphsNV它依赖于 freetype2 库来获取指标等。然后您还可以glGetPathSpacingNV使用 NVpr 的通用路径渲染机制访问字距调整信息并使用路径“转换”字体显示文本。(我把它放在引号中,因为没有真正的转换,曲线按原样使用。)

不幸的是,录制的 NVpr 字体功能演示并不是特别令人印象深刻。(也许有人应该按照可以在 intertubes 上找到的更时髦的 SDF 演示制作一个...)

字体部分的 2011 NVpr API 演示演讲从这里开始,在下一部分继续;令人遗憾的是,该演示文稿是如何拆分的。

更多关于 NVpr 的通用资料:

  • Nvidia NVpr hub,但登录页面上的某些材料不是最新的
  • Siggraph 2012 年关于路径渲染方法大脑的论文,称为“模板,然后覆盖”(StC);该论文还简要解释了 Direct2D 等竞争技术的工作原理。与字体相关的部分已归入论文的附件。还有一些附加功能,例如视频/演示
  • 更新状态的GTC 2014 演示文稿;简而言之:它现在得到 Google 的 Skia 的支持(Nvidia 在 2013 年末和 2014 年贡献了代码),而后者又用于 Google Chrome 和 [我认为独立于 Skia] 在 Adob​​e Illustrator CC 2014 的测试版中
  • OpenGL 扩展注册表中的官方文档
  • USPTO 已向 Kilgard/Nvidia 授予至少四项与 NVpr 相关的专利,如果您想自己实施 StC,您可能应该知道其中的专利:US8698837US8698808US8704830US8730253。请注意,还有 17 份与此相关的 USPTO 文件“也发布为”,其中大部分是专利申请,因此完全有可能从中授予更多专利。

而且由于“模板”这个词在我回答之前没有在这个页面上产生任何点击,看起来参与这个页面的 SO 社区的子集,尽管数量很多,但并不知道无镶嵌、模板缓冲区-一般基于路径/字体渲染的方法。Kilgard在 opengl 论坛上有一个类似常见问题的帖子,它可能说明无曲面细分路径渲染方法与沼泽标准 3D 图形的不同之处,即使它们仍在使用 [GP] GPU。(NVpr 需要支持 CUDA 的芯片。)

从历史的角度来看,Kilgard 还是经典的“A Simple OpenGL-based API for Texture Mapped Text”的作者,SGI,1997,不应与 2011 年首次亮相的基于模板的 NVpr 混淆。


本页上讨论的大多数(如果不是全部)最近的方法,包括基于模板的方法(如 NVpr)或基于 SDF 的方法(如 GLyphy)(我在这里不再讨论,因为其他答案已经涵盖了它)有一个限制:它们是适合在传统(~100 DPI)显示器上显示大文本,在任何缩放级别上都没有锯齿,即使在小尺寸的情况下,它们在高 DPI、类似视网膜的显示器上也看起来不错。然而,它们并没有完全提供 Microsoft 的 Direct2D+DirectWrite 为您提供的功能,即在主流显示器上提示小字形。(对于一般提示的视觉调查,请参见这个typotheque页面。更深入的资源在antigrain.com上。)

我不知道任何开放和产品化的基于 OpenGL 的东西可以做微软目前可以做的事情。(我承认对 Apple 的 OS X GL/Quartz 内部结构一无所知,因为据我所知,Apple 尚未公布他们如何进行基于 GL 的字体/路径渲染。似乎 OS X 与 MacOS 9 不同,没有做提示,这让一些人很恼火。)无论如何,有一篇 2013 年的研究论文解决了 INRIA 的 Nicolas P. Rougier 编写的通过 OpenGL 着色器进行提示的问题;如果您需要从 OpenGL 进行提示,它可能值得一读。虽然看起来像 freetype 这样的库在提示方面已经完成了所有工作,但实际上并非如此,原因如下,我从论文中引用:

FreeType 库可以在 RGB 模式下使用亚像素抗锯齿对字形进行光栅化。然而,这只是问题的一半,因为我们还希望实现亚像素定位以准确放置字形。在分数像素坐标处显示纹理四边形并不能解决问题,因为它只会导致整个像素级别的纹理插值。相反,我们希望在子像素域中实现精确的偏移(0 到 1 之间)。这可以在片段着色器中完成 [...]。

解决方案并不完全是微不足道的,所以我不打算在这里解释它。(这篇论文是开放获取的。)


我从 Rougier 的论文中学到的另一件事(Kilgard 似乎没有考虑到这一点)是(Microsoft + Adob​​e)的字体功能创建的不是一种而是两种字距调整规范方法。旧的基于所谓的kern表,并且由 freetype 支持。新字体称为 GPOS,它仅受自由软件世界中的 HarfBuzz 或 pango 等较新的字体库支持。由于 NVpr 似乎不支持这些库中的任何一个,因此对于某些新字体,字距调整可能不适用于 NVpr 开箱即用;根据这个论坛的讨论,其中一些显然是在野外。

最后,如果您需要执行复杂的文本布局 (CTL),您目前似乎对 OpenGL 不走运,因为似乎不存在基于 OpenGL 的库。(另一方面,DirectWrite 可以处理 CTL。)有像 HarfBuzz 这样的开源库可以渲染 CTL,但我不知道如何让它们正常工作(如使用基于模板的方法)通过OpenGL。您可能必须编写胶水代码来提取重新塑造的轮廓并将它们作为路径输入到基于 NVpr 或 SDF 的解决方案中。

于 2014-08-07T15:03:34.417 回答
3

我认为您最好的选择是使用 OpenGL 后端查看cairo 图形。

在开发具有 3.3 核心的原型时,我遇到的唯一问题是在 OpenGL 后端中不推荐使用函数。那是1-2年前的事了,所以情况可能会有所改善......

无论如何,我希望将来桌面opengl图形驱动程序能够实现OpenVG。

于 2011-03-10T19:11:46.840 回答