HTML5+WebGL 的加油站 3D 可视化监控
前言
随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA。SCADA 系统的推广使用,大大提高了我国加油站的监控效率,本文所讲的则是通过对加油站的可视化建模,结合 HT 的 3D 可视化以及 2D 监控面板来实现对加油站的可视化监控。三维可视化监控系统是将三维的可视化技术和数据采集与监控技术融合,充分发挥了两种技术的核心优势,并通过数据库进行数据共享,共同构成一种全新的 SCADA 系统。该系统中也结合了海康的摄像头监控,通过调用海康提供的摄像头地址,实时的将视频流传输到前台,并且展示在 2d 页面上。在真实的系统中,每个加油机以及加油罐都有自己对应需要展示的数据,这个可以根据自己需要展示的内容来设计 2d 面板,之后根据后台传来的数据进行展示。数据采集与监控系统通过各类的传感器实时采集监控对象的各类数据,上传数据库并实时共享给三维可视化技术搭建的监控对象的三维可视化模型及场景,最后通过监控系统直观的展示出来,极大的提高了监控对象数据的表达能力和工作人员的工作效率。
该系统中实现了对加油机,油罐的监控,以及对加油站内的摄像头进行调取并显示,本文会讲解使用 HT 来搭建该系统的步骤,以及对场景中使用到的部分关键代码进行说明。
预览地址:基于 HTML5 WebGL 的加油站 3D 可视化监控 http://www.hightopo.com/demo/gas-station-demo/
界面效果预览
视频监控效果
加油机监控效果
油罐监控效果
加油站切换效果
因为系统中对许多的加油站进行建模,所以系统中可以根据 url 地址的 stationCode 来区分不同的加油站,即 stationCode 为该加油站的唯一标识,当然该系统可以嵌入到任何第三方的系统中,HT 只需要第三方页面给一个 dom,就可以将该页面放到该 dom 中,例如上图 3d 场景,ht 可以通过 g3d.addToDOM(el) 来将 3d 场景的 dom append 到 el 这个 dom 下,g3d 为 ht 中 ht.graph3d.Graph3dView 的实例,具体可查看 3D 手册
HT 搭建系统步骤
1.制作模型
在 ht 的 3D 场景中部分简单的建模可以根据 ht 的 api 来进行搭建,例如墙面,管道,六面体,地板等等基本 3d 模型,例如如果想制作一个三维的球体模型,则可以通过以下代码:
1 var node = new ht.Node(); 2 node.s({ 3 "shape3d": "sphere" // 此处指定该 node style 的 shape3d 为 sphere 即球体的意思 4 });
如果需要使用代码来进行较为复杂些的建模,则可以通过指定模型的顶点信息来进行搭建,大体可理解为 3d 中的模型都是由三角面进行拼接而成的,所以指定该模型的所有三角面就可以构造出该模型,三角面又是由三个顶点信息构成,所以指定模型的顶点信息也可以构建模型,具体可以参考 建模手册
但是在我们这个加油站可视化监控系统中,我们的加油机模型以及油罐和加油站外景的模型都是十分复杂的模型,如果采用上述两种方法:
- 通过第一种简单的墙面,球体,六面体等模型拼接出加油站是不现实的,因为模型没有那么全面,而且贴图部分也不好分开贴,所以不可行。
- 通过指定顶点信息来构造加油站的所有模型,这部分虽然在理论上是可行的,但是计算顶点信息需要大量的工作,可想而知代码部分的工作量是不小的,所以不可行。
目前 ht 可以支持 obj 模型的导入,所以设计师可以根据加油站拍摄的外景图片对加油站场景以及加油机,油罐等的模型进行建模,obj 模型可以使用主流的 3dMAX 等的建模工具进行搭建,之后导入到 ht 中进行显示。对于系统中需要交互的模型则要分开进行建模,例如加油机模型不可和加油站场景的 obj 模型在同一个 obj 中,例如下图分开方式:
2.搭建场景
上一步中我们已经得到了场景所需要的所有模型,在 ht 中可以通过 ht.Default.loadObj(objUrl, mtlUrl, params) 来加载 obj 的模型,之后通过 ht.Default.setShape3dModel(name, model) 来注册模型,loadObj 用来读取模型的顶点信息以及贴图部分的信息,就是上一步中所指的第二点通过顶点信息来构造模型,此时顶点信息已经由 obj 模型提供,所以拿到顶点信息,贴图等信息之后,可以通过 setShape3dModel 来注册模型,具体使用方法请参考 OBJ 手册,之后可以通过 ht 的 node 图元来使用该模型,具体使用方法如下:
1 var node = new ht.Node(); 2 node.s({ 3 "shape3d": name 4 });
上面的 name 就是通过 ht.Default.setShape3dModel(name, model) 中的 name 得到的,表示此图元使用该模型来展示。
构成该监控系统的还有用来展示加油机,油罐模型的具体监控参数的面板,ht 中所有的 2d 都为矢量,所以放大不会失真,2d 面板也是通过一个个图元进行摆放展示,通过调整每个图元的样式来美化图元,具体的样式可参考 风格手册。
油罐 2d 面板展示如下:
3.对接数据
上一步中我们已经把需要展示的模型以及需要展示的监控数据进行了设计,并且在场景中进行了摆放,所以该步骤中则需要对数据进行对接,目前对接部分包括:
- 2d 面板两侧的数据对接
- 视频监控的对接
第一条的对接可以通过 socket 或者 ajax 来进行,将后台数据传输到前台之后动态绑定到界面上显示即可,ht 中通过数据绑定来驱动界面上内容的动态刷新,具体的绑定操作可以查看 数据绑定手册。
系统中摄像头监控部分主要是通过将第三方的视频 dom 嵌入到 ht 的图纸中,ht 中可以通过 renderHTML 来嵌入 dom,嵌入 dom 的原理其实也是在图纸的对应位置加入一个 node 图元,根据图纸的缩放值(zoom),以及横向偏移值(tx),纵向偏移值(ty) 的图纸信息以及该图元的 position, width, height 的图元信息来动态的计算 dom 的宽高和 dom 的位置,具体代码可以参考如下:
1 let rect = node.getRect(), // 获取该 node 的包围矩形信息 2 zoom = graphView.getZoom(), // 获取图纸的缩放值 3 tx = graphView.tx(), // 获取图纸的横向偏移值 4 ty = graphView.ty(); // 获取图纸的纵向偏移值 5 // 下面操作为对 node 的包围矩形进行缩放 6 rect.x *= zoom; 7 rect.y *= zoom; 8 rect.width *= zoom; 9 rect.height *= zoom; 10 11 // div 的 left 即为下面的 x 坐标, top 即为下面的 y 坐标 12 let x = tx + rect.x; 13 let y = ty + rect.y; 14 15 div.style.position = 'absolute'; 16 div.style.width = rect.width + 'px'; 17 div.style.height = rect.height + 'px'; 18 div.style.left = x + 'px'; 19 div.style.top = y + 'px';
上面代码展示了动态摆放 dom 到 ht 图纸的原理,所以用户可以根据自己的需求将 dom 元素放到 2d 图纸中去,该系统中的监控模块 dom 就是通过该方式的原理进行动态摆放,从下图可以看出 dom 叠加的效果。
4.制作动画效果
该系统中动画效果比较简单,主要就是点击加油机或者油罐时,将视角飞向该物体,并且二维面板上显示对应的监控数据,视角的切换主要是修改 3D 场景的 eye 以及 center 的数据,但是 ht 中提供了更为方便的操作函数 flyTo,所以主要代码即为下面一行:
1 // node 即为要飞向的节点 例如加油机 2 // 第二参数为配置参数 3 g3d.flyTo(node, { animation: true, direction: [-16, 6, 8], distance: 600 });
上述第二个参数具体可参考上述所提供的 3D 手册,direction:默认undefined,眼睛处于目标的方向(相对目标,受到目标自身旋转影响,distance :默认undefined(未定义的话则使用下面的ratio模式计算距离),浮点类型,表示眼睛跟中心的固定距离,上述所用到的两个参数解释即为此。
该函数还有一个使用方法为当第一参数传值为 null 空时,视角会调整看向场景内所有节点,所以利用此功能可能看到加油站的全景。
系统中还有一个效果是虚化背景,虚化背景的原理就是修改场景中所有模型的透明度,例如该系统中通过遍历所有节点,将当前节点的透明度设置为 0.1,则在视觉上我们看到的场景即为虚化的场景,具体节点的样式属性可以参考上面已经给出的风格手册,关键代码如下:
1 // 遍历场景中所有图元 2 dataModel.each((d) = >{ 3 // opacityMap 用来记录当前某个节点没有虚化之前的透明度值 4 if (!opacityMap[d.getId()]) { 5 opacityMap[d.getId()] = { 6 'shape3d.opacity': d.s('shape3d.opacity'), 7 'shape3d.transparent': d.s('shape3d.transparent'), 8 'all.opacity': d.s('all.opacity'), 9 'all.transparent': d.s('all.transparent'), 10 'left.opacity': d.s('left.opacity'), 11 'left.transparent': d.s('left.transparent'), 12 'right.opacity': d.s('right.opacity'), 13 'right.transparent': d.s('right.transparent'), 14 'front.opacity': d.s('front.opacity'), 15 'front.transparent': d.s('front.transparent'), 16 'back.opacity': d.s('back.opacity'), 17 'back.transparent': d.s('back.transparent'), 18 'top.opacity': d.s('top.opacity'), 19 'top.transparent': d.s('top.transparent'), 20 'bottom.opacity': d.s('bottom.opacity'), 21 'bottom.transparent': d.s('bottom.transparent'), 22 '3d.selectable': d.s('3d.selectable') 23 }; 24 } 25 // 设置当前节点的透明度 opacity 为需要虚化至多大透明度 系统中为 0.1 26 d.s({ 27 'shape3d.opacity': opacity, 28 'shape3d.transparent': true, 29 'all.opacity': opacity, 30 'all.transparent': true, 31 'left.opacity': opacity, 32 'left.transparent': true, 33 'right.opacity': opacity, 34 'right.transparent': true, 35 'front.opacity': opacity, 36 'front.transparent': true, 37 'back.opacity': opacity, 38 'back.transparent': true, 39 'top.opacity': opacity, 40 'top.transparent': true, 41 'bottom.opacity': opacity, 42 'bottom.transparent': true, 43 '3d.selectable': false 44 }); 45 });
上面代码执行之后场景中所有的节点就被虚化,因为每个节点虚化所需要设置的透明度属性不同,所以一共有上面十几种样式属性需要判断设置,具体的样式名称可以参考上文提出的风格手册,以下为虚化效果:
场景中有双击便利店进入便利店内景的操作,具体交互以及效果如下图:
5.优化场景
当 3d 场景中点或者面的数量较多时,3d 面板,公告板部分较多时,ht 中有几种优化策略可以进行优化,我们知道 3d 场景中的所有模型都是由三角面构成的,而三角面又是由三个顶点构成的,所以如果场景中的点或者面比较多的时候场景会出现一定的卡顿,GPU 渲染会比较费时,在 ht 中可以通过在控制台输入 g3d.showDebugTip() 来显示当前场景一共有多少面和顶点,具体效果如下:
其中 Vertices 为点的数量,Faces 为面的数量。
所以在 ht 中可以有以下 4 种直观优化策略可以优化:
- 减少模型的面数
- 使用批量处理场景中大量的相同图元
- 对 3d 面板使用缓存
- 当场景视角距离较远时隐藏部分细节图元,或者当场景视角距离某个模型很近时,隐藏看不见的图元以提高性能
第一种情况可以在设计建模时通过各种减面的手段来减少模型的面数,这一部分优化的空间是最大的,也是效果最明显的。
第二种情况可以使用批量,批量能提高性能的原理在于,当图元一个个独立绘制模型时性能较差,而但一批图元聚合成一个大模型进行一次性的绘制时, 则会极大提高WebGL刷新性能,具体可参考 批量手册。
第三种情况使用 shape3d.image.cache 这个属性来开启面板的缓存,当我们一个场景中如果需要使用大量的类似公告板的功能,我们可以利用上面的属性对该节点设置缓存,具体使用方法可以参考 3D手册。
第四种情况我们可以在眼睛距离场景很远的时候隐藏部分细节图元,类似地图缩放到很小的时候,具体的城市会隐藏掉,放大到具体模型细节时,其它看不见的图元可以相应设置隐藏,这样可以提高不少的性能。
手机端效果