前言
2019 年 VR, AR, XR, 5G, 工业互联网等名词频繁出现在我们的视野中,信息的分享与虚实的结合已经成为大势所趋,5G 是新一代信息通信技术升级的重要方向,工业互联网是制造业转型升级的发展趋势。本文所讲的 VR 是机械制造业与设备的又一次交流,当技术新星遇上制造潮流,无疑将成为制造业,工控业等行业数字化转型的重要驱动力。“5G + VR + 工业互联网”必将成为新的一年不变的话题,如何将当前工业中遇到的问题通过虚拟现实结合起来,让我们可以更近的去交流,去感受技术带给我们的变化。在今年苹果的发布会上,相信大家都知道苹果的 5G 手机没有问世,说明 5G 的应用和发展还处在快速发展的阶段,但是手机结合 AR 功能的 APP 已经早就问世,5G 的速度加上 AR, VR 的身临其境,让我们感受到的不仅仅是技术的革新,更是让我们感受到技术在不同领域的实际应用场景,我相信 2020 年新的一年必定是 “5G + VR + 工业互联网” 应用的又一个新的开始,本文接下来所讲的就是 HT for Web 结合 WebVR 开发的具体应用案例。
系统预览
预览地址:基于 HTML5 WebGL 与 WebVR 3D 虚实现实的可视化培训系统 http://www.hightopo.com/demo/vr-training/
VR 拆解还原
VR 操作
VR 场景切换
PC 端拆解还原
PC 端考试
系统介绍
该系统共分为三个实际应用层面:
文章主要讲解第三部分的 VR 模式,让我们了解如何结合 HT 来搭建 VR 场景。下面描述了 VR 中的主要操作,没有进入 VR 的时候不会出现如下所说的六个按钮操作,在点击进入 WebVR 时,系统自动显示出 VR 场景里的六个操作按钮,反之退出 VR 时,系统也会自动隐藏三维中的六个操作按钮,VR 中的主要操作如下:
系统开发
三维场景 HT 支持 obj 模型的导入,VR 场景所出现的设备零件均为 obj 模型,由于需要在之后进行设备的拆解,所以建模的时候需要分别对设备的各部分零件进行建模,而不是对设备整体进行建模,如果对设备整体建模那么在 HT 的场景中就是一个 Data 节点,从而不能对零件进行拆解,如果拆解开来,那么在 HT 中可以加载多个 obj 则就有多个 Data 节点,有多个零件的 Data 节点之后就可以对设备零件进行移动或者其它旋转操作,具体的 Data 在 HT 的含义可以参考 HT for Web 数据模型手册
如下为导入场景中的 obj 模型:
从上图可以看出我们导入 obj 之后零件之间是分散的,所以需要对零件的初始位置进行调整,从而调整出一个由许多零件构成的完整设备,当然调整不可能通过代码来调整,对应的有三维编辑器可以调整,进行拖拖拽拽将不同零件拼凑起来,如下为组合之后的设备整体:
VR 搭建 VR 场景的搭建是在第一步的基础上进行搭建,上面所说的只在 VR 场景中显示的按钮也是在场景中进行搭建,在正常的场景时候我们可以隐藏掉对应的节点,node.s('3d.visible', false) 上面的代码就是 HT 中在三维下面隐藏三维节点的代码,因为进入 VR 和离开 VR 的时候,HT 内部会派发出对应的状态告诉用户此时已经进入 VR 或者此时已经离开 VR,相应伪代码如下:
1 // graph3dView 为 HT 中的三维场景视图容器
2 // vr 获取挂载在 graph3dView 上的 vr 对象
3 var vr = graph3dView.vr;
4 vr.mp(function(e) {
5 // property 对应的 vr 事件类型,detail 此时事件的状态
6 var property = e.property;
7 var detail = e.newValue;
8 // present 代表此时进入或者离开 VR 场景
9 if (property === 'present') {
10 // 此时 detail 为 true 表示进入 vr,false 表示离开 vr
11 if (detail) {
12 // 执行显示 vr 场景中需要显示的节点操作
13 } else {
14 // 执行隐藏 vr 场景中需要隐藏的节点操作
15 }
16 }
17 });
上面 property 在 HT 总共会派发出以下几种类型,主要是包括 VR 的状态和手柄的操作类型:
VR 中有一个关键的配置就是比例尺,因为 VR 里面的单位是和现实中的长度单位是一致的,我们戴着头盔往前走 1m 那么对应在 HT 三维场景中需要往前走多远这需要一个对应关系,HT 提供的 VR 插件中会提供一个 measureOflength 的配置项,如下:
1 var vr_config = {
2 measureOflength: 0.01,
3 }
上面 0.01 代表的意思就是 HT 场景中的单位长度 1 代表现实场景的 0.01 米,所以如果此时现实场景你戴着头盔往前移动 1m,那么 HT 中对应的视角会往前移动 100 个单位,所以如果需要搭建 VR 场景要注意场景的模型建模比例和现实世界是相差多少,按照统一的比例来建模,不然在 VR 场景中会出现设备大小不一的问题,导致出现错觉,如下对比图,左侧是 0.01 的比例,射线的小点很小,右侧是是 0.001 的比例导致射线的小点变大。
HT 中已经对浏览器提供的 WebVR 相关接口的 API 进行了封装,包括获取设备 navigator.getVRDisplays() 这是进入 VR 世界的第一步,如果此时执行此代码返回的结果为空代表获取 VR 设备失败,那么之后更不用说了,以及获取手柄信息 navigator.getGamepads(),用户可以通过在浏览器控制台敲入上面两行代码,查看浏览器是否已经获取到了 VR 设备信息和 VR 手柄信息,如果返回为空则说明获取失败。HT 只要通过执行 graph3dView.vr.enable = true 就可以开启 VR,当然用户不用执行该代码,HT 提供的 VR 插件也会提供对应的配置项 vrEnable: true 来代表开启 VR,对应的配置也挂在在上面的 vr_config 对象内,如下:
1 var vr_config = {
2 measureOflength: 0.01,
3 vrEnable: true, // 代表开启 VR
4 }
在该展示的系统中有直接在 VR 中切换场景的功能,由于每个场景的 vr_config 中的配置项值可能会有差别,例如第一个场景的 measureOflength 比例尺的大小为 0.01,可能第二个场景的比例尺大小 measureOflength 就变成了 0.02,所以 VR 插件提供一个销毁的功能,用来销毁上一个场景的资源,销毁场景的资源包括清空上一个场景的所有节点,所以在加载新的场景时,不需要再执行清空场景节点的操作,即不需要执行 dataModel.clear(),因为 VR 提供的销毁功能已经都清空了,手柄和射线都是场景中的一个 Data 节点,所以在新的场景不需要额外的清除手柄和射线这两个节点,故插件帮你管理场景的节点。在调用销毁功能之后,可以调用 graph3dView 的序列化函数 graph3dView.deserialize('场景资源json地址') 来序列化新的场景 json 文件,在序列化完成的回调函数中,可以根据新的场景修改此时 vr_config 的值,然后再次调用 graph3dView.initVRForScene() 来再次初始化 VR 场景。相关的步骤伪代码如下:
1 // window.GVR 是在调用 graph3dView.initVRForScene() 之后初始化的一个全局 VR 插件变量 用于用户获取插件对象
2 window.GVR.destory();
3 // 执行新的场景序列化操作
4 graph3dView.deserialize('场景资源json地址',
5 function(json, dm, g3d, datas) {
6 // 修改新的场景比例尺为 0.02
7 window.vr_config.measureOflength = 0.02;
8 // 修改新的 VR 场景初始化视角
9 window.vr_config.vrEye = ht.Default.clone(g3d.getEye());;
10 // 再次初始化 VR 场景
11 graph3dView.initVRForScene()
12 });
当然 HT 提供的 VR 插件还有很多的配置项,方便用户更好的调整 VR 场景,包括刷地形,场景移动方式,场景操作方式都可以通过配置进行配置,利用 HT 进行 VR 搭建主要流程如下流程图所示:
通过上面的流程图,我们大体可以了解配合 HT 提供的 VR 插件如何进行快速的搭建 VR 场景。
目前谷歌浏览器和火狐浏览器都很友好的支持 VR,可以通过火狐官网提供的 WebVR Demo 在线感受下官方提供的 VR 场景。
拆解规则 从文章前面的部分效果图可以看到我们每个场景的设备都有拆解,并且每个设备的零件数量,零件位置,零件拆解的方向,偏移的长短都是不一致的,所以不可能通过代码来将上面的偏移长短,偏移方向写死,需要制定一套拆解规则来帮助我们可以更方便制作每个场景的拆解动画,这样只需要设计师根据与程序约定好的拆解规则进行配置就可以配置出不同场景不同设备的拆解动画。该系统的拆解分为两种情况:
单体移动示意图如下:
组合移动示意图如下:
在 HT 中可以通过 data.setDisplayName('名称') 来给节点设置名称,这里约定通过不同设备的名称,来获取到不同设备的偏移信息,例如 data.setDisplayName('1-0.5-1000') 该名称就是和设计师约定好的配置规则,1 代表拆解步骤的第一步执行,当然场景中可以有多个 1,即第一步同时拆解这些零件 0.5 代表朝着父节点的方向偏移自己位置和父节点位置连接线长度的 50%。1000 代表偏移的过程持续 1000 毫秒,当然之后可以约定旋转以及旋转的角度等信息。设计师知道这些配置规则之后便可以通过可视化编辑器进行不同零件的配置,这样程序方面只需要写一套通用的逻辑就可以对不同的设备进行拆解和还原。
系统中维护了一个队列和一个栈,队列用来记录拆解顺序,栈用来记录还原顺序。拆解的过程通过配置的序号,按顺序推进队列,采用队列的数据结构便是因为队列先进先出的特点,第一个压入队列的零件则第一个执行,最后压入队列的零件最后一个执行拆解顺序。拆解出队列的零件则同时压入栈,采用栈记录还原顺序是因为先进后出的特点,即第一个执行完拆解的零件,在还原的时候却是最后一个执行还原的动作。所以上述采用的不同数据结构便是为了更好的记录数据。以下为相关 js 伪代码:
1 // 记录拆解顺序
2 const queue = [];
3 // 记录还原顺序
4 const stack = [];
5 // each 循环中用来记录拆解队列 queue 顺序
6 dataModel.each((node) = >{
7 const displayName = node.getDisplayName();
8 if (displayName) {
9 const[index, distancePer, during] = displayName.split('-');
10 if (index !== void 0) {
11 if (queue[index]) {
12 if (queue[index] instanceof Array) {
13 queue[index].push(node);
14 } else {
15 const tempNode = queue[index];
16 queue[index] = [tempNode, node];
17 }
18 } else {
19 queue[index] = node;
20 }
21 }
22 }
23 });
相关逻辑如下流程图:
通过上面的约定,设计师可以使用可视化编辑器来配置不同零件的移动规则,大大提高了动画的制作效率。
代码分析 该部分主要对拆解还原动画的代码进行分析,主要采用向量和部分三角函数的概念来计算不同零件在三维空间的位置,初始的时候需要记录下每个零件在前面所有组合移动之后的初始移动位置向量,以及零件没有组合移动之前的初始位置向量,获取这两个位置向量目的是一是为了零件拆解在前面所说组合之后移动,和零件在拆解之后恢复到一整个设备形态的初始位置,两个位置向量都有重要的作用,以下为相关伪代码:
1 // Vector3 为 HT 封装的三维向量
2 const Vector3 = ht.Math.Vector3;
3 // 记录第一个重要位置向量
4 node.a('relativeP3Vec', new Vector3(node.p3()));
5 // node 当前零件节点
6 // moveQueue 为移动顺序在 node 之前的,并且为 node 节点的祖先节点
7 for (let i = 0, l = moveQueue.length; i < l; i++) {
8 const moveNode = moveQueue[i],
9 parentMoveNode = moveNode.getParent();
10 if (parentMoveNode) {
11 const[, distancePer] = moveNode.getDisplayName().split('-');
12 moveNode.a('defP3', moveNode.p3()) moveNode.p3(new Vector3().lerpVectors(new Vector3(moveNode.p3()), new Vector3(parentMoveNode.p3()), distancePer).toArray());
13 }
14 }
15 // 记录组合节点移动之后的第二个重要相对位置向量
16 node.a('relativeP3Vec', new Vector3(node.p3()));
17 // 逆序还原组合的父节点位置
18 for (let i = moveQueue.length - 1; i >= 0; i--) {
19 const moveNode = moveQueue[i];
20 moveNode.p3(moveNode.a('defP3'));
21 moveNode.a('defP3', undefined);
22 }
由于在场景拆解过程中需要设置设备零件节点不可选择,所以需要记录下不可选择之前的零件是否可选择状态,用来恢复节点初始状态,相关伪代码如下:
1 dm3d.each((node) = >{
2 node.a('defSelectable', node.s('3d.selectable'));
3 });
文中所示的线框效果为 HT 核心包支持的线框模式,可以通过如下代码进行配置:
dm3d.each((data) = >{
if (data.s('shape3d') && data.s('shape3d').startsWith('models/')) {
data.s({
'shape3d.transparent': true,
'shape3d.opacity': 0, // 目的为隐藏原本的模型
'wf.geometry': true, // 开启线框模式
'wf.combineTriangle': 2, // 线框三角面合并类型
'wf.color': 'rgba(96,172,252,0.3)' // 线框颜色
});
}
});
上述 wf.combineTriangle 主要包括
VR 软件以及硬件安装
本系统采用的 VR 硬件设备为 HTC VIVE 接下来讲的是安装 HTC VIVE 的过程和步骤。
第一步:撮合 HTC VIVE 和电脑主机
到 HTC 官网找到连接指南,然后按照步骤安装即可,我们只需看以下截图部分的目录即可。
第二步:下载软件
到 Steam 官网下载 Steam,下载完 Steam 可以在 Steam 中下载 Stream VR。
第三步:打开 Stream VR 检查设备状态
打开 Stream VR,会出现以下画面,这是用来表示 HTC VIVE 头显的工作状态的,通过图标我们即可查看头显、手柄控制器和定位器等配件的工作情况。
第四步:选择房间设置模式
如果您的房间位置比较大可以选择第一项,我选择的模式为第二项,站立模式。建议选择一种房间规模,可以完整的进行设置。
第五步:将头盔、两个手柄控制器放置在两个定位器可视范围内,建立定位
第六步:校准头盔中心点
该步为设置头盔默认的朝向。
第七步:定位地面
将两个手柄控制器放置在定位器可视范围内,然后点击电脑屏幕上的按钮“校准地面”,等待系统校准
第八步:进入 Steam VR 自带房间进行测试
设置完毕之后可以进入 Steam VR 自带的房间进行体验。
总结 当人们谈起 5G 时代的新应用,VR、AR 总是一大热门话题。4G 时代移动网络已经足以承载起高清视频,那么 5G 时代理所当然就能传输数据量更大的沉浸式 VR、AR 影像。因此,不少人将 5G 视为 VR、AR 崛起的踏板,随时随地身临天涯海角,似乎并非是遥不可及的梦。当前 4G 网络应用在 VR/AR 上会带来大约 70ms 的时延,这个时延会导致体验者存在眩晕感,而 5G 数据传输的延迟可达到毫秒级,可以有效解决数据时延带来的眩晕感,有助于 VR/AR 的大规模应用。目前随着 5G 网络的逐渐普及,VR/AR 产业正逐步走向复苏,市场热情在逐渐升温,虚拟现实游戏、虚拟现实现场直播等都是 5G 在 VR/AR 上的具体应用。在科技进步的今天,安全也是一个重要的话题,VR 结合仿真的应用也是大势所趋,仿真可以让用户真实切身感受,例如消防预警,管道预警,可以让用户在 VR 世界中体验消防灭火等消防员的操作,让用户沉浸在 VR 世界中感受到火灾来临时怎么进行实际操作。所以 VR 带来的应用远远不止仿真,模拟等体验,更多带来的是能为人们提供真实的实际作用,而不是噱头。
程序手机端运行截图: