从版本4.1开始,OpenGL中的文本渲染技术是什么? [关闭]

问题描述 投票:188回答:5

关于OpenGL中的文本呈现已经有很多问题,例如:

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

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

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

  • 几何着色器接受例如位置和方向以及字符序列并发出纹理四边形
  • 渲染矢量字体的几何着色器
  • 如上所述,但改为使用曲面细分着色器
  • 用于进行字体光栅化的计算着色器
opengl text glsl shader opengl-4
5个回答
194
投票

除非您仅渲染十几个字符,否则渲染轮廓仍然是“不行”,因为每个字符需要近似曲率的顶点数量。虽然已经有了在像素着色器中评估贝塞尔曲线的方法,但是这些方法不容易被抗锯齿,这在使用距离图纹理四边形时是微不足道的,并且在着色器中评估曲线在计算上仍然比必要的贵得多。

“快速”和“质量”之间的最佳折衷仍然是具有签名距离场纹理的纹理四边形。它比使用普通的普通纹理四边形略慢,但不是那么多。另一方面,质量完全不同。结果非常令人惊叹,它可以尽可能快地获得,并且发光等效果也很容易添加。此外,如果需要,该技术可以很好地降级到较旧的硬件。

请参阅着名的Valve paper技术。

该技术在概念上类似于隐式曲面(元球等)的工作方式,尽管它不会生成多边形。它完全在像素着色器中运行,并将纹理采样的距离作为距离函数。超过所选阈值(通常为0.5)的所有内容都是“in”,其他所有内容都是“out”。在最简单的情况下,在具有10年历史的非着色器硬件上,将alpha测试阈值设置为0.5将完成相同的操作(尽管没有特殊效果和抗锯齿)。 如果想要为字体添加更多的权重(人造粗体),稍微小一点的阈值就可以完成这一操作,而无需修改单行代码(只需更改“font_weight”统一)。对于发光效果,只需将一个阈值以上的所有内容视为“in”,将另一个(较小)阈值之上的所有内容视为“out,but in glow”,以及两者之间的LERP。抗锯齿的工作方式类似。

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

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

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

Jonathan Dummer有一个方便的工具用于生成距离纹理:gl_VertexID

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

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

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

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

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

在上面已经引用了“available at github”之后,值得指出Stefan Gustavson撰写的“距离场的2D形状渲染”一章,它详细解释了距离场渲染。

2016年更新:

同时,存在若干附加技术,其旨在消除在极端放大率下变得令人不安的拐角圆角伪影。

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

另一种方法在三通道纹理细节和实现has become practical中使用三个中值。这旨在改进以前用于解决该问题的和/或黑客。良好的质量,略微,几乎不明显,较慢,但使用三倍的纹理内存。此外,额外的效果(如发光)更难以正确。

最后,存储构成字符的实际贝塞尔曲线,并在片段着色器here中进行评估,性能稍差(但不是很多,这是一个问题),即使在最高放大倍率下也能获得惊人的效果。 WebGL演示使用这种技术实时渲染大型PDF可用http://code.google.com/p/glyphy/


14
投票

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

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

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


12
投票

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

glPathGlyphsNV


7
投票

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

遗憾的是,much snazzier SDF demo并不是特别令人印象深刻。 (也许有人应该在starts here上制作一个可以在intertubes上找到的...)

2011年NVpr API演示文稿谈论字体部分next part并继续在Nvidia NVpr hub;这个演示文稿如何拆分有点不幸。

关于NVpr的更一般材料:

由于“模板”这个词在我的答案之前没有在这个页面上产生任何点击,所以看起来参与此页面的SO社区的子集,尽管数量众多,却没有意识到无曲面,模板缓冲 - 一般来说,基于路径/字体渲染的方法。 Kilgard有一个"A Simple OpenGL-based API for Texture Mapped Text", SGI, 1997,它可以说明无曲面路径渲染方法与沼泽标准3D图形的区别,即使它们仍在使用[GP] GPU。 (NVpr需要一个支持CUDA的芯片。)

从历史角度来看,Kilgard也是经典this typotheque page的作者,不应该与2011年推出的基于模板的NVpr混淆。


大多数(如果不是全部)最近在本页讨论的方法,包括基于模板的方法,如NVpr或基于SDF的方法,如GLyphy(我在这里没有进一步讨论,因为其他答案已经涵盖了它)有一个限制:它们是适用于传统(~100 DPI)显示器上的大文本显示,在任何级别的缩放都没有锯齿,并且在高DPI,视网膜状显示器上它们看起来也很好,即使尺寸很小。但是它们没有完全提供Microsoft的Direct2D + DirectWrite为您提供的功能,即在主流显示器上暗示小字形。 (有关暗示的视觉调查一般见on antigrain.com。更深入的资源是annoys some people。)

我不知道任何开放和产品化的基于OpenGL的东西,可以做微软目前的暗示。 (我承认对Apple的OS X GL / Quartz内部构件一无所知,因为据我所知,Apple还没有公布他们如何进行基于GL的字体/路径渲染。似乎OS X与MacOS 9不同总是暗示,哪个one 2013 research paper that addresses hinting via OpenGL shaders。)无论如何,有INRIA的Nicolas P. Rougier写的this forum discussion;如果你需要从OpenGL中提示,那么它可能值得一读。虽然看起来像freetype这样的库已经完成了暗示的所有工作,但实际上并非如此,原因如下:我从论文中引用:

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

解决方案并非完全无关紧要,因此我不打算在此解释。 (该论文是开放获取的。)


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

最后,如果你需要做cairo graphics,你似乎目前运气不好OpenGL,因为似乎没有基于OpenGL的库存在。 (另一方面,DirectWrite可以处理CTL。)有像HarfBuzz这样的开源库可以渲染CTL,但我不知道你是如何让它们运行良好的(如使用基于模板的方法) OpenGL的。您可能必须编写粘合代码以提取重新形状的轮廓并将其作为路径提供给基于NVpr或SDF的解决方案。


3
投票

我认为你最好的选择是用OpenGL后端调查qazxswpoi。

开发3.3核心原型时遇到的唯一问题是OpenGL后端的功能使用已被弃用。这是1 - 2年前所以情况可能会有所改善......

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

© www.soinside.com 2019 - 2024. All rights reserved.