除非您总共仅渲染十二个字符,否则绘制轮廓将保持“不可行”状态,因为每个字符需要近似于曲率的顶点数量。尽管已经有方法来评估像素着色器中的贝塞尔曲线,但是这些方法不易抗锯齿,这在使用距离贴图纹理的四边形时是微不足道的,并且在着色器中评估曲线在计算上仍然比需要的昂贵得多。
在“快速”和“质量”之间最好的权衡仍然是带有符号距离场纹理的纹理四边形。它比使用普通的普通纹理四边形稍微慢一点,但是没有那么多。另一方面,质量完全不同。结果确实令人惊叹,它可以尽快获得,并且发光等效果也很容易添加。此外,如果需要,可以将该技术很好地降级为较旧的硬件。
有关该技术,请参见著名的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”,第239页)中指出的那样,在新一代GPU上以编程方式从着色器中提取顶点数据没有明显的额外延迟或开销,与使用标准固定功能执行相同操作相比
而且,最新一代的GPU具有越来越合理大小的通用L2高速缓存(例如nvidia Kepler上的1536kiB),因此当从缓冲区纹理中提取四角的随机偏移时,人们可能会期望出现不一致的访问问题。问题。
这使得从缓冲区纹理中提取恒定数据(例如四边形大小)的想法更具吸引力。因此,假设的实现可以使用以下方法将PCIe和内存传输以及GPU内存减少到最低程度:
- 仅将字符索引(每个要显示的字符一个)上载为传递到此索引和的顶点着色器的唯一输入
gl_VertexID
,并将其放大到几何着色器中的4个点,仍然具有字符索引和顶点ID(将作为“ gl_primitiveID在顶点着色器中可用”)作为唯一属性,并通过转换反馈捕获此属性。
- 这将很快,因为只有两个输出属性(GS的主要瓶颈),否则在两个阶段都接近“无操作”。
- 绑定一个缓冲区纹理,该缓冲区纹理针对字体中的每个字符包含相对于基点的纹理四边形的顶点位置(这些基本上是“字体度量”)。通过仅存储左下角顶点的偏移量并编码轴对齐框的宽度和高度(假设为半浮点数,这将是每个字符8字节的恒定缓冲区),可以将该数据压缩为每四方4个数字。典型的256个字符的字体可能完全适合L1缓存的2kiB)。
- 为基准设置统一
- 将缓冲区纹理与水平偏移绑定。这些可能甚至可能在GPU上进行计算,但它更容易和更高效的那种事的CPU上,因为这是一个严格的顺序操作,而不是在所有的琐碎(认为字距的)。同样,它将需要另一个反馈传递,这将是另一个同步点。
- 渲染先前从反馈缓冲区生成的数据,顶点着色器从缓冲区对象中提取基点的水平偏移和角顶点的偏移(使用原始ID和字符索引)。现在,提交的顶点的原始顶点ID是我们的“原始ID”(请记住,GS将这些顶点变成了四边形)。
这样,理想情况下可以将所需的顶点带宽减少75%(摊销),尽管它只能渲染一条直线。如果希望能够在一个绘制调用中渲染多条线,则需要将基线添加到缓冲区纹理,而不是使用统一的线(使带宽增益变小)。
但是,即使假设减少了75%,因为显示“合理”数量的文本的顶点数据仅在50-100kiB左右(实际上为零)到GPU或PCIe总线)-我仍然怀疑增加复杂性和失去向后兼容性真的值得麻烦。将零减少75%仍然仅是零。我承认我还没有尝试过上述方法,因此需要做更多的研究才能做出真正合格的陈述。但是,除非有人能够表现出真正令人惊叹的性能差异(使用“正常”数量的文本,而不是数十亿个字符!),否则我的观点仍然是,对于顶点数据,一个简单的普通旧顶点缓冲区就足够了。被视为“最新解决方案”的一部分。它简单明了,可以正常工作,并且效果很好。
在上面已经提到了“ OpenGL见解 ”之后,还值得一提的是Stefan Gustavson撰写的“通过距离场进行2D形状渲染”一章,其中详细介绍了距离场渲染。
2016年更新:
同时,存在一些附加技术,其目的是消除在极端放大倍率下变得不安的拐角圆角伪像。
一种方法只是使用伪距离场而不是距离场(区别在于该距离不是到实际轮廓线的最短距离,而是到轮廓线或突出到边缘的假想线的最短距离)。这样会更好一些,并且使用相同数量的纹理内存以相同的速度(相同的着色器)运行。
另一种方法是在三通道纹理细节中使用三位数的中位数,并在github上提供实现。目的是对以前用于解决此问题的“与”或“黑”进行改进。好的质量稍微慢一点(几乎不明显),但速度慢,但是使用的纹理内存是其三倍。而且,很难获得额外的效果(例如发光)。
最后,存储构成字符的实际贝塞尔曲线,并在片段着色器中对其进行评估已变得很实用,即使在最高放大倍数下,其性能也略逊一筹(但问题不大),并且效果惊人。
WebGL的演示渲染大PDF文件使用这种技术可以实时地在这里。