从文本生成3D网格

近期我希望将文本转换为可渲染的3D网格,用于Geotoy项目及Geoscript语言开发。我调研了能解决不同环节的工具与库,最终构建出完整实现流程——可生成任意字体、文本样式等参数的优质二维流形3D网格。

本文概述整个实现方案,旨在为有类似需求者提供完整可复现的操作指南。

svg-text-to-path

系统首层采用名为svg-text-to-path的JavaScript库。该库能处理任意文本输入与字体参数,生成包含最贴合文本路径的SVG文件。

该库内部同时处理用户指定字体的获取加载与文本转路径转换,并支持为每个步骤配置不同后端。

针对我的使用场景,我采用了Google Fonts提供商。其配置简单,仅需免费获取的Google Fonts API密钥即可使用。这使我能调用Google Fonts库中几乎所有字体创建网格。虽然偶有字体加载失败的情况,但仅涉及少数冷门字体,我并未深入探究原因。

在文本转路径环节,svg-text-to-path默认采用fontkit后端。Fontkit是另一款纯JavaScript字体引擎库,虽未深入研究,但其功能丰富且支持多种高级字体特性。

元素周期表

基于我的应用场景——程序在浏览器中运行——本可直接使用svg-text-to-path生成路径。但文本转网格功能并非核心需求,我不愿因此增加应用负担。同时希望尽可能简化用户配置流程,并安全地使用Google字体API密钥。

因此我选择创建一个微型后端服务:接收输入文本及参数,返回生成的路径字符串。该服务基于极简的Bun网络服务器,利用其内置的Bun.serve功能,仅暴露单个HTTP/JSON接口。

svg-text-to-path本身也内置了简易Web服务器,但我选择自建服务器以便实现自定义缓存,并能对生成的SVG进行后处理提取路径。我采用大型语言模型(LLM)搭建了应用框架,效果相当理想。这类低风险的一次性/独立应用正是LLM的理想用武之地。

感兴趣者可查看源代码,不过我保证没什么特别之处:https://github.com/Ameobea/sketches-3d/tree/main/geoscript_backend/text-to-path

总之,该工具的输出是SVG路径,其中编码了一系列绘制命令,用于生成如下文本:

{
  “path”: "M5.86 24L5.86 9.53L1.15 9.53L1.15 7.2L13 7.2L13 9.53L8.28 9.53L8. 28 24ZM18.8 24.29Q17.09 24.29 15.85 23.47 ...."
}

lyon

路径生成功能实现后,我需要将其转换为网格三角形。幸运的是,优秀的lyon Rust库(我过去在多个项目中多次使用过)完美解决了这个问题。

lyon_extra内置SVG路径解析器,能将路径解析为底层绘制指令。

随后,lyon_tessellation库将这些绘制指令转换为三角形。它处理了所有复杂情况和边界条件,包括凹形形状、中空区域、贝塞尔曲线离散化等所有细节。

我实现了一个微型 WebAssembly 封装器,它接收输入路径并返回顶点和索引缓冲区: https://github.com/Ameobea/sketches-3d/blob/main/src/viz/wasm/path_tessellate/src/lib.rs

除了处理自定义缩放的少量额外逻辑外,该实现本质上只是对lyon功能的轻量封装。

需要说明的是,我不得不修改默认的FillTessellator选项,将fill-rule设置为非零值——我认为这是SVG的默认设置。此举修复了某些含自相交路径字体的输出问题,效果从原先这样:

图0:从文本生成3D网格

到:

图1:从文本生成3D网格

挤出操作

此时我已获得两个缓冲区,分别存储着定义文字路径的二维网格顶点与索引。剩下的唯一步骤就是将其挤出为三维模型。这是对三角网格进行的相当直接且常见的操作。

首先,将所有顶点从二维转换为三维,方法是将新轴填充为零(例如 (5, 10) -> (5, 0, 10))。

然后,翻转网格中所有三角形的绕行顺序。WebGL及绝大多数渲染系统采用逆时针绕行顺序,该顺序决定了三角形的可见方向。翻转时只需将索引缓冲区中每个三角形的第一个和第三个索引互换,例如:

1,2,3,5,7,9,1,4,2

变为

3,2,1,9,7,5,2,4,1

接着,将每个顶点沿新轴偏移n个单位创建副本(例如(5, 0, 10)→(5, 2, 10))。

然后,按原始(未翻转)绕行顺序用三角形连接这些新顶点。这将使顶面和底面朝向相反——顶面朝上,底面朝下。

最后生成三角带连接顶底面边界棱线。边界棱线指仅属于单一面体的棱线。处理网格时通常采用半边数据结构等图论表示法,这有助于实现该步骤。

最终效果应类似如下所示:

图2:从文本生成3D网格

若感兴趣可参考我的源代码,但请注意其中使用了自定义的网格内部表示法: https://github.com/Ameobea/sketches-3d/blob/main/src/viz/wasm/geoscript/src/mesh_ops/extrude.rs

若操作正确且仔细追踪顶点索引以避免在相同位置创建重复顶点,生成的网格应结构良好且为2维流形/水密。这是至关重要的拓扑属性,也是包括构造实体几何(CSG)在内的多种网格处理算法的必要条件。

输出网格的流形特性意味着它们可通过布尔运算与其他网格组合,或进行平滑等后续处理。虽然无法100%保证所有字体所有字符生成的路径都能产出流形输出,但经测试所有案例均符合要求。

结论

至此完成!经过上述处理,最终输出为定义输入文本的3D网格的顶点与索引集。

我已将此功能集成到Geoscript语言中作为内置函数:

图3:从文本生成3D网格

虽然需要处理的步骤不多,但底层强大的库(svg-text-to-pathfontkitlyon)承担了所有复杂运算和繁重工作。

尽管部分关键库采用JavaScript编写,且生成过程在远程服务器端完成,但实践表明对于我转换的(相对简短)文本,其运行速度相当快——足以实现即时响应而无需等待。

目前尚未遇到字体导致输出损坏或网格异常的情况,甚至能处理复杂的非英文字符:

图4:从文本生成3D网格

这个小项目充满乐趣,最终成果令我非常满意。

本文文字及图片出自 Generating 3D Meshes From Text

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号