从字体文件到 3D 文字

字体类型主要有:点阵字体、轮廓字体、笔画字体。根据描述方式的不同,可以分为点阵字体与矢量字体,它们的区别可以参考位图与矢量图。本文主要讨论轮廓字体中常见的 TrueType 标准(.TTF)下的相关实践。

truetype-bitmap

字体解析

既然它是矢量字体,那么把字体当成 svg 看待不就简单了吗?首先对字体文件进行解析,这里使用 typr.js,解析过后,可以拿到字形的路径以及 metrics 信息。

typr-result

有了路径之后,我们就可以在 canvas 2D 中进行绘制。

1
2
const path = new Path2D(pathStr);
canvas.stroke(path);

triangulation

上一步中我们绘制了 2D 的文字,但是如何将它变成 3D 呢?我们还需要对其进行 triangulation,将其分成很多个三角形,这些三角形将会是文字平面的基本组成部分。

triangulation

为了方便,以字母 i 为例,字母 i 共有 4 个路径点:1、2、3、4。

i-path;

使用 earcut 进行三角化转换。

1
var triangles = earcut([point1, point2, point3, point4]); // returns [1,0,3, 3,2,1]

这样就得到了两个三角形三个顶点对应的索引。

3D 化

经过了 triangulation,我们已经可以得到文字的一个平面,现在可以准备将它从 2D 转换成 3D 了。

如果将前面得到的那个平面作为正面,那么其他面要如何得到呢?

3D 化的字母 i 可以看作近似立方体,一个立方体需要 8 个顶点,而我们现在只有 4 个顶点。如果将得到的 4 个顶点组成的面作为正面,那么可以将这 4 个顶点沿 z 轴偏移,这样就能够拿到新的 4 个顶点作为背面。最后得到的顶点数据就是以下八个点:

1
[0,1,2,3,4,5,6,7]

cube-index

要得到背面,我们需要将正面的索引反转,反转之后加上总的顶点数:

1
[0, 1, 2] -> [0, 1, 2].reverse().map(i = i + verticesNum)

侧面就复杂一些,把正面的 4 个顶点分成 4 段(每两个相邻的点),将它们与背面上对应的点连接就能得到一个面,而这个面就是我们需要的侧面。例如,0、1、5、4 组成的面,我们可以将它拆分成 [0, 1, 5][5, 4, 0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// [0, 1, 5, 4] -> [0, 1, 5] && [5, 4, 0]

for(let i = 0; i < verticesNum; i++) {
if(i === verticesNum - 1) {
const a = i;
const b = i + 4;
const c = i + 3;
const d = i + 7;
}

const a = i;
const b = i + 1;
const c = i + 5;
const d = i + 4;

console.log(
[a, b, c],
[c, d, a]
);
}

一通操作后,拿着得到的顶点数据和索引(面)丢进任意一个 WebGL demo 就搞定了。

1
2
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, faces);
gl.drawElements(gl.TRIANGLES, 4 * faces.length, gl.UNSIGNED_SHORT, 0);

参考