oom的Web客戶端可以在用戶不下載它們App的情況下加入會議。Chris Koehncke很高興能看到它是如何工作的。這確實有效,不必花時間下載App.并且視頻質量可以接受,對此我們愉快地討論了半小時。
打開 chrome://webrtc-internals只看到了getUserMedia被用來獲取攝像頭和麥克風,但是沒有看到RTCPeerConnection的使用。這激起了我的興趣,它們是如何不用WebRTC進行通話的?
為什么Zoom不使用WebRTC?
Zoom與WebRTC的關系很難說清楚,就像網站上的陳述一樣:
Jitsi的伙伴對此進行了比較。Tsahi Levent-Levi對此也進行了有用的評論。
讓我們快速看看在某些有趣的情況下的‘特性’—–在Chrome瀏覽器中運行。
Zoom Web客戶端
Chromes網絡開發工具很快展現出兩點:
WebSockets被用來傳輸數據
存在wasm文件
Wasm文件名快速產生了GitHub目錄,在目錄里,與其它JavaScript元素并存。這些文件和產品中所用的文件幾乎相同。
WebSocket上的媒體
總體設計很有趣。它使用WebSockets來傳輸媒體,這并不是最好的選擇。就像在WebRTC中使用TURN/TCP一樣,這樣做會對視頻質量產生影響,并且在很多情況下不能很好地工作。通過TCP進行實時交流的問題是丟包會導致重新發送,因此增加了延遲。Tsahi之前對此進行了描述,展示了這樣做對比特率和其它方面的影響。
這樣做最大的優點是即使TURN/TCPhe TURN/TLS被防火墻擋住,它也有可能正常通過。這當然就避免了WebRTC中由于不能獲得授權代理而無法建立TURN連接的問題。這在Chrome的WebRTC實現中是個長期存在的問題,直到去年才得以解決。
WebSockets上接收的數據進入了一個基于WASM的解碼器。音頻被傳輸給瀏覽器中的AudioWorklet來對它提供支持。
視頻被加入canvas。過程非常流暢,并且視頻質量也很高。
在另一個方向,WebAudio通過getUserMedia的調用捕捉媒體,并且被發送給WASM編碼器,接著通過WebSocket被送達。視頻捕捉以640*360的分辨率進行,不幸的是,在被發送給WASM編碼器之前就被從canvas上抓取下來。
WASM文件看起來與Zoom的原始客戶端包含同樣的編解碼器,意味著網關不需要進行轉碼。不止是websocket到RTP的傳輸,更像是TURN服務器。被編碼的視頻某種程度上被像素化了,Kranky甚至抱怨視頻中的階梯失真。盡管編碼器對CPU的使用率很高,但是這沒有關系,用戶只會責怪Chrome,下次使用原始客戶端。
H.264
使用WASM形式傳輸媒體很有趣,它支持了Chrome/WebRTC不支持的編解碼器。這不是一個創新,FFmpeg之前已經編譯過emscripten很多次,看起來效果不錯。將編碼之后的信息通過WebSocket傳輸就可以使用Chrome的debug工具查看其內容,并且展示具有RTP標題的H264負載。
令我吃驚的是,NALU沒有顯示出H264-SVC的使用。
與WebRTC對比
讓我們總結一下這種情況下Chrome中使用的和WebRTC標準的不同
特征 | Zoom Web 客戶端 | WebRTC/RTCWeb
加密 | 普通RTP | DTLS-SRTP
數據頻道 | n/a ? | 基于SCTP
ICE | n/a | RFC 5245(RFC8445)
音頻編解碼| 未知 | Opus
多媒體流 | 未觀察到 | Chrome最終實現了它
同時聯播 | 在Web客戶端中未觀察到| 擴展規范
WebRTC,下一代
即使離WebRTC1.0完成還有很遠的路,很多人已經在討論下一代了。Zoom Web客戶端的總體設計深深地提醒了我:在今年Stockholm的面對面會議中Google的Peter Thatcher對于WebRTC NV的想法。請查看PPT(從26頁開始)。
如果我們在2018年重建WebRTC,我們可能會采取同樣的方法來將各種元素分離。大概需要這些步驟:
1.編譯webrtc.org編解碼器wasm
2.將解碼器與canvas連接
3.將編碼器與getUserMedia連接,作為輸入
4.通過數據通道發送編碼之后的媒體流量,實現傳輸
5.將RTCDataChannel反饋指標與視頻音頻編碼器連接
工作組的會議材料中將此方法可視化。
這個提議比Zoom的方法具有明顯的技術優勢。例如,使用RTCDataChannels來傳輸數據,相較于Websockets可以更好的控制堵塞,尤其是當出現丟包現象時。
這個設計的巨大優勢是將編解碼器從瀏覽器中分離變為可能,允許自定義版本。主要問題是將數據處理從主線程中高效分離,還有硬件加速。這一直是Chrome的巨大挑戰,我記得很多人抱怨沙盒將問題變得復雜。Zoom看起來可以正常工作,但是我們只嘗試了一對一交談,但WebRTC APP的需求顯然不止于此。更希望重用MediaStreamTrack來進行數據傳輸,這樣就可以更好的處理Canvas和WebAudio元素。
于canvas組件是原生組件,原生組件的層級是最高的,所以頁面中的其他組件無論設置 z-index 為多少,都無法蓋在原生組件上。
1.當滾動遮擋時解決辦法。
<scroll-view> 可以完美解決滾動時canvas層級過高出現的遮擋。
這里有個問題要注意,使用<scroll-view>必須把 @touchstart寫在標簽內,不然,在真機上不生效(大坑)。
2.當彈框遮擋時解決辦法。
當點擊彈框時,使用v-show的方式,切換顯示。從而不會出現層級的問題。
3.不能使用v-if的原因。
v-if是讓盒子消失 并不是改變盒子的display 屬性 所以 當時你顯示 v-if的時候 他會重新插入盒子 但是echarts圖表并不會刷新 因為在vue中mounted hook 已經調用一遍echarts表格的方法了。
解決的辦法就是使用v-show,就不會存在這個問題。
工作中有不少涉及到地圖的項目,我參加了幾次技術評審,前端伙伴們在 WebGIS 方面的知識儲備稍有不足,這次分享的主要目的是科普一些在前端領域比較常用的 WebGIS 知識。另外,我之前的工作中積攢了一些從零開始搭建 WebGL 地圖引擎的微薄經驗,雖然最終遺憾沒有上線,但在其中學到的一些WebGL知識還是值得分享一下。WebGL 可以說是前端可視化技術領域難度最大的一項圖形編程技術,所以今天就結合 WebGIS 這個話題順帶分享一些 WebGL 的相關知識,不會太深入,很細節的技術點在后續文章里再講解。
在前端領域需要關注的 WebGIS 知識最主要的是搞清楚電子地圖中的各種坐標系,其次需要對路網有一些基本的認知,包含路網的特征以及尋路算法的復雜度量級,其中對算法復雜度的了解不用精確到數字,只需要有一個大致的概念即可。
路網尋址是一套非常復雜的算法,除了路網本身的有向圖特征以外,還需要將路況、天氣甚至民生、政治等因素考慮在內。這是一項單獨的研究課題,前端研發不需要關注太細節的東西。
我們日常接觸的地理坐標最多的是經緯度坐標,地球是一個橢球體,經緯度是球面坐標系。但是我們平時使用的電子地圖都是平面的,如何把球面坐標系下的經緯度坐標映射為電子地圖的平面坐標系(數學上稱謂是笛卡爾直角坐標系)呢?這個映射過程就是投影變換,目前在 WebGIS 領域國際上統一使用墨卡托投影實現。
下面就分別介紹一下以上兩種坐標系以及映射原理。
表面上看是兩種,經緯度和墨卡托,但準確的說應該是三種(甚至N種)。因為我們日常接觸到的經緯度坐標都是經過加密算法處理之后的偏移坐標,與地理上真實的經緯度坐標有一定的偏移量。
真實的地理經緯度坐標系是國際標準,稱為WGS84標準,此標準下的坐標系稱為地球坐標系或地理坐標系。絕大多數電子地圖服務商都不會(或者說不準)直接使用 WGS84 坐標,因為地理信息是涉及國家安全的重要信息,所以一般都需要進行加密。
我們國家目前使用的加密標準是國家測繪局2002年制定GCJ02 標準,經過加密后的坐標系被稱為火星坐標系。在我國的所有電子地圖都必須至少經過 GCJ02 加密一次才可以上線使用。請注意,至少的意思是經過 GCJ02 加密之后,地圖廠商還可以進行二次甚至三次加密,比如百度地圖使用的 BD09 標準就是在 GCJ02 加密之后進行二次加密的結果。
下圖顯示的是同一個經緯度坐標在不同地圖上的位置:
墨卡托坐標是球面坐標經過墨卡托投影之后得到的笛卡爾直角二維坐標,墨卡托投影全名叫做正軸等角圓柱墨卡托投影。其原理是假設地球被圍在一個中空的圓柱里,其基準緯線(赤道)與圓柱相切接觸,然后再假想地球中心有一盞燈,把球面上的圖形投影到圓柱體上,再把圓柱體展開,這就是一幅選定基準緯線上的“墨卡托投影”繪制出的地圖,見下圖:
為了便于建模和計算,墨卡托投影在真實的地球模型上做了以下幾個假設:
墨卡托投影有兩個致命的缺點:
計算兩個POI點之間的“直線”距離是我們日常項目中出現概率很高的一種需求,之所以“直線”兩字加引號是因為在現實中地球上的兩個點不存在絕對的直線距離,在地理上都是球面距離,也就是數學上的弧長。球面上兩點之間的弧長計算是比較復雜的,而且地球是橢球體,進一步加大了復雜度。
這個問題有了墨卡托投影的輔助就很好解決了,墨卡托投影的計量單位是米(m),首先將兩個POI點的經緯度坐標換算為墨卡托坐標,剩下的就是簡單的勾股定理計算了。
經緯度與墨卡托坐標之間的轉換沒有絕對統一的換算公式,每個地圖廠商根據自己的加密算法都多少存在一些差異,一般不能跨地圖廠商使用
電子地圖的制圖是一項非常復雜的流程,技術的縱深涉及前端、后端、(空間)數據庫等等,除了技術層面以外,還涉及民生、政治等因素。篇幅有限,這些細節就不一一列舉了,只挑選在前端范圍內以及現有項目中涉及的知識點講一下,主要有兩個方面:
其中第一點是出于技術層面考慮,對從事 WebGIS 的前端開發者來說是必須具備的,因為我們對地圖只是使用,不會涉及這么深入的知識,所以大家可以當這點為科普內容;第二點的目的是讓大家對路網尋址算法的復雜度有大概的認知,從而在進行與路網相關需求的技術評審時能夠全面考慮,從而制定更合理的研發周期。
下面就分別展開講一講。
參照下面這張圖理解后續的內容:
球面的經緯度坐標經過墨卡托投影之后是一張二維的平面圖,圖中的大部分內容的變動頻率是非常低的,比如上圖中展示的大陸和海洋板塊,除非遇到地殼運動,否則基本不會變動。為了持久化存儲,在webgis領域引入了「瓦片」的概念,意思是將墨卡托坐標系的二維地圖按照既定的規則切成一個個小方塊保存到服務器,然后前端的應用程序在繪制地圖時將這些方塊按順序拼接為完整的地圖,這些小方塊被稱為瓦片-tiles。
Tile 直接翻譯是“瓷磚”,倒是很貼切,電子地圖就是用一個個 tile 拼起來的,至于為啥被翻譯成“瓦片”我也不清楚,行業術語,跟著叫就是了。
還記得前面提到的墨卡托投影的第二個假設嗎?將地球假設為正球體,投影之后得到的平面地圖是一個正方形,被切割成一個個瓦片也是正方形,這樣能夠大大降低計算復雜度。因為長方形需要考慮長和寬兩個計算因子,而正方形只需要考慮邊長一個因子即可。
瓦片的尺寸是固定的,普清瓦片邊長是256像素,高清瓦片邊長在普清基礎上乘以2也就是512像素。但即便是高清瓦片在瀏覽器中渲染的時候也是被壓縮成256像素,這里我先不解釋為什么,大家也先不要看下文,先思考一下為什么這么做。
留空思考時間..
5...
4...
3...
2...
1...
下面揭曉答案。
所謂高清和普清的區別在于:在相同物理尺寸上的像素密集程度。高清瓦片是為了讓地圖在高清屏幕上看起來更清晰,高清電子屏幕的準入標準是DPR=2(retina屏),當然目前市面上有很多高清屏已經突破了這個值。DPR是屏幕物理像素與獨立像素的比值,前端開發者應該清楚 DPR 對于圖形的影響,也就能夠理解為何高清瓦片被壓縮一倍了,我就不在贅述了。
看到這里,前端伙伴們是不是覺得 WebGIS 其實也并沒有那么神秘?其實跟我們日常開發所用的技術有很多共同點和相似性。
上面介紹了瓦片的基本概念,在地圖中還有另外一個重要概念:比例尺-Scale。可以類比成望遠鏡的放大倍數,倍數越大,看到的東西就越多越清晰,地圖比例尺就類似望遠鏡的放大倍數。在墨卡托投影的平面地圖中比例尺代表每個像素等價的以米(meter)為單位的地理距離。
地圖從宏觀到微觀被切分為不同的級別(level),相鄰level的比例尺一般成兩倍關系(并不絕對,下文解釋)。請再次參考上面的圖片,每放大一個級別(即level+1),每個瓦片都會被切割為4張新瓦片,比如level 1 的1號瓦片在level 2中被切割為1-0、1-1、1-2、1-3四張瓦片,但這四張瓦片代表的地理范圍與 level 1和1號瓦片是完全相同的,只是細節更多了(類比望遠鏡就是看到的東西更清楚了)。
在這樣的切割規則下,從宏觀到微觀,瓦片的數量隨著地圖 level 的增長成四倍增長關系(4^n),以數量為維度,所有的瓦片構成了一個金字塔結構,這就是 WebGIS 領域的術語:瓦片金字塔 - Tiles Pyramid。如下圖:
上面介紹的其實是理論上的行業標準,但在現實工作中一般不會嚴格按照這份標準落地。在瓦片切割方面一般由3 個不同于標準的地方:
基于以上3點區別,不同的地圖在一些涉及瓦片和level的計算規則上也有差異,另外再加上坐標加密算法的區別,所以大部分地圖的數據是無法共通的。
對于路網這部分知識的科普,主要目的是讓大家對路網尋址算法的復雜程度和計算量級有一個大概的認知,從而針對目前以及后續項目中涉及到尋址功能的需求大家能夠對技術上的可行性、成本以及排期有更加理性的評估。
下面這張圖是在電子地圖上的某個區域的路網示意圖:
路網在數學上的模型是圖(Graph)。圖論是離散數學的一個分支,在計算機應用科學領域,《數據結構與算法》這門課中有專門的圖論算法,而且占比非常大。但由于相比較其他內容,圖論算法的復雜度高出很多,所以即便教材里有這一部分的內容,但很多高校在實際教學中不會教也不會考(反正我當時沒學~囧)。
最簡單的圖是一個二元組,由頂點(vertex)和邊(edge)組成,表達式為:
G = (V,E)
在 WebGIS 領域,路網在是一種有向帶權圖。所謂帶權圖可以簡單的理解為每條邊有一些額外的屬性,比如路況、方向等等。
路網尋址的需求主要是用在路徑規劃和導航場景下,這兩種場景有一個共同點:起點和終點是確定。在這個前提下,路網尋址其實就是圖論中經典的最小路徑尋址算法,這種算法已經非常成熟了,而且復雜度也已經被很多前人反復驗證和改良過,目前各家地圖使用的此類算法都是在時間復雜度和空間復雜度之間權衡的最優解,而且還要綜合考慮出行方式、交通、天氣等現實因素(這些在數學模型中都是帶權圖結構中edge的「權」)。
但是(沒錯,什么都有但是),高效的尋址算法背后,請一定要注意「起點和終點是確定的」這個重要的前提。如果沒有了這個前提,復雜度會呈指數型增長,甚至可以說以現在的計算機硬件技術,這個復雜度是沒有上限的。為什么這么講,且看下文。
在地圖的業務場景中還有一個非常典型的功能:POI檢索。比如以某個點為中心在指定半徑的圓形區域內檢索特定類型的POI。或者在地圖上自定義指定幾個點,然后在以這些點為頂點的不規則圖形內進行POI檢索。這兩種都是典型的POI檢索場景,跟路網尋址一毛錢關系都沒有。
然而有時候我們還期望另外一種檢索方式:
針對這種需求,我在搜狗工作期間寫了一個專利,但是在商業軟件領域基本不具備可行性,因為計算量太大了。這個專利純粹是為了完成老板交付的任務~哈哈。
我們可以設想一下應該按照什么樣的流程去解決這個需求。
第一種是正向解法:從起點開始沿著路網圖的邊遞進檢索,直到到達出行范圍的最遠邊界。這是符合現實規律的一種方法,就好比我想找一家便利店,最遠不能超過步行30分鐘,然后我就從當前位置開始沿著路走啊走,遇到路口就隨機選一個方向接著走,運氣好的話選的路邊有家店,運氣不好的話只能回到路口再隨機選一個方向試著找找,以此類推。當然現實跟算法的區別就是人的體力有限,一是不可能多線程,二是體力堅持不了走所有的路。
第二種是逆向解法。就是在進行尋址算法之前盡量做減法,以給定的條件盡量縮小檢索范圍。比如指定步行最長距離是5公里,起點在中關村科貿大廈,按照以下步驟進行:
事實上,前文提到的兩種POI檢索場景(圓形和自定義多邊形)都是逆向解法。POI在數據庫中的模型除了坐標以外,還有其他附加屬性,比如國家、城市、行政區域、甚至在哪條路等信息,就是為了縮小檢索范圍從而減輕計算量。
逆向解法比正向解法的計算量小很多,但是兩種解法的計算量都會隨著出行時長和距離的增加呈指數型增長,幾乎沒有上限(當然這么說不準確,肯定是在地球范圍之內~)。
如果地圖廠商自己想要不計成本地實現這個需求還是有一定可行性的,因為他們自己擁有路網和POI數據。但是如果我們想實現就很困難了,首先我們沒有數據,所以正向解法絕無可能;其次,我們是采買的地圖廠商的服務,而商業化的服務都是有限制的,比如每天的POI檢索量上限,如果限制在比較小的范圍內同時檢索量沒有超過上限,逆向解法是有一定可行性的。但是(是的,還有但是),對于我們來說,這個可行性必須建立兩個前提下:
路網相關的知識分享到這里,大家應該對尋址算法的計算量級有大概的認知了吧。作為科普,對 WebGIS 的了解到這個程度就可以了,其中還有很多WebGIS領域內的技術細節,篇幅有限就不一一列舉了。下半部分是跟前端技術相關性比較高內容,以電子地圖的渲染流程為引,介紹一下 WebGL 的一些基礎知識。
這塊內容分為兩部分,第一部分介紹一下電子地圖的渲染流程,期間按照瓦片的兩種類型(靜態/動態)分別講一下涉及的前端技術;第二部分以當前主流的矢量地圖為引,簡單介紹一下 WebGL 的一些基礎知識。關于 WebGL 的知識不會很深入,目的是讓大家的對 WebGL 以及圖形編程有大概的認知,后續前端組會制定一套數據可視化技術的系列課程,到時再深入到各項技術的細節知識。
先講一點預備知識,電子地圖涉及幾種坐標系,每種坐標的計量單位如下:
前端拿到的地圖數據中絕大多數是墨卡托坐標,很小一部分是經緯度坐標。墨卡托或經緯度坐標需要先被換算成屏幕坐標,最后被CSS拼接或WebGL渲染。
這里的屏幕坐標準確的說應該是畫布(canvas)坐標,前端常規認知的屏幕坐標是CSS坐標,在柵格地圖中CSS坐標與canvas坐標是相等的,在矢量地圖中根據屏幕的DPR值,CSS坐標與canvas坐標成倍數關系。
web地圖的渲染流程大致如下:
地圖在進入渲染流程之前有一些必要的前置條件:
以上幾個條件的目的是為了計算地圖當前的視野范圍(bounds),進而計算出當前視野包含的瓦片編號列表。
前半部分介紹了瓦片切圖,準確地說應該是「瓦片切割」,早期web地圖使用的瓦片是一張張靜態的png圖片,前端開發者使用CSS position按照瓦片編號拼接成一張完整的二維地圖。對前端來說,瓦片就等同于是圖片,所以“瓦片切圖”這個叫法一直被延續下來。
但地圖數據本身是一個個坐標值并不是圖片,之所以將瓦片保存為圖片格式是因為早期的瀏覽器沒有能夠繪制海量數據的圖形技術,也就是大家熟知的 WebGL。在這個前提下,地圖廠商會在服務端搭建一套瓦片切圖預處理的流程,簡單理解就是先用 OpenGL 將地圖數據可視化,然后按照既定的規則把每個 level的地圖切割成一張張 256 * 256 的圖片托管到靜態文件服務器,最后前端開發者取圖片拼接。以圖片拼接而成的web地圖叫做「柵格地圖」。
注意上圖里的切圖服務中包含「瓦片-data」和「瓦片-png」,兩者的內容一般是不同的。瓦片data的功能一方面是為了瓦片圖片切割,另一方面是提供給其他支持矢量圖形技術的平臺使用,比如 app。
柵格地圖的優點是:
基于以上兩個優點,目前仍然有很多地圖的JavaScript SDK使用柵格瓦片或者柵格混合矢量數據(一般是底圖用柵格瓦片,建筑物和poi用矢量數據)的形式。不過柵格地圖也有很明顯的缺點:
隨著大部分主流瀏覽器對 WebGL實現了支持,很多地圖廠商都陸續開始研發并上線了矢量地圖。矢量地圖同樣需要預處理的切圖服務,但是預處理的產出并不是圖片格式的瓦片,而是與app一樣的瓦片data,換句話說,矢量web地圖可以與app地圖使用同一份數據,這意味著所有平臺的地圖數據可以統一維護和迭代。
“可以”的意思是可行但不一定,分業務場景。比如導航是app地圖獨有的功能,導航場景使用的地圖數據稱為“市街圖-street map”,這些數據是web地圖用不到的。
矢量地圖說白了就是把原本OpenGL干的活交給了WebGL干,說起來簡單做起來難,WebGL 是非常底層的圖形編程技術,幾乎沒有任何上層封裝,接近純粹的計算機圖形學。相關的研發人才非常稀缺,圖形編程本身就是一個相對小眾的垂直領域,WebGL 圖形編程則更加小眾,雖然同屬于前端技術領域,但 WebGL 研發人員的招聘和培養難度比常規web前端研發人員要難很多,所以有能力開發 WebGL 矢量地圖的廠商要么是有足夠的人才儲備想為產品錦上添花,比如高德和百度的WebGL地圖第一個產品是自家的PC地圖;要么是有充分的客戶需求兌現商業價值,比如騰訊的WebGL地圖第一個產品是B端的 JavaScript SDK(2020年初上線),截止到今天PC地圖也沒有接入WebGL。否則單純靠愛發電很難落地,比如搜狗地圖的WebGL引擎開發到80%的時候被叫停,之后再也沒有撿起來過。
WebGL 圖形編程與常規web網站是完全不同的一套知識體系,雖然都使用JavaScript語言,但細節技術點完全不同,比如 WebGL 中被大量使用的 buffer、TypedArray、Protobuf等知識點在常規web網站中幾乎不會被涉及,另外還有一套類似C++的shader語言-GLSL。這些細節知識點會在后續的文章中講解,今天就簡單科普一下WebGL的渲染管線以及WebGL矢量地圖中常用的幾種算法。
WebGL 是 canvas的一種渲染上下文(context),canvas有兩種context:2D和WebGL。二者沒有任何關系,相同點是都需要借助canvas輸出圖像。目前大部分瀏覽器都支持 WebGL1.0,對 WebGL 2.0 的兼容很不理想,下文的討論都是針對 1.0 版本。
下面這段代碼是創建WebGL 上下文的API以及幾個常用配置項:
const canvas = <htmlcanvaselement>createElement('canvas');
const gl: WebGLRenderingContext = canvas.getContext("webgl",{
// 是否開啟自動抗鋸齒,建議關閉,瀏覽器兼容性差開了也沒用,就算有用性能也很差(因為瀏覽器用的抗鋸齒算法是效果很好同時性能很差的一種),大多是自己寫代碼實現
antialias: false,
// 是否開啟透明通道,一般建議關閉,性能損耗嚴重,自己寫代碼根據透明值計算出混合色值更高效。如果開啟的話,對研發人員的技術能力有更高要求
alpha: false,
// 是否開啟 stencil(模板) 緩沖區支持,數據量大的應用建議開啟,配合stencil test能夠減少無效渲染
stencil: true,
// 是否開啟 depth(深度) 緩存區支持,簡易的webgl地圖基本用不到depth test,一般是關閉的。像mapbox這類復雜的webgl地圖引擎是開啟的
depth: false
});
WebGL 中有幾個核心概念:
除了以上兩種shader以外,OpenGL 還支持 geometry shader-幾何著色器,不過也不常用。WebGL不支持幾何著色器,
基于上面的幾個核心概念,WebGL 執行渲染的API調用流程是:分別創建兩種shader -> 創建一個program -> 將program與兩個shader綁定 -> 鏈接(link)program ->激活(use)program -> 傳參給shader -> 傳值&渲染。如下:
// 1.1-創建vertex shader instance
const vShader:WebGLShader = gl.createShader(gl.VERTEX_SHADER);
// 1.2-指定vertex shader源-vShadersStr,字符串格式
gl.shaderSource(vShader, vShadersStr);
// 1.3-編譯vertex shader
gl.compileShader(vShader);
// 2.1-創建fragmentshader instance
constfShader:WebGLShader = gl.createShader(gl.FRAGMENT_SHADER);
// 2.2-指定fragmentshader源-fShadersStr,字符串格式
gl.shaderSource(fShader,fShadersStr);
// 2.3-編譯fragmentshader
gl.compileShader(fShader);
// 3-創建program
const program: WebGLProgram = gl.createProgram();
// 4-綁定program與兩個shader
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
// 5-鏈接program
gl.linkProgram(program);
// 6-激活program
gl.useProgram(program);
// 7-傳值&渲染相關API下文再講
接下來就是傳值和執行渲染,這部分需要了解WebGL shader中的三種變量類型:
#define PI 3.1415926538
結合上文的幾種變量類型,WebGL的渲染流程大致如下圖所示(條紋框表示GPU內部流程,開發者無法干預):
簡單聊一下上文提到的 MVP 矩陣,細節的技術實現方案后續的分享中再說。
MVP 矩陣是仿射變換過程中三種變換矩陣的統稱:
頂點的原始坐標需要依次經過Model矩陣、View矩陣和Projection矩陣變換(左乘)之后才能夠得到它在裁剪空間中的最終坐標值。如下代碼所示:
precision mediump float;
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat4 u_mMatrix;
uniform mat4 u_vMatrix;
uniform mat4 u_projMatrix;
void main() {
position = (u_vMatrix*u_mMatrix*vec4(a_position,0,1)).xy;
gl_Position = u_projMatrix*vec4((position / u_resolution * 2.0 - 1.0)*vec2(1,-1), 0, 1);
}
上面代碼中的u_resolution是畫布的尺寸,Model和View矩陣的數值一般是與畫布的坐標使用相同的計量單位(px),Projection矩陣一般是歸一化的矩陣。
三種矩陣在數學上沒有區別只是計算邏輯上的三種抽象,都是4*4矩陣,都可以包含位移、縮放、斜切等形變信息。一般Projection矩陣是單獨的,Model和View矩陣可以分開也可以在CPU側計算之后得到一個Model&View矩陣再傳入頂點著色器。
最后這部分介紹兩種 WebGIS 領域常用的算法,準確地說應該是 WebGIS 繪圖領域,一種是多邊形三角剖分算法,一種是R-Tree算法。這兩種算法與 WebGIS本身并沒有太大關系,屬于計算機圖形學通用的算法。
三角剖分算法
計算機圖形學中只有三種基本圖元:點、線段、三角形。點和線段的適用面很窄,極少被使用,
繪圖過程中絕大部分的圖形底層都是一個個三角形組成的,如下圖所示:
喜歡玩3D游戲的人可能知道,建模對游戲的視覺效果影響很大,除了模型本身的設計風格以外,建模的精細度也很重要,而衡量精細度的核心指標之一便是三角形的數量。雖然數量不是唯一指標,但細致的3D模型的三角形數量一定非常龐大,一般數量越多,模型的邊緣越平滑,視覺效果越好。反面例子比如下圖展示學動畫三年系列,人物(姑且算是個人吧)模型邊緣有非常明顯的棱角,過渡非常不順滑。
回到 WebGIS 領域,我們看到的電子地圖是由一個個不規則的多邊形(Polygon)和線(Line)組成,三角剖分算法的作用就是把這些多邊形分割成一個個三角形,然后才能夠被 WebGL 繪制出來。
其實線也是多邊形,因為 WebGL 1.0 不支持寬于1像素的線,所以寬線必須以多邊形的形式繪制。
三角剖分算法有兩種類型,一種是多邊形三角剖分,一種是點集三角剖分,后者在圖形編程領域不常用,我們只需要關注多邊形三角剖分。
三角剖分是典型的動態規劃算法,對于多邊形三角剖分最簡單的場景就是三個點,也就是三角形,這種根本不需要分割。再復雜一點就是矩形,前端小伙伴們可以想像一下我們常用的 CSS盒子,html布局就是一個個矩形拼起來的,對于一個矩形來說需要2個三角形組成。然后依次再遞增多邊形的頂點個數,比如6個:
這時候需要4個三角形。
很細節的算法實現就不講了,其實我也沒搞太懂哈哈。對于前端工程師來說,從零實現這套算法的代價太大,更別提還要很細化地調優,我們直接使用經過大量實踐驗證的開源算法和工具就可以了。WebGL圖形編程常用的三角剖分工具是Libtess,這套算法也是OpenGL編程常用的,非常高效。
R-Tree算法
R-Tree是一種樹狀數據結構,在 GIS領域主要用于空間數據的儲存。在繪圖方面,R-Tree較多地被用于圖形沖突檢測。
柵格地圖的POI點坐標是在瓦片預處理過程中被計算好的,哪個顯示哪個不顯示都被預定義好了,前端拿到數據之后按照既定的坐標渲染出來即可。而矢量地圖則不然,前文提到,矢量地圖實際上就是讓WebGL干了OpenGL的活,不單是繪圖,繪圖過程中的任何事情都變成了前端的事情,POI沖突檢測就是其中一項。
先看下面這張圖:
圖中有兩個POI點:微電子與納電子學系(下文簡稱POI點A)和超導量子信息處理實驗室(下文簡稱POI點B),每個點都有圖標和文本兩部分,點A和點B的文本都位于圖標的下方。
POI有一個「權重-rank」的屬性,繪圖時要保障權重高的優先渲染,如果畫布空間有限則要合理地調整低權重POI的布局甚至不渲染。仍然以上圖為例,假設點A的權重高于點B:
以上便是使用R-Tree進行位置沖突檢測的簡易流程。除了POI位置檢測以外,繪圖中R-Tree另一個使用場景是道路名稱的位置標注算法,如下圖中的「雙清路」「荷清路」文本:
R-Tree沖突檢測的開源工具推薦rbush。
POI的位置布局(POI Placement)算法也是單獨的一項研究課題,有大量論文,大家有興趣可以自行查閱相關資料。
其實R-Tree不僅僅適用于圖形編程,在常規前端領域也有可借鑒的場景。比如下圖展示的一個報表看板:
圖中的布局亂了,報表之間存在遮擋情況,如果這種情形需要前端實現一個自動布局,也就是圖中的「一鍵美化」功能,你可能考慮怎么辦?
這時候就可以嘗試用R-Tree解決,每個報表的容器都是一個個矩形盒子,使用rbush可以檢測出所有矩形的沖突情況,然后再嘗試自動調整布局直到rbush檢測不沖突為止。R-Tree提供了一種解決思路和搭配的工具,在此基礎之上可以進一步完善細化的布局調整邏輯。
以上是今天分享的全部內容,簡單總結一下。
第一部分介紹了 WebGIS 領域的一些基礎知識,包括坐標體系、制圖繪圖流程和路網結構。對于日常工作中涉及地圖的項目,對這些基礎知識有個大概了解可以對工作有輔助作用比如技術評審。
第二部分介紹了兩種地圖類型以及矢量地圖所使用的圖形技術WebGL,簡單分享了WebGL的渲染管線和常用的兩種算法。電子地圖不像游戲、動畫等高復雜度圖形應用對WebGL技術有很苛刻的要求,地圖引擎頂多發揮了WebGL 三分之一的能力,我們日后在數據可視化方面的技術需求,可能涉及WebGL的部分甚至不如地圖那么復雜,所以今天我們對WebGL先有一個大概的認知,后續再一步步學習內部的細節知識。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。