WebAssembly + Forge实战 – 整合Forge AR/VR ToolKit + Unity场景至前端框架

3次阅读

共计 5739 个字符,预计需要花费 15 分钟才能阅读完成。

在浏览器环境下,解释运行 JavaScript 脚本实现高阶功能早已是家常便饭,然而,Web 前端日新月异的需求已逐渐无法完全依赖 JavaScript 实现。幸运的是,打破瓶颈的新技术已逐渐成熟,它就是 WebAssembly。
什么是 WebAssembly
WebAssembly 是一项神奇的技术,简而言之就是一种底层的类汇编语言,其编译后的二进制模块 wasm 可在浏览器中运行以接近原生的性能运行 CC++、C#、Java、GO、PHP、Rust 等等语言的代码!自 2015 年颁布、2017 年初正式发布最小功能版本以来,WebAssembly 迅速开始盛行,并已得到主流浏览器的广泛支持,详细支持情况可以参见下图或 MDN:图片描述图片描述(数据采于 2019-01-25)
需要强调的是:WebAssembly 并不旨在取代 JavaScript 或任何现有的 H5/ES6 技术,而是与他们共存 – 我们耳熟能详的 WebGL、Web Audio 等组件都是 WebAssembly 模块在浏览器端的运行时,在浏览器端实现所需功能图片描述
优势与异同
那么问题来了 – WebAssembly 究竟和 asm.js、Dart 等类似技术有何不同?我们早已可以通过 Emscripten 编译 asm.js 在浏览器中跑 c /c++ 了,为什么还需要 WebAssembly 呢?相比之下,WebAssembly 主要具备以下优势:

WebAssembly 模块不论在加载速度和性能上都有明显优势 – 它以二进制码的形式在浏览器中原生运行,无需像 asm.js 那样将原始语言编译成 JavaScript,远超 JavaScript 引擎解释脚本的运行速度,即便首屈一指的 Chrome V8 有 JIT 加持也无济于事。
WebAssembly 并不是基于现有组建的扩展,而是一个 Web 开发新特性 / 标准,它有独立的路线图,不断有新特性加入进来。
不受 asm.js 等技术在 AOT 等层面的限制,特性拓展潜力极大,应用场景广泛,详见底部延伸阅读部分的介绍。

编译与运行
那么如此神奇的技术究竟如何编译运行?当下最主流编译器可谓就是 Emscripten 了,广泛应用于原始语言 ->LLVM 中间码 ->JavaScipt(asm.js)的编译。当然在 WebAssembly 全面企稳的今天,直接将原始语言编译成 WebAssembly(wasm)也不在话下。
较新版本的 Emscripten 支持跳过 LLVM 中间码 ->asm.js->wasm 的过度,直接编译 wasm,以 c 语言为例可通过如下命令直接编译:
# `WASM=1`: 仅生成 wasm 模块(默认为 LLVM 中间码),`SIDE_MODULE=1`: 仅编译用户代码,而不包含 printf、memalloc 等函数
./emcc hello-world.c -O3 -s WASM=1 -s SIDE_MODULE=1 -o hello-world.html
编译生成的结果包括:

hello-world.wasm: wasm 模块二进制码
hello-world.html: 展示页面
hello-world.js: 读取 wasm 模块的 JavaScript

其中编译生成的 hello-world.js 是帮助我们在页面中调用加载 wasm 模块的脚本,我们也可结合 Fetch API 在自己的代码进行加载:
fetch(‘path/to/wasm’)
.then(response => response.arrayBuffer()) \\ 将 wasm 文件响应转为二进制数组
.then(bits => WebAssembly.compile(bits)) \\ 编译模块
.then(module => { return new WebAssembly.Instance(module) }); \\ 生成模块实例
可通过自带的 emrun 工具在指定浏览器中运行编译结果,或直接托管在 Web 服务器上:
emrun –browser /path/to/browser/executable hello-world.html
实战 Unity+WebAssembly
接下来我们就进入今天的实战:将经由 Autodesk Forge Model Derivative 服务轻量化的模型,通过 Forge AR/VR Toolkit 导入 Unity 场景,结合 C#/JSLIB 脚本与 Unity 插件,编译为 WebAssembly,并集成至我们的前端框架中!图片描述

参考该教程,为 Unity3D 项目配置好 Forge AR/VR Toolkit

将轻量化后的模型导入至 Unity 场景,如图所示填入模型的 URN 和从 Forge 服务端获取的 Access Token

图片描述
需注意将.NET 运行时版本调整为 4.5 以便支持 TLS 1.2

图片描述

完成场景的建模与开发,本例结合 Cinemachine 的 Freelook Camera 插件与简单的 c# 脚本实现由键鼠操控的场景漫游。Cinemachine 是一套强大的 Unity 相机管理工具,可利用其路径(Path)路点(Waypoint)等特性(并结合 Timeline)轻松制作强大的预制路线漫游等效果。
通过较新 Unity3D(2017/5.6+)直接将场景编译为 WebAssembly,设定发布目标平台为 WebGL,并在发布设定中将连接器目标设为 WebAssembly,开始 Build 编译:

图片描述图片描述

编译结果包括:

html:展示页面
Build 目录:< 项目名 >.json(包括运行所需的参数与设置)、UnityLoad.js(浏览器加载 wasm 所需的脚本)、< 项目名 >.*.unityweb(发布设定中指定格式的压缩包,包含 wasm 模块与场景资源等)
Template:展示页面依赖

与前端框架整合

浏览展示页面确认实际效果无误后,将 Build 目录导入前端项目的静态资源路径(如./src/assets)
接下来将分别介绍针对 Vue、React、Angular 框架与无框架的整合

React
推荐使用 react-unity-webgl 加载 Unity WebAssembly:
npm install vue-unity-webgl
在 React 组建中调用
import Unity, {UnityContent} from “react-unity-webgl”;
class App extends Component {
unityContent:UnityContent = new UnityContent(
“Build/forge_sample.json”, \\ 引用编译结果,将所有编译结果置于相同路径下
“Build/UnityLoader.js” \\ 并确保浏览器会话可以 http 协议访问
);

\\…
render() {
\\…
<Unity unityContent={this.unityContent} />
}
}
与 Unity 中的对象通讯
this.unityContent.send(
“Unity 对象名称 ”,
“C# 或 JSLIB 脚本函数名称 ”,
1 \\ 参数值
);
在 Unity 脚本中与 JavaScript 通讯:
[DllImport(“__Internal”)]
private static extern void EventName (int arg);

public void CallAnEvent (int arg) {
EventName(arg);
}
在 Unity 中创建 JSLIB 脚本(如 Assets/Plugins/WebGL/forge-sample.jslib)注入事件:
mergeInto(LibraryManager.library, {

EventName: function(arg) {
ReactUnityWebGL.EventName(arg);
}
});

在前端监听该事件
this.unityContent.on(“EventName”, arg => {
\\…

– 同理我们可以在前端监听 Unity 的生命周期事件:

public class NewBehaviourScript : MonoBehaviour {\… [DllImport(“__Internal”)] private static extern void EventName (); void OnSceneLoaded (Scene scene, LoadSceneMode mode) {
EventName();
}
\… }

##Vue
– 推荐使用 [vue-unity-webgl](https://github.com/votetake/vue-unity-webgl) 组件加载 Unity WebAssembly:

npm install vue-unity-webgl

– 在 Vue 组件中调用

<template> <div>
/*…*/
<unity src=”Build/forge_sample.json” unityLoader=”Build/UnityLoader.js”></unity> \\ 引用编译结果,将所有编译结果置于相同路径下,并确保浏览器会话可以 http 协议访问
/*…*/
</div></template><script>import Unity from ‘vue-unity-webgl’\…export default {components: { Unity} \…}<script>

##Angular 与无框架
– 对于适用于 Angular 的 Unity 组件库,我们只找到了[ng-unity](https://github.com/cHullaert/ng-unity),但在实测中出现报错,似乎是由于其内置的 `UnityLoader.js` 与我们的模块并不兼容(该库不能引用外置 Loader),因此我们结合了无框架的引用方式来做示范
– 在页面中引用编译生成的 `UnityLoad.js`

<script language=”JavaScript” src=”assets/Build/UnityLoader.js”></script>

– 组件页面中加入容器元素

/ app.component.html/ <div id=‘unityContainer’></div>

– 在组件中载入模块

\app.component.tsdeclare var UnityLoader: any; \ 声明 UnityLoader 为任意类 export class AppComponent implements AfterViewInit{private unityInstance: any; \… ngAfterViewInit(){
(<any>window).UnityLoader = UnityLoader; \\ 将 UnityLoader 对象暴露为窗体具柄
this.unityInstance = UnityLoader.instantiate(‘unityContainer’, ‘./assets/Build/forge_sample.json’); \\ 引用编译结果,将所有编译结果置于相同路径下,并确保浏览器会话可以 http 协议访问

}
sendMessage(objectName: string, functionName: any, argumentValue: any) {
this.unityInstance.SendMessage(objectName, functionName, argumentValue); \\ 与 Unity 对象通讯
} \…}

– 运行结果如下
![图片描述][9]

# 调试与优化
编译后的 wasm 是二进制的,可以通过编译工具(如 [WABT](https://github.com/WebAssembly/wabt)、[Binaryen](https://github.com/WebAssembly/binaryen) 等)生成或转换为 `WebAssembly Text (wat) Format` – 人类可读的类汇编代码:

(module (func $i (import “imports” “imported_func”) (param i32)) (func (export “exported_func”)
i32.const 42
call $i
))

在浏览器中也可以查看 `wat`,并断点调试
![图片描述][10]

## 优化考量
– 将 wasm 等依赖在服务器端压缩,加速网络传输,以 Node 后台为例: https://blog.csdn.net/github_38140984/article/details/83011150
– 编译时的优化(如使用 WebGL2.0,.NET4.5 等),参考: https://docs.unity3d.com/Manual/class-PlayerSettingsWebGL.html#Optimization
– 使用 WebAssembly JIT:https://webassembly.org/docs/jit-library/

# 延伸阅读
– Forge AR/VR 介绍:https://segmentfault.com/a/1190000013672044
– Unity Cinemachine 插件学习笔记:https://blog.csdn.net/l773575310/article/details/78070808
– Emscripten-WebAssembly 专栏:https://segmentfault.com/blog/yunhuangbeiqing
– WebAssembly 入门: https://www.jianshu.com/p/bff8aa23fe4d
– WebAssembly 入门到入门:https://blog.csdn.net/m549393829/article/details/81839822
– WebAssembly 应用案例: https://blog.csdn.net/frf0lw4/article/details/79267457

[1]: /img/bVbnQqb
[2]: /img/bVbnQqh
[3]: /img/bVbnQqi
[4]: /img/bVbnQqp
[5]: /img/bVbnQqw
[6]: /img/bVbnQqA
[7]: /img/bVbnQqs
[8]: /img/bVbnQqt
[9]: /img/bVbnQqu

正文完
 0