截至OpenGL 4.1版本,文本渲染的最新技术是什么?

208

已经有一些关于OpenGL文本渲染的问题,例如:

但大多数讨论的是使用固定功能管线渲染纹理矩形。肯定着色器必须有更好的方法。

我并不真正关心国际化,我的大部分字符串将是绘图刻度标签(日期和时间或仅数字)。但是,这些图形将按屏幕刷新率重新呈现,并且可能会有相当多的文本(不超过几千个字形在屏幕上,但足够使用硬件加速的布局)。

使用现代OpenGL进行文本渲染的推荐方法是什么?(引用使用该方法的现有软件是其有效性的良好证据)

  • 可接受位置、方向和字符序列并发出纹理矩形的几何着色器
  • 渲染矢量字体的几何着色器
  • 与上述方法相同,但使用曲面细分着色器
  • 使用计算着色器进行字体光栅化

10
我现在主要关注于OpenGL ES,对于最新的技术状况无法回答。但是,通过使用GLU分解器对TrueType字体进行分解,并将其作为几何图形通过旧的固定功能管道提交,同时在CPU上计算字距(kerning),即使在近十年前也能获得良好的视觉效果和优秀的性能反应。因此,你不仅仅只能通过着色器找到“更好”的方法(当然,这取决于你的标准)。FreeType可以生成贝塞尔曲线字符边界以及字距信息,因此您可以在运行时直接从TTF中实时处理。 - Tommy
QML2(Qt5的一部分)在渲染文本时使用OpenGL和距离场进行了一些有趣的技巧:http://blog.qt.digia.com/blog/2012/08/08/native-looking-text-in-qml-2/ - mlvljr
9
“离题讨论”是Stack Overflow的诅咒。真的吗? - Nikos Yotis
1
一个更加朴素的“如何做到”版本:https://dev59.com/wWox5IYBdhLWcg3w_JLd - Ciro Santilli OurBigBook.com
@Peter:如果你想要一个类似的问题来涵盖4.1版本之后的版本,请作为一个新问题提出,并包含一个回指到这个问题的链接(同时也需要命名一个特定的版本号,因为移动的目标是不好的)。 - Ben Voigt
显示剩余3条评论
5个回答

211

如果您需要呈现的字符数不止一打,那么呈现轮廓线仍然是“不可取”的,因为每个字符所需的顶点数来近似曲率太多了。尽管已经有方法在像素着色器中评估贝塞尔曲线,但这些方法无法轻松地进行反走样处理,而使用距离映射纹理四边形则非常容易实现。而且,在着色器中评估曲线仍然比必要的计算代价更高。

“快速”和“质量”之间最好的折衷方案仍然是使用带有符号距离场纹理的纹理四边形。它比使用普通纹理四边形稍微慢一点,但并不多。另一方面,质量则完全不同。结果真的很惊人,速度也非常快,例如添加发光效果也非常容易。此外,如果需要,该技术可以很好地降级到旧硬件。

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

这种技术在概念上类似于隐式表面(代谢球等)的工作方式,尽管它不会生成多边形。它完全在像素着色器中运行,并将从纹理采样得到的距离作为距离函数。选择阈值以上的所有内容(通常为0.5)为“内部”,其他所有内容为“外部”。在最简单的情况下,在10年前的非着色器可用硬件上,将alpha测试阈值设置为0.5将完成这个过程(虽然没有特殊效果和反走样)。如果要增加字体的粗度(模拟粗体),则稍微减小阈值即可实现,而无需修改任何一行代码(只需更改“font_weight”统一变量)。对于发光效果,只需将一个阈值以上的所有内容视为“内部”,并将另一个(较小的)阈值以上的所有内容视为“外部,但具有发光效果”,并在两者之间进行LERP。反走样处理方法也类似。

通过使用8位带符号距离值而不是单个位,该技术每个维度将您的纹理映射的有效分辨率增加16倍(使用黑白以外的所有可能的色调,因此使用相同的存储空间我们获得256倍的信息)。但即使您放大到16倍以上,结果仍然看起来相当可接受。长直线最终会变得有点波浪,但没有典型的“块状”采样伪像。

您可以使用几何着色器从点生成四边形(减少总线带宽),但是说实话,收益相对较小。就像在GPG8中描述的那样,对于实例化字符渲染也是如此。仅当您有大量文本需要绘制时,才会摊销实例化的开销。在我看来,这种收益与增加的复杂性和不可降级性之间没有关系。此外,您要么受到常数寄存器数量的限制,要么必须从纹理缓冲区对象中读取,这对于高速缓存一致性来说是非最优的(而意图是进行优化!)。
一个简单的旧式顶点缓冲区只要稍微提前安排上传时间,就可以和任何在过去15年内建造的硬件一样快(甚至更快)。而且,它不限于字体中的任何特定字符数,也不限于要呈现的特定字符数。
如果您确定字体中没有超过256个字符,那么纹理数组可能值得考虑,以类似于从几何着色器的点生成四边形的方式剥离总线带宽。使用数组纹理时,所有四边形的纹理坐标都具有相同的常量s和t坐标,并且仅在r坐标上有所不同,该坐标等于要呈现的字符索引。
但是和其他技术一样,预期的收益很小,而且不兼容以前的硬件。
Jonathan Dummer提供了一个方便的工具来生成距离纹理:说明页面 更新: 正如最近在“可编程顶点拉动”(D. Rákos,“OpenGL Insights”,pp. 239)中指出的那样,在最新一代GPU上从着色器中以编程方式拉取顶点数据与使用标准固定功能进行相同操作没有显着的额外延迟或开销。
此外,最新一代GPU具有越来越多合理大小的通用L2缓存(例如nvidia Kepler上的1536kiB),因此可以预期从缓冲区纹理中随机偏移量拉取四边形角落时的不连贯访问问题会更少成为问题。
这使得从缓冲区纹理中提取常量数据(例如四边形尺寸)的想法更具吸引力。因此,假设实现可以将PCIe和内存传输以及GPU内存最小化,采用此方法:
  • 只上传每个要显示的字符的字符索引(每个索引只有一个),作为顶点着色器的唯一输入,将该索引和gl_VertexID传递下去,在几何着色器中将其扩大到4个点,仍然保留字符索引和顶点ID作为唯一属性,并通过变换反馈捕获它。
  • 这将很快,因为只有两个输出属性(GS的主要瓶颈),在两个阶段中都接近于“无操作”。
  • 绑定一个缓冲纹理,其中包含每个字体字符的纹理四边形顶点相对于基准点的位置(基本上是“字体度量”)。此数据可以通过仅存储底部左侧顶点的偏移量,并编码轴对齐框的宽度和高度(假设是半浮点数,则每个四边形的大小为8个字节的常量缓冲区 – 一个典型的256个字符的字体可以完全放入2kiB的L1缓存中)。
  • 设置一个基线uniform。
  • 绑定一个带有水平偏移量的缓冲纹理。这些可能甚至可以在GPU上计算,但在CPU上执行这种操作更容易,更有效,因为它是一个严格的顺序操作,而不是完全微不足道的(考虑字距)。此外,它需要另一个反馈传递,这将是另一个同步点。
  • 从反馈缓冲区中渲染先前生成的数据,顶点着色器从缓冲对象中提取基准点的水平偏移量和角落顶点的偏移量(使用原始ID和字符索引)。提交的顶点的原始ID现在是我们的“基本ID”(记住GS将顶点转换为四边形)。

这样,理想情况下,可以将所需的顶点带宽降低75%(摊销),但仅能渲染单行。如果要在一个绘制调用中渲染多行,则需要将基线添加到缓冲纹理中,而不是使用uniform(使带宽收益较小)。

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

已经在上面提到过“OpenGL Insights”,值得一提的是Stefan Gustavson的章节“2D Shape Rendering by Distance Fields”详细介绍了距离场渲染。
更新2016年:
与此同时,出现了几种额外的技术,旨在消除在极端放大时会变得令人不适的角落圆角伪影。
一种方法简单地使用伪距离场而不是距离场(区别在于距离是到实际轮廓而不是到轮廓或一个虚拟线突出边缘的最短距离)。这样做有些更好,速度相同(使用相同的着色器),使用相同数量的纹理内存。
另一种方法使用三通道纹理中的三个值的中间值详细实现可在github上获取。这旨在改善以前用来解决问题的and-or hack。质量好,略微,几乎不会注意到,慢,但使用的纹理内存增加了三倍。此外,额外的效果(如光晕)更难以正确地实现。
最后,将构成字符的实际贝塞尔曲线存储,并在片段着色器中评估已变得可行,性能略低(但不足以成为问题),即使在最高放大倍数下也具有惊人的效果。
使用此技术实时渲染大型PDF的WebGL演示可在此处找到。

1
它们看起来相当不错(即使使用天真的过滤并且没有mipmapping,因为您有非常小的纹理并且数据很好地插值)。就个人而言,在许多情况下,我认为它们甚至比“真实”物品看起来更好,因为没有像提示那样的奇怪现象,这经常会产生我认为是“奇怪”的东西。例如,较小的文本不会突然变粗,也不会弹出到像素边界-这些效果通常在“真实”字体中看到。可能有历史原因(1985年黑白显示器),但今天,我无法理解为什么必须这样。 - Damon
2
非常好的工作和外观,感谢分享!对于那些想要HLSL frag着色器源代码的人,请参见此处。您可以通过将clip(...)行替换为if (text.a < 0.5) {discard;}(或text.a < threshold)来适应GLSL。希望有所帮助。 - Engineer
1
感谢更新。我希望我能再次点赞。 - Ben Voigt
2
@NicolBolas:您似乎没有仔细阅读。这两个问题在答案中都有解释。Kepler被作为“最新一代”示例提供,没有第二次遍历(并且已经解释了原因),我声明我不认为假设的节省带宽技术明显更快或值得麻烦。然而,信仰什么都不能证明——人们必须亲身尝试才能知道(我没有尝试过,因为我认为绘制“正常”数量的文本不管哪种方式都不是瓶颈)。即使如此,在某些情况下,当一个人在带宽方面处于绝望状态并且需要处理“异常”数量的文本时,这仍然可能是值得的。 - Damon
3
@NicolBolas:你说得对,那个句子有点误导。在前一段中,我写道“甚至可以在 GPU 上生成它,但这需要反馈和……”-- 但接着错误地继续写“从反馈缓冲区生成的数据”。我会纠正这个错误。实际上,我会在周末重写整个内容,以便更加明确。 - Damon
显示剩余7条评论

15

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

GLyphy与其他基于SDF的OpenGL渲染器最大的区别在于,大多数其他项目将SDF采样到纹理中。这会导致轮廓失真和低质量等常见问题。相反,GLyphy使用实际向量表示提交给GPU的SDF。这可以实现非常高质量的渲染。

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


3
对于对GLyphy有兴趣的人,建议观看作者在Linux.conf.au 2014上的演讲:https://www.youtube.com/watch?v=KdNxR5V7prk。 - Fizz

14

目前最常用的技术仍然是纹理四边形。然而在2005年,LORIA开发了一种称为矢量纹理的技术,即将矢量图形渲染成基本元素上的纹理。如果使用这种技术将TrueType或OpenType字体转换为矢量纹理,则可以获得以下结果:

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


2
你知道有没有使用这种技术的实现吗? - luke
2
没有(指成熟的产品),但是Kilgard的论文(链接见下文)对其进行了简要批评,我的总结是:目前还不可行。这个领域已经有更多的研究了;Kilgard引用的最近的工作包括http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf和https://uwspace.uwaterloo.ca/handle/10012/4262。 - Fizz

11
我很惊讶马克·基尔加德的宝贝NV_path_rendering(NVpr)没有被上面提到。尽管其目标比字体渲染更为一般,但它也可以从字体中呈现文本并进行字距调整。它甚至不需要OpenGL 4.1,但目前只是供应商/ Nvidia扩展。它基本上使用glPathGlyphsNV将字体转换成路径,该路径依赖于freetype2库来获取度量等信息。然后,您还可以使用glGetPathSpacingNV访问字距信息,并使用NVpr的一般路径呈现机制显示使用路径-"转换"字体的文本。(我用引号括起来,因为没有真正的转换,曲线就是原样使用。)
遗憾的是,记录的NVpr字体能力演示并不特别令人印象深刻。(也许有人应该按照更酷的SDF演示的思路制作一个...)
2011年NVpr API演示中关于字体的部分从这里开始,并在下一部分继续;遗憾的是这个演示被分成了几个部分。
更多关于NVpr的一般材料:
  • Nvidia NVpr hub,但着陆页上的一些内容已经不是最新的了
  • Siggraph 2012 论文介绍了路径渲染方法“stencil, then cover”(StC)的脑部,并简要解释了竞争技术如Direct2D的工作原理。与字体相关的部分已被降级到论文的附件中。还有一些额外的视频/演示
  • GTC 2014 演讲更新状态;简而言之:现在被Google的Skia支持(Nvidia在2013年和2014年贡献了代码),Skia又被用于Google Chrome和[独立于Skia,我认为]Adobe Illustrator CC 2014的测试版中
  • OpenGL扩展注册表中的官方文档
  • USPTO已授予Kilgard / Nvidia至少四项与NVpr相关的专利,您应该意识到这一点,以防您想自己实施StC:US8698837US8698808US8704830US8730253。请注意,还有大约17个与此相关的USPTO文件作为“也发布为”,其中大多数是专利申请,因此完全有可能从中授予更多专利。
而且,由于在我的回答之前,该页面上没有任何与“模板”相关的内容,因此似乎参与该页面的SO社区子集虽然人数众多,但对于无镶嵌、基于模板缓冲区的路径/字体渲染方法并不了解。Kilgard在opengl论坛上有一个类似FAQ的帖子,可以阐明无镶嵌路径渲染方法与普通3D图形的区别,尽管它们仍在使用[GP]GPU。(NVpr需要一个支持CUDA的芯片。)
从历史的角度来看,Kilgard也是经典{{link2:“基于简单OpenGL的纹理映射文本API”,SGI,1997}}的作者,不应将其与2011年推出的基于模板的NVpr混淆。
大多数,如果不是所有最近在本页面上讨论的方法,包括基于模板的方法如NVpr或基于SDF的方法如GLyphy(我在这里不再深入讨论,因为其他答案已经涵盖了),然而有一个限制:它们适用于传统(~100 DPI)显示器上的大文本显示,无论缩放级别如何都没有锯齿,并且它们看起来很好,即使在高DPI,类似Retina的显示器上也是如此。 然而,它们并不能完全提供Microsoft的Direct2D+DirectWrite所提供的,即在主流显示器上对小字形进行提示。 (有关提示的视觉调查,请参见this typotheque page。更深入的资源是on antigrain.com。)
我不知道有什么开放的并能像微软一样提示的产品化基于OpenGL的东西。(我承认对苹果的OS X GL/Quartz内部一无所知,因为据我所知,苹果没有公布他们是如何做基于GL的字体/路径渲染的。看起来,与MacOS 9不同,OS X根本不进行提示,这使得一些人感到恼火。) 无论如何,INRIA的Nicolas P. Rougier撰写了一篇2013年的研究论文,讨论通过OpenGL着色器提示的问题。如果您需要从OpenGL进行提示,则阅读此论文可能非常有价值。尽管看起来像freetype这样的库已经在提示方面完成了所有工作,但实际上却不是这样,原因如下(我引用自该论文):
FreeType库可以在RGB模式下使用亚像素抗锯齿来光栅化字形。然而,这只解决了问题的一半,因为我们还想实现亚像素定位以准确放置字形。在分数像素坐标处显示纹理四边形不能解决问题,因为它只导致整像素级别上的纹理插值。相反,我们想在亚像素域中实现精确移位(介于0和1之间)。这可以在片段着色器中完成[...]。
解决方案并不是非常简单,所以我不打算在这里进行解释。(该论文是开放获取的。)

我从Rougier的论文中学到了一件事情(Kilgard似乎没有考虑过),即字体权威(Microsoft+Adobe)创建了不止一种字距规范方法。旧的方法基于所谓的kern表,由freetype支持。新的方法称为GPOS,只有像HarfBuzz或pango这样的新字体库才支持它。由于NVpr似乎不支持这些库中的任何一个,因此对于某些新字体,NVpr可能无法直接使用字距;根据this forum discussion,这些字体在某些情况下确实存在。

最后,如果你需要进行复杂文本布局(CTL),似乎OpenGL并没有相应的库可用(而DirectWrite则可以处理CTL)。有一些开源库,比如HarfBuzz,可以渲染CTL,但我不知道怎样通过OpenGL将它们与基于模板的方法很好地结合使用。你可能需要编写粘合剂代码来提取重新调整的轮廓,并将它们作为路径输入到NVpr或基于SDF的解决方案中。


4
我没有提到NV_path_rendering,因为它是一个扩展,而且更糟糕的是,它是专有的供应商。我通常只尝试回答普遍适用的技术问题。 - datenwolf
1
嗯,我在某种程度上同意这一点。该方法本身(“模板,然后覆盖”)在OpenGL中直接实现实际上并不难,但是如果以天真的方式这样做,则会有很高的命令开销,就像之前基于模板的尝试一样。Skia [通过Ganesh]曾经尝试过基于模板的解决方案,但是根据Kilgrad的说法放弃了它。 Nvidia在下面一层使用CUDA功能实现它的方式使其执行。您可以尝试使用整个EXT / ARB扩展来自己“维护”StC。但是请注意,Kilgard / Nvidia对NVpr有两项专利申请。 - Fizz

3
我认为你最好的选择是研究带有OpenGL后端的cairo graphics
当我使用3.3核心开发原型时唯一遇到的问题是在OpenGL后端使用了不推荐使用的函数。那是1-2年前的事情,所以情况可能已经得到改善...
无论如何,我希望将来桌面OpenGL图形驱动程序将实现OpenVG。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接