小编典典

从 4.1 版开始,OpenGL 中文本渲染的最新技术是什么?

all

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

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

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

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

阅读 112

收藏
2022-06-30

共1个答案

小编典典

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

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

有关该技术,请参阅著名的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 演示可在此处获得。

2022-06-30