前言
混迹 VR 届的发烧友兼开发者们一定不要错过这款 Facebook 推出的跨端 VR 开发框架 --React360, 称为 360 全景体验框架更为准确, 因为其前身是 Facebook 和 Oculus2017 年发布的一个叫作 "Racet VR" 的 JS 库, 用来在 web 端创建 3D 和 VR 体验. 后来 Oculus 使用该框架的原生 C++ 版本构建自己部分应用, 随着时间推移, 由于要求框架解决不同需求, 项目的 APIs 开始发生分叉. 为了避免两个系统的混淆, 开源框架重命名为 React360, 这更好地反映它的使用场景: 创建横跨 PC, 移动端, VR 设备上的沉浸式 360 体验.
可以先看一下官方示例效果, 这是一个稍复杂的应用, 加入了 3D 模型, 在 3D 模型和 2D 面板间共享数据.
体验
工作原理
官方提供了构建工具 react-360-cli, 内部使用和 ReactNative 一样的打包工具 Metro, 基于 JS Bundle 在自己的 JS Runtime 中进行解析, 通过事件机制与客户端通信, 其实该框架还有很多方面相像或依赖于 RN. 构建一个 React360 应用程序需要完成两部分, 需要渲染的 Raect 组件和 Runtime 定义(这种角色划分直接借鉴于 React Native). 这也很清楚地反映出 React360 的工作流程, 可以参见下图:
(工作流程图)
基于 JavaScript Core/V8 引擎, React360 提供了 Runtime 需要的 APIs, 在客户端 (头戴设备, 移动端, 浏览器等) 完成构建界面, Web 端的渲染底层依赖于 Three.JS, 这是业界较为成熟的 3D 图形框架, 一般需要手动设置 3D 网格和纹理, 而框架中的 react-360-Web 模块隐藏了这些细节. 当创建了新的 React 组件, 框架会指示 Runtime 将它们添加到 3D 场景中, 当用户提供输入将作为事件通过 Runtime 传递给 React, 这两部分相互合作形成一个凝合系统. 如果想在系统中分享数据, 就需要借助框架提供的 Native Modules.
需要注意的是, 由于 JS 运作在浏览器中是单线程的, 应用中任何阻碍行为都有可能造成渲染延迟, 这对于 VR 这种即时性很强的体验是十分致命的, 所以框架将 React 组件和渲染过程放在分离的上下文中情有可原.
默认情况下, React360 使用 Web Worker 执行你的 React 代码, 而不是标准浏览器, 这就意味着在组件定义的文件中访问不到原生 Windows.location 这类 APIs. 并不是严格意义上的无法访问, 事实上当你打印 Windows 对象时 React360 提供了一个 DedicatedWorkerGlobalScope 类型实例, 它包装了 Windows 的一些内容.
Surfaces
Surfaces 实际上是一个载体, 允许你添加 2D 内容到 3D 场景, 开发者依据像素定义 Surfaces 宽高, React360 获取信息产生合适尺寸的对象, 官方介绍了两种类型的 Surfaces,Cylinder 和 Flat. 一个 Cylinder Surface 让 2D 内容投射到半径为 4m 的 Cylinder 内部, 其实是假想的圆柱模式. 一个 Flat Surface 位于 4m 半径的球体外侧, 一个假想的球体模式. APIs 也提供了像 yaw(垂摇),pitch(纵摇),roll(横摇)这些物体自由度控制信息.
为了将 React 组建附着在 Surface 上, 需要使用 AppRegistry 注册组件, 又一次与 ReactNative 相似. 这会告知 Runtime 你的组件通过 id 字段被唯一确定.
AppRegistry.registerComponent('MyAppName', () => MyAppName);
同时在 Runtime 文件中引用.
- r360.renderToSurface(
- r360.createRoot('MyAppName'),
- r360.getDefaultSurface(),
- 'default' /* 可选项, 引用的 surface 的名称 */
- );
- Components
官方提供了呈现 2D,3D 内容的展示组件和交互按钮组件.
View:UI 构建最基本的元素, 被用来组织实体或其他 View 元素, 也是输入事件的容器.
Image: 呈现 2D 图像
ENtity: 渲染 3D 对象, 支持 obj,mtl,gltf 格式文件
VrButton: 是一个实用程序类, 是捕获事件的包装器. 可以检测各种输入设备上单击类型操作, 这是通过一个可以监听按键事件的内部状态机做到的.
Layout
支持 2D Surface 布局, 完全以 Flexbox 格式布局, 又是一个和 RN 相似的点. 支持 3D Space 布局, 使用 Entity 组件时候, 通过 transform 完成 3D 对象放置, x 轴指向用户右侧, y 轴指向上方, z 轴指向用户后方.
APIs 官方提供了常见的 APIs, 例如来自 React Native 的 Animated; 键值对存储系统 AsyncStorage; 值得一提的是提供的 ControllerInfo 可以被用来响应控制器的 connect/disconnect 事件, 获取关于所连接的游戏手柄和控制器的静态信息, 比如唯一标识符, 按钮, 轴数等信息. 环境 API Environment 用来改变场景的背景, 包括图片, 音频, 视频.
实例解读
利用 react-360-cli 生成的项目中主要有这三个文件:
index.JS: 放置应用的主要代码, React 组件的地方, 在这里可以组织拆分多个组件
client.JS: 也就是 Runtime 的配置, 这部分连接浏览器环境和 React 应用. 根据代码示例看到主要完成三件事:(1)创建 React360 一个新实例, 加载并附加 React 代码到 DOM 特定位置, 这里也是传递初始化选项的地方.(2)将你的代码挂载到 3D 场景中, 在 index.JS 中声明的挂载点附着在应用程序的默认曲面.(3)添加背景信息, 这个部分可选, 允许代码仍在加载过程中展示图片, 让用户尽快看到一些内容.
index.html: 提供安装 JS 代码的挂载点.
- import {ReactInstance} from 'react-360-web';
- function init(bundle, parent, options = {}) {
- const r360 = new ReactInstance(bundle, parent, {
- fullScreen: true,
- ...options,
- });
- r360.renderToSurface(
- r360.createRoot('SlideshowSample', {
- photos: [
- {uri: './static_assets/360_world.jpg', title: '360 World1', format: '2D'},
- {uri: './static_assets/360_world2.jpg', title: '360 World2', format: '2D'},
- // Add your own 180 / 360 photos to this array,
- // with an associated title and format
- ],
- }),
- r360.getDefaultSurface(),
- );
- }
- Windows.React360 = {init};
- Native Modules
前面说过 React 组件运行在单独上下文中, 那么如何与主窗口通信, 官方提供了 Native Modules 模块, 让 React 代码有了回调到 Runtime 的能力, 包括在加载中存值, 请求有关连接控制器信息或操纵渲染环境. Native 模块被创建在 Runtime 代码中, 使用 Native Module 需要自定义类, 继承自 Module, 使用前需注册, 这个示例模板代码演示了 Native Modules 的许多用法
- import {Module} from 'react-360-web';
- class MyModule extends Module {
- constructor() {
- // 使这个模块在 NativeModules.MyModule 可用
- super('MyModule');
- }
- // 这个方法将被暴露到 React 应用一侧
- doSomething() {
- }
- }
- const r360 = new ReactInstance(
- 'MyApp.bundle?platform=vr',
- document.getElementById('container'),
- {
- // 在初始时刻注册自定义模块, 接收 Native Module 实例, 或一个返回实例的函数(需要传递上下文)
- nativeModules: [
- new MyModule(),
- ctx => new MyModule(ctx)
- ]
- }
- );
通常有两种使用场景, 暴露常量和普通到 React(同步), 回调函数或返回 Promise 方法 (异步). 这一段代码同时演示了这几种使用场景, 这是一个发送浏览器信息到 React 侧的应用示例, 在注册阶段, 模块构造时常量生成并添加模块实例的 userAgent 属性上, 这个值被直接传递给 React. 第二个例子是暴露了同步 setTitle() 方法, 只需要一个字符串设置窗口标题栏. 剩下两个异步方法展示了异步数据如何返回到 React. 当 getBatteryLevel()在 React 侧被调用, 开发者传递的回调在数据可用时触发, 调用上下文提供的 invokeCallback, 将参数放置在数组中, 你可以给回调传递任意数量的参数. 尽管回调是处理异步任务的一种方式, 但我们更偏向于用 Promise 创建有组织可读性强的异步逻辑链. 通过 Native Module, 你可以使用 $ 符号前缀形式来暴露这种行为, 两个回调 ID 会作为 Promise 的 resolve, reject 自动传递给 Runtime, 该方法会返回一个 Promise 到调用端.
- import {Module} from 'react-360-web';
- export default class BrowserInfoModule extends Module {
- constructor(ctx) {
- super('BrowserInfo');
- this._rnctx = ctx;
- this.userAgent = navigator.userAgent;
- }
- /*
- */
- setTitle(title) {
- document.title = title;
- }
- getBatteryLevel(cb) { // 读取 Windows 信息
- const getBattery = navigator.mozGetBattery || navigator.getBattery;
- getBattery
- .call(navigator)
- .then(
- battery => {
- // extract the level and return it
- return battery.level;
- },
- e => {
- // if an error occurs, return null
- return null;
- }
- )
- .then(level => {
- if (this._rnctx) {
- this._rnctx.invokeCallback(cb, [level]);
- }
- });
- }
- $getConfirmation(message, resolve, reject) {
- const result = Windows.confirm(message);
- if (this._rnctx) {
- if (result) {
- this._rnctx.invokeCallback(resolve, []);
- } else {
- // When rejecting a Promise, a message should be provided to populate
- // the Error object on the React side
- this._rnctx.invokeCallback(reject, [{message: 'Canceled the dialog'}]);
- }
- }
- }
- }
后记
对于 React360 的整体一览, 官方文档还是对在 Web 端介绍比较多, 官方开发团队在 GitHub 也比较活跃, 所以有问题可以及时 issue 都会有人回复. Facebook 在几年前收购了 Oculus 足已看出其进军 VR 届的雄心已经初见倪端, 目前市面上许多 App 对 360 全景图的应用也万象回春, 微博的全景图借助手机的陀螺仪和重力传感器在不点击图片详情的情况下跟随用户手势动态变化, 自如的 VR 看房, 在我们 App 里也引入了全景酒店实景体验. 在昂贵的 VR 设备消费者负担不起的情况下, 360 度全境体验正是 VR 在当今阶段最普及的形态, 虽然只是纯粹的平面图像, 却也一定程度上营造了沉浸式感受, 而 React360 在静态全景的基础上引入了多种交互, 这更加方便消费者了解需求, 相信 360 全景的未来还能做得更多.
参考
https://facebook.github.io/react-360/
来源: https://www.cnblogs.com/venoral/p/10914222.html