基於 HTML5 WebGL 的加油站 3D 視覺化監控

圖撲軟體發表於2020-04-06

前言

隨著數字化,工業網際網路,物聯網的發展,我國加油站正向有人值守,無人操作,遠端控制的方向發展,傳統的人工巡查方式逐漸轉變為以自動化控制為主的線上監控方式,即採用資料採集與監控系統 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 中的模型都是由三角面進行拼接而成的,所以指定該模型的所有三角面就可以構造出該模型,三角面又是由三個頂點資訊構成,所以指定模型的頂點資訊也可以構建模型,具體可以參考 建模手冊

但是在我們這個加油站視覺化監控系統中,我們的加油機模型以及油罐和加油站外景的模型都是十分複雜的模型,如果採用上述兩種方法:

  1. 通過第一種簡單的牆面,球體,六面體等模型拼接出加油站是不現實的,因為模型沒有那麼全面,而且貼圖部分也不好分開貼,所以不可行。
  2. 通過指定頂點資訊來構造加油站的所有模型,這部分雖然在理論上是可行的,但是計算頂點資訊需要大量的工作,可想而知程式碼部分的工作量是不小的,所以不可行。

目前 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.對接資料

上一步中我們已經把需要展示的模型以及需要展示的監控資料進行了設計,並且在場景中進行了擺放,所以該步驟中則需要對資料進行對接,目前對接部分包括:

  1. 2d 皮膚兩側的資料對接
  2. 視訊監控的對接

第一條的對接可以通過 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 種直觀優化策略可以優化:

  1. 減少模型的面數
  2. 使用批量處理場景中大量的相同圖元
  3. 對 3d 皮膚使用快取
  4. 當場景視角距離較遠時隱藏部分細節圖元,或者當場景視角距離某個模型很近時,隱藏看不見的圖元以提高效能

第一種情況可以在設計建模時通過各種減面的手段來減少模型的面數,這一部分優化的空間是最大的,也是效果最明顯的。

第二種情況可以使用批量,批量能提高效能的原理在於,當圖元一個個獨立繪製模型時效能較差,而但一批圖元聚合成一個大模型進行一次性的繪製時, 則會極大提高WebGL重新整理效能,具體可參考 批量手冊

第三種情況使用 shape3d.image.cache 這個屬性來開啟皮膚的快取,當我們一個場景中如果需要使用大量的類似公告板的功能,我們可以利用上面的屬性對該節點設定快取,具體使用方法可以參考 3D手冊

第四種情況我們可以在眼睛距離場景很遠的時候隱藏部分細節圖元,類似地圖縮放到很小的時候,具體的城市會隱藏掉,放大到具體模型細節時,其它看不見的圖元可以相應設定隱藏,這樣可以提高不少的效能。

手機端效果

 

相關文章