Pixi.js: 提案:改进文本渲染

创建于 2020-04-05  ·  27评论  ·  资料来源: pixijs/pixi.js

社区已经说过 - Pixi 的文本渲染性能需要改进。 本期专门讨论我们如何改进以及我认为最好的交付方式:

  • 有符号距离字段:此技术将文本渲染为纹理(使用 Canvas 2D API)并应用距离变换。 简单来说,距离变换会将输出纹理中每个像素的值设置为该像素到输入纹理中最近的文本轮廓的距离。 pixi-sdf-text 是一个例子: https :

  • VTM:矢量纹理贴图在纹理的像素级别对曲线不连续性进行编码。

  • 精确的贝塞尔曲线渲染:这通过细分除曲线之外的所有内容来“按原样”渲染字体。 曲线使用特殊的片段着色器渲染(没有对曲线进行采样,它通过抗锯齿在 GPU 上精确渲染): https : https ://codepen.io/sukantpal/pen/GRJawBg = 0010

@bigtimebuddy和我讨论了这些方法,我们认为第三种方法是最好的,因为:

  • SDF 需要预先生成的地图集。 生成多通道 SDF 非常复杂(如果我们想在运行时进行)。

  • VTM 对于字体来说太复杂了。

  • 精确的贝塞尔曲线只需要您像 Graphics 中的其他所有内容一样将字体渲染为路径。

最有用的评论

@eXponenta请不要那么敌视。 我们正在探索没有什么是可靠的方法。 请有建设性,不要使用侮辱。

所有27条评论

它不应该在核心。
它是一个沉重的包,必须在主包之外实现,核心团队不应该担心它的实现。

Pixi SDF 就是正确的例子。

请不要尝试从 PIXI 制作 Phaser

我想提供更多背景信息。 我已经要求@SukantPal研究具有不同权衡的替代 Text/BitmapText 方法。 特别是,如果我们能找到比 Text 性能更好并且看起来很棒的东西。

我认为这里有很多方法。 有些可能适合作为第 3 部分插件,有些可能在 repo 中但未捆绑,有些可能会替换/增强当前的 API。 让我们找出最有成效的文本渲染方法是优化性能但对 BitmapText 的权衡较少。

我会使用几条标准来判断一个好的 Text 显示对象:

  • 良好的运行时性能- 渲染文本和更改文本的成本很低
  • 内存不足 - RAM 使用率低
  • 小依赖- 依赖足迹文件大小很小
  • 高保真- 适用于不同的分辨率
  • 灵活的样式- 支持笔触、阴影、渐变
  • 支持大字形语言- 例如,中文、日文、韩文
  • 向后兼容- 与 Context2D 一起使用

| | 性能 | 内存 | 依赖 | 保真| 造型 | 中日韩 | Context2d |
|--|--|--|--|--|--|--|--|
|正文| 👎 | 👎 | 👍 | 👍 | 👍 | 👍 | 👍 | 👍 |
|位图文本| 👍 | 👎 | 👍 | 👎 | 👎 | 👎 | 👍 |

行。
描述fragment interpolation和所有非本地实现:

  1. 需要额外的着色器和额外的数据 - 这将阻止批处理。
  2. 更改时需要重建几何体(第 3 页)。
  3. 语言中存在的所有字形都需要一个字形表,需要将特定的 TTF 完全加载到内存中,因为它不能支持流式传输,然后解析它。 看起来像 SWF。
    因为我们无法将系统字体加载为形状 - 我们无法使用它。
  4. 语言特定规则正在从浏览器团队转移到 lib 团队,实现渲染规则:ltr/rtl、印地语/阿拉伯字形连接规则等......与将 10% 浏览器移动到 lib 相同。 Unity 仍然没有正确实现它。
  5. 带有支持样式的额外参数的重型着色器。 这会增加渲染成本,因为我们应该实时渲染它。
    当然,我们应该将其缓存为纹理。

@eXponenta

  • 文本可以批处理 - 尽管不能与图形等其他内容一起使用。

  • 当前文本实现多次缓存每个字形(如果一个字母在多个 PIXI.Texts 中多次使用)。 每个字形有一个几何体更好。 此外,您不需要以不同的字体大小进行缓存。

  • 不需要重新构建几何 - 如果文本中的字母发生变化,您需要从字形几何表中再次获取。 如果变换改变,你只需要改变一件制服。

  • 我们可以使用库进行字体解析。 到几何的转换是微不足道的。

  • 我不知道字形连接规则是什么。 你能解释一下吗?

  • 从左到右 vs 从右到左 - 如果你想反转字母,你可以。 如果你想要镜像,那么在内部设置scale.x=-1

  • 文本样式不会向着色器添加更多参数。 它将改变几何形状。 现在,将字形存储为几何图形比将其缓存在纹理中占用的内存更少。

  • 从 API 获取的字体文件应该缓存在用户的机器上。

你很天真:

  1. 它应该是外部批处理器,因为字形需要额外的属性,例如贝塞尔锚点、阴影值、梯度锚点...,或者都应该作为统一传递 - 再见,批处理。
  2. 从右到左和从左到右,它们的组合是很复杂的问题。
    在 PDF 渲染库中打开的问题:
    https://github.com/asciidoctor/asciidoctor-pdf/issues/175
  3. 印地语问题(和类似的语言)
    https://docs.microsoft.com/en-us/typography/opentype/spec/gsub

我们不能使用外部依赖,因为会与isMobileresource-loader存在相同的问题。
核心应该是干净的。 最小的外部依赖。

@eXponenta

  • 混合布局(ltr 和 rtl 组合)的优先级低于高性能。 您始终可以回退到当前算法。 梵文类似。 梯度停止等特殊功能也需要回退。

  • 批处理可以在这里工作,因为没有任何纹理会更容易。 可以创建像CanvasGraphicsRenderer这样的插件。

行。 你赢了。 做吧。 但是在内核之外,然后我们将查看它并决定我们应该做什么。
但没有严重的依赖。

@eXponenta请不要那么敌视。 我们正在探索没有什么是可靠的方法。 请有建设性,不要使用侮辱。

@eXponenta您确定我们现在支持从右到左的文本吗?

我们正在考虑的另一种方法是分别使用 canvas 2D(与现在相同)渲染每个字形并将它们缓存到图集中。 该图集可以在所有PIXI.Text实例中全局重用。 每个字形 + 字体大小 + 字体样式组合将单独缓存。

你如何看待将其纳入核心?

嘿,偷看,听到这些新方法摆在桌面上真是令人兴奋。 @eXponenta ,与@bigtimebuddy相呼应,请让我们保持建设性。 您显然对新提议的方法有一些很好的了解,但让我们看看我们是否可以减轻提出的问题并保持友好:)

在文字上,这是我的 2 美分(便士?)。

我个人认为最有价值的方法是获得位图文本的速度和性能,但无需事先实际创建位图字体。 如果您愿意,可以构建动态位图纹理!

动态位图字体

优点

  • 用于创建位图字体的工具很少见! 很想消除那个障碍。
  • 我们可以创建各种大小的文本大小纹理。
  • 与画布一起使用
  • 可以批量处理每个字形的性能
  • 很多很酷的效果!
  • API 只需要稍微调整:
const textStyle = new TextStyle({
    font:'comic sans',
    fill:'bright green'
});


const bitmapFont = new BitmapFont(textStyle);

缺点

  • 位图当前位图字体的所有其他缺点:P
  • 动态更新纹理图集会导致速度变慢吗?

    • 虽然这会在一段时间后稳定下来,并且可能会在会话之间缓存?

    • Current Text 每次换帧也得上传一个纹理,这种做法会不会少点?

想法

  • 可以缓存在本地存储中生成的纹理吗? 为后续访问?
  • 只添加使用过的字符,所以纹理是需要的那么大。 随时更新
  • 将过滤器烘焙到此纹理以获得额外的酷炫效果(轮廓、阴影等)
  • 我们可以通过不使每个字形成为精灵来减少位图字体的内存吗?

自卫队
SDF 可以是上述方法的扩展吗? 动态生成该纹理有多复杂? 它会超级笨拙吗? 我们是在谈论大量增加代码库吗?

大多数人不知道那是什么,因此理想情况下应该隐藏它。 如果我们需要在外部生成 SDF 字段或生成所需的代码太大或太复杂,那么在家里作为插件肯定会更好:

const textStyle = new TextStyle({
    font:'comic sans',
    fill:'bright green',
    sdf:true, <----- how sweet would this be, maybe rename to more user friendly like 'cleanEdges'
});

const sdfFont = new BitmapFont(textStyle);

精确的贝塞尔曲线渲染

这肯定是一个有趣的方法! 我过去曾对此进行过研究,并决定将着色器开关引入渲染循环和所需的额外曲面细分会产生很大的开销。 @SukantPal ,您比我现在更接近这个想法,所以想了解您对我的担忧!

话虽如此,也许值得构建一个原型,我们可以测试和抨击一下以验证我们的想法?

激动人心的文字时光 :D

SDF 可以是上述方法的扩展吗? 动态生成该纹理有多复杂? 它会超级笨拙吗? 我们是在谈论大量增加代码库吗?

SDF 生成成本高昂,并且有打包时间(当我们在地图集中打包不同字体时),但它可以缓存在客户端(与所有其他变体一样)。
它有一个画布(呵呵)实现:
https://github.com/mapbox/tiny-sdf
但是 SDF 编译并不完全正确(增加/减少字体大小时字形会发生变化)
看起来很有用,但对于 MSDF,我们应该执行 3 次(每个通道)。

哦,哇,这就是一小段代码! 看起来着色器也已经成熟了!

@GoodBoyDigital tiny-sdf 包生成单通道距离场。 当您以较小的字体大小缓存文本时,它不会产生尖角。 这可以通过以相同的字体大小进行缓存来缓解。

单通道 SDF 生成应该不会太糟糕。 多通道 SDF 更胜一筹,只是它太复杂了。 我无法理解 Chumskly 是如何编码角点的。

现在,我们正在考虑的一切都不会支持混合的 rtl 和 Ltr 布局(正如@eXponenta正确指出的那样)。 但我认为我们现在也没有正式支持它。

确切的方法有两个好处 - 小缓存大小(您缓存的是顶点而不是所有像素),即使关闭了“抗锯齿”设置也能抗锯齿(我认为文本应该总是抗锯齿的。当前实现是通过使用画布来实现的) 2D)。

至于demo,你看我的二次贝塞尔曲线demo了吗? https://codepen.io/sukantpal/pen/GRJawBg?editors=0010

——
我和@bigtimebuddy讨论的另一件事是,最简单的解决方案是继续使用 canvas 2D 并将字形缓存到纹理中。 字形可以渲染为四边形。 缓存图集可以在文本之间重复使用。 当然,这意味着大文本以相同的大小缓存,因此当渲染大量文本但只有一部分(如 PDF 中的当前页面)可见时,会占用大量内存。

好的,

是的,不用担心 rtl 或 ltr,它们将始终通过当前方法可用,并且是所有自定义文本呈现技术在以后需要解决的问题。
这些决定。 理想情况下应该关注内存消耗和运行时性能。

Bezier 演示很酷,但我认为这不足以理解如果我们渲染一个完整的句子可能隐藏的真正复杂性。

我上面写的关于动态位图字体的内容与您提到的字形缓存几乎完全相同。 我认为这绝对是一条有用的路线!

总之:

Bezier 曲线解决方案:这可能是一条很好的路线,但我觉得这需要进一步的研发来确定它如何适合实际的生产用例。

来自普通字体的动态位图字体:我们绝对应该探索这一点,因为我们知道这将提供良好的性能,同时还保持 API 简单。 我最喜欢的 :D

单通道 SDF欣赏它不会像多通道 SDF 那样好,但演示对我来说看起来不错! 如果文本可以很好地缩放并且每个字体可以有一个纹理,那么我认为这条路线也值得研究!

@GoodBoyDigital我认为演示确实有很好的文字。 但是,如果您尝试查找详细信息,则存在明显差异:

Screen Shot 2020-04-06 at 1 34 29 PM

Screen Shot 2020-04-06 at 1 34 35 PM

顶部的文字不清晰 - 边缘有摆动。 每个字体+(字体大小的~12-15px 差异)组合可能需要单独缓存。

如果这些摆动不重要或者我们可以使用多种字体大小进行缓存,我完全可以选择 SDF。


最简单的解决方案对我来说也很好💯tbh!

大家好。

我是 Pixi 的所有事情的超级外行,但我是 GDevelop 的用户,它使用 Pixi 作为其后端,我已经深入研究了一段时间,以了解 GDevelop 对 PixiJS 和文本渲染的实现。 对于我正在制作的包含大量文本的游戏来说,这实际上很有影响力。 (您可以在我在 GDevelop 中发布的问题中查看我的研究/示例:https://github.com/4ian/GDevelop/issues/1449)

我发现的一个选项是完全忽略文本的缩放问题,并通过始终将文本缩放比例保持在 100% 来完全消除它们,而是缩放文本字体大小? 这是字体不可知的,似乎适用于所有尺寸。

这是一个使用 Pixi 完成的代码示例: https :
我在这个线程中找到了它: https :

我实际上对这个问题很感兴趣,因为我希望有人可以修改 GDevelop 的 Pixi 文本扩展,但是(作为外行)我没有意识到这是 Pixi 本身的一个更大的问题。

不知道这种方法是否存在潜在问题,但它似乎可以避免 sdf/msdf/etc 的所有性能问题。

@Silver-Streak 如果您的意思是通过应用于文本显示对象的比例来增加字体大小,那么在性能方面如何更好? SDF 的东西有助于提高性能,因为您不会以更大的字体大小重新渲染。

@Silver-Streak 当然,你的提议会提高 _quality_,但我看不出它会如何提高性能。

@Silver-Streak 当然,你的提议会提高 _quality_,但我看不出它会如何提高性能。

除非我误解,否则更改字体大小使用的资源不会比缩放整个对象或在另一个渲染器中加载更少吗? 再说一次,当谈到 Pixi 的缩放实际工作方式时,我是超级外行,对我来说,将字体保留为原始分辨率但仅更改文本大小会比缩放它更快。 如果那是不正确的,为分心而道歉。

@Silver-Streak Scaling 是作为一种变换来实现的。 您有一个转换矩阵 - 更改该矩阵中的比例是一项微不足道的操作。

到目前为止,文本是使用 Canvas 2D API 渲染到画布中的。 然后将其复制到屏幕上。 更改比例只会更改画布中文本映射到的屏幕坐标。

啊,说得有道理。 这很不幸,虽然公平地说,我的问题更多是字体的图形渲染在缩放后变得模糊,但我认为它也会有更好的性能。

虽然我很高兴有人实施它来解决一般的文本质量问题,但我也完全理解想要提高性能。

谢谢你和我讨论它。

@Silver-Streak 没问题。 然而,在 WebGL 中实现质量第一的文本也不难。 一些应用程序通过自己创建网格来实现文本。 您可以解析字体文件,制作一个顶点列表,然后细分它。 这些顶点可以通过网格渲染。

在高比例下,这可能会导致轻微的“边缘” - 因为最终,您将文本呈现为三角形。 为了获得更高的质量,您可以使用我在此线程中谈论的“精确贝塞尔曲线”着色器 - 它会将曲线渲染为曲线而不是三角形。

如果您需要文字质量方面的帮助,我可以帮助你们:)

@SukantPal我绝对不是 GDevelop 的贡献者,(你也不希望我成为。我在生活中主要是业务系统分析师/DevOps,而不是开发人员😄)尽管我喜欢这个项目并且在社区中很活跃. 我也知道社区的其他成员希望看到缩放文本质量的解决方案。

但是,如果您想查看帖子中的问题,我也在 Bountysource 上悬赏并向所有人开放。 不过,我不想在这里占用更多的空间,因为很明显,总体上围绕 Pixi 进行了更深入的对话,而我的建议并不像我想象的那样准确。

@Silver-Streak 我可能会看一看,因为在电晕季节我无事可做

我知道这是一个封闭的线程,但我相信每个人仍在寻找改进文本呈现的最佳方法。 我确实注意到有人有一个适用于 Pixi v5 的 msdf 实现。 https://github.com/cjsjy123/pixi-msdf-text-v5想我会向那些感兴趣的人提及它。

该线程绝对应该重新打开并保持活动状态 - 或者至少每隔一段时间进行某种更新,因为这是最受关注的问题之一。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

zcr1 picture zcr1  ·  3评论

lunabunn picture lunabunn  ·  3评论

YuryKuvetski picture YuryKuvetski  ·  3评论

courtneyvigo picture courtneyvigo  ·  3评论

lucap86 picture lucap86  ·  3评论