在 WebVR 中呈现文本

细节展示

查看网站

Inside (https://with.in/) 是一个在虚拟现实世界中讲故事的平台。因此,当团队在 2015 年听说 WebVR 时,我们立刻对它的潜力产生了兴趣。如今,这种兴趣已体现在我们的网络平台的唯一子网域 https://vr.with.in/ 中。只要使用支持 VR 的浏览器,任何人都可以访问该网站、点击按钮并戴上头戴设备,沉浸在我们的 VR 影片系列中。

目前包括但不限于 Daydream View 上的 Chrome 浏览器。如需了解您的设备和头戴式显示屏,请访问 https://webvr.info/

与其他特定于虚拟现实的渲染环境一样,网络主要依赖于场景的三维表示。此场景包含一个镜头、您的视角以及任意数量的对象。为帮助管理此场景、相机和对象,我们使用了一个名为 Three.js 的库,该库利用 <canvas> 元素将渲染投放到计算机的 GPU 上。有许多实用的 Three.js 插件可让您的场景在 WebVR 中可见。其中主要两种是 THREE.VREffect(用于为每只眼睛创建视口)和 THREE.VRControls(用于将视角(例如头戴式显示屏的旋转和位置)以令人信服的方式转换为您的场景。关于如何实现此功能的示例,有很多示例。查看 Three.js WebVR 示例,了解如何开始使用。

随着对 WebVR 的深入探索,我们发现了一个问题。如果我们看一下 Web 内容,就会发现文本是其中不可或缺的一部分。虽然我们的内容大多都是视频,但如果您前往网站内部,内容周围会有文本;界面以及有关电影或相关电影的其他信息都是由文本构成的。此外,所有这些文本都是在 DOM 中创建的。我们的 WebVR 探索和 https://vr.with.in/ 均位于 <canvas> 中。

WebVR 中使用的文本 WebVR 中使用的文本
WebVR 中使用 vr.with.in 的文本

我可以怎么做?

幸运的是,我们仍在努力解决这个问题。事实上,在我们的研究中,我们发现了许多在三维环境中通过 <canvas> 元素渲染文本的有效方法。下面列出了我们发现的一些方案,并分别标记了各自的优缺点:

与分辨率无关 排版特征 性能 实施难度
2D 画布文字
三角形矢量文本
3D 文本拉伸
符号距离字段位图文本

我们的决定:SDF 位图字体

使用 ctx.fillText() 的 2D 画布可以进行文本换行、字母间距和行高,但溢出时会截断,并且如果放大得非常远,文本将变得模糊。您可以增加画布纹理的大小,但如果纹理过大,则可能会达到纹理大小的上限或性能可能会受到影响。

拉伸 3D 文本基本上与三角化矢量文本相同,但具有深度并可能具有倾斜度,因此其几何图形至少是前者的两倍。对于标题或徽标,这两种方法的小量都适用,但在处理大量文本时效果欠佳,并且都没有排版功能。

字体转换为 SDF 位图的工作流程
字体转换为 SDF 位图的工作流程

位图字体为每个字符使用一个四边形(两个三角形),因此它们使用的几何图形更少,并且性能优于三角化矢量。它们仍然基于光栅,因为它们使用纹理贴图精灵;但是使用 SDF 着色器时,它们基本上与分辨率无关,因此看起来比 2D 画布纹理更美观。Matt DesLauriers' Three-bmfont-text 还包含可靠的文本换行、字母间距、行高和对齐功能。溢出不会截断。字体大小通过缩放进行控制。我们之所以选择这条路线,是因为它为我们提供了 最佳的设计方案和出色的设计方案遗憾的是,这不那么容易实现,因此我们将介绍这些步骤,希望能够为使用 WebVR 的同行开发者提供帮助。

1. 生成位图字体 (.png + .fnt)

Hiero 接口
Hiero 接口
Hiero 输出(位图 PNG 和 .fnt 文件) Hiero 输出(位图 PNG 和 .fnt 文件)
Hiero 输出(位图 PNG 和 .fnt 文件)

Hiero 是一种通过 Java 运行的位图字体打包工具。Hiero 文档不会真正说明如何在不执行复杂的构建流程的情况下运行它。首先,安装 Java(如果尚未安装)。然后,如果双击 runnable-hiero.jar 无法打开 Hiero,请尝试在控制台中使用以下命令运行它:

java -jar runnable-hiero.jar

Hiero 运行后,打开 .ttf 或 .otf 桌面字体,输入要添加的任何额外字符,将渲染更改为 Java 以启用特效,增加字符大小,使字符填满整个字形缓存方块,添加距离字段效果,调整距离字段的缩放和扩散。缩放值就相当于分辨率。它越高,越不模糊,但 Hiero 渲染预览所需的时间越长。然后保存位图字体。它会生成由 .png 图片和 AngelCode .fnt 字体描述文件组成的位图字体。

2. 将 AngelCode 转换为 JSON

现在,位图字体已生成,我们必须使用 Matt DesLauriers 的 load-bmfont npm 软件包将其加载到 JavaScript 应用中。

我们可以对 load-bmfont 进行浏览器化并在前端使用它,但我们将使用 Node 运行 load-bmfont.js,以转换 Hiero 的 AngelCode .fnt 并将其保存到 .json 文件中:

npm install
node load-bmfont.js
输出 JSON 示例
输出 JSON 的示例

现在,我们可以绕过 load-bmfont,对 .json 字体文件执行 XHR (XMLHttpRequest) 请求。

var r = new XMLHttpRequest();
r.open('GET', 'fonts/roboto/bitmap/roboto-bold.json');

r.onreadystatechange = function() {
    if (r.readyState === 4 && r.status === 200) {
    setup(JSON.parse(r.responseText));
    }
};

r.send();

function setup(font) {
    // pass font into TextBitmap object
}

3. Browserify 三个 BMfont-Text

字体加载完成后,Matt 的 3-bmfont-text 将负责其余工作。由于我们没有将 Node 用于自己的应用,因此要将 three-bmfont-text.js browserify 为可用的 three-bmfont-text-bundle.js

npm install -g browserify
browserify three-bmfont-text.js -o three-bmfont-text-bundle.js

4. SDF 着色器

调整 vr.with.in/archive/text-sdf-bitmap/ 上的 afwidththreshold 滑块,以了解有符号距离字段着色器的影响。

5. 用法

为方便起见,我针对经过浏览器化处理的 3-bmfont-text 创建了一个 TextBitmap 封装容器类

Text-sdf-bitmap 实际应用
Text-sdf-bitmap 实际应用
<script src="three-bmfont-text-bundle.js"></script>
<script src="sdf-shader.js"></script>
<script src="text-bitmap.js"></script>

为 .json 字体文件创建 XHR 请求,并在回调中创建一个文本对象:

var bmtext = new TextBitmap({ options });

如需更改文字,请执行以下操作:

bmtext.text = 'The quick brown fox jumps over the lazy dog.';

scene.add( bmtext.group );
hitBoxes.push( bmtext.hitBox );

位图字体的 .png 通过 THREE.TextureLoader 在 text-bitmap.js 中加载

TextBitmap 还包含一个不可见的碰撞框,用于通过鼠标、相机或手跟踪运动控制器(如 Oculus Touch 或 Vive 控制器)进行 three.js 光线投射互动。当您更改文本选项时,命中框的大小会自动更新。

Bmtext.group 已添加到 three.js 场景。如果您需要访问子对象/Object3D 文本,则文本的场景图如下所示:

文件系统示意图

6. 统一 JSON 文件并修改 xoffset

在文本 GIF 内

如果查看字距,您可能需要修改 JSON 中的 xoffset。将 JSON 粘贴到 Jsbeautifier.org 以获取非精简版文件。

xoffset 本质上是针对一个字符的全局字距。字距调整适用于两个相邻的特定字符。字距调整数组中的默认值实际上并没有改变,并且修改起来过于繁琐,因此您可以清空该数组以减小 json 的文件大小。然后修改 xoffset 以设置字距。

首先,您必须弄清楚哪些字符与 JSON 中的哪个字符 ID 相对应。在 three-bmfont-text-bundle.js 的第 240 行后插入 console.log

    var id = text.charCodeAt(i)
    // console.log(id);

然后在 https://vr.with.in/archive/text-sdf-bitmap/ 上的 dat.gui 文本字段中输入内容,并检查控制台,以找到与字符对应的 ID。

例如,在我们的位图字体中,“j”始终靠得太靠右。其字符 ID 为 106。因此,请在 JSON 文件中找到 "id": 106,并将其 xoffset 从 -1 更改为 -10。

7. 布局

如果您有多个文本块,并且希望它像 HTML 一样从上到下流动,则必须手动定位所有内容,这与使用 CSS 自行绝对定位每个 dom 元素类似。您能想象一下在 CSS 中这样做吗?

    * { position: absolute; }

这就是 3D 文字布局的效果。在详情视图中,标题、作者、说明和时长都是一个新的 TextBitmap 对象,分别具有各自的样式、颜色和缩放比例等:

3D 布局
author.group.position.y = title.group.position.y - title.height - padding;
description.group.position.y = author.group.position.y - author.height - padding;
duration.group.position.y = description.group.position.y - description.height - padding;

这假设每个 TextBitmap 组的本地原点与 TextBitmap 网格的顶部垂直对齐(请参阅 text-bitmap.js 更新中的居中位置)。如果您之后更改了其中任何对象的文本,并且该对象的高度发生变化,您还需要重新计算这些位置。在这里,我们只修改了文本的 y 轴位置,但在 3D 场景中,我们可以在 z 轴方向上推拉文本,并围绕 x 轴、y 轴和 z 轴旋转文本。

总结

WebVR 中的文本和布局在像 HTML 和 CSS 一样简单且被广泛使用之前,还有很长的路要走。但有效的解决方案是存在的,而且 WebVR 提供了比传统 HTML 网页更多的功能。WebVR 目前已经存在。未来可能会有更好的工具。在此之前,您可以试用一下,再接再厉。在没有通用框架的情况下进行开发可以产生更多独特的项目,这令人兴奋。