1. 程式人生 > 實用技巧 >騰訊地圖基於 WebGL實現自定義柵格圖層踩坑實錄

騰訊地圖基於 WebGL實現自定義柵格圖層踩坑實錄

以下內容轉載自totoro的文章《WebGL-Y軸翻轉踩坑實錄》

作者:totoro

連結:https://blog.totoroxiao.com/webgl-flipY/

來源:https://blog.totoroxiao.com/

著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

前言

自定義柵格圖層 是指使用者可以通過特定軟體,將自定義的影象按照上文所述的方式切割為瓦片,並生成圖片,然後按照瓦片座標拼接形成地圖的圖層。常用於手繪地圖、衛星圖、地形圖等。

案例背景

基於 WebGL 的地圖渲染API,實現自定義柵格圖層(將地圖切分為等大的正方形,並以圖片進行拼接渲染)時,為了節省紋理上傳的開銷,將柵格瓦片集中繪製到一張紋理上,然後繪製時根據瓦片各自的紋理座標取各自的紋理,大概示意圖如下:

瓦片根據載入的先後順序依次排列繪製到大紋理上,佔位寬度一致,豎向排列。比如若瓦片大小為256px,那麼瓦片1的位置為{x:0, y:0}, 瓦片2的位置為{x:0, y:256}

然後出現了一系列問題:

  1. 瓦片錯亂:瓦片1的位置顯示了瓦片4的內容;
  2. 瓦片內容倒置。

問題分析

根據除錯定位,發現問題的根源在於Y軸翻轉。

問題1: Y軸翻轉是什麼?為什麼要翻轉?

先看看沒有任何處理的情況下如何繪製紋理,我們繪製瓦片的基本頂點模型是一箇中心在原點的正方形,對於每個頂點座標,需要對映到一個紋理座標(下圖左),傳給片元著色器,再使用 texture2D() 取紋理畫素,這種情況下左上角頂點(-1,1)

對應的紋理座標為(0,0)

紋理座標系與頂點座標系的Y軸方向不同,進行座標對映的時候會不方便,所以如果將紋理座標系的Y軸翻轉則能使座標對映更容易(上圖右)。

WebGL 也提供了相應介面實現該功能, WebGLRenderingContext.pixelStorei() 是 WebGL 中用於描述畫素儲存模式的函式,其中 UNPACK_FLIP_Y_WEBGL 可以用於設定Y軸是否翻轉:

// 1表示翻轉,0表示不翻轉
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

問題2: 為什麼Y軸翻轉會導致瓦片錯亂呢?

如上文所述,首先需要通過 texImage2D 建立一個大紋理,然後使用 texSubImage2D 將瓦片繪製到大紋理上:

// x, y 表示偏移量
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, image);

這個介面用於改變紋理中指定子區域的資料,可以類比於 CanvasRenderingContext2D.drawImage() ,我們平常使用 drawImage 時都是以左上角為原點進行偏移,所以想象中的大紋理是如下圖所示的那樣,瓦片1的左上角對應紋理座標(0, 1),左下角為(0, 0.75),以此類推。

但實際上Y軸翻轉並不只作用在片元著色器的紋理中,使用 texImage2D 建立大紋理時其畫素儲存模式就已經確定了,當執行texSubImage2D時也會對image的畫素儲存位置進行反轉,其執行過程是這樣:

所以實際上大紋理應該長如下這樣:

所以當使用紋理座標左上角(0, 1)+左下角(0, 0.75)時,我們取到的是瓦片4的紋理,最終導致了瓦片錯亂。

問題3: 為什麼瓦片會倒置?

正確取得紋理座標後,又出現了新的問題:

瓦片在螢幕上顯示出來是上下顛倒的,且這種情況只出現在chrome/firefox裡,因為在這兩個瀏覽器中我們使用了 createImageBitmap 將blob格式的圖片轉為了點陣圖,而在safari瀏覽器(不支援 createImageBitmap)中我們將blob格式轉為了 Image 物件,最終導致了這種差異,所以我們從 ImageBitmap 著手去定位問題原因。

ImageBitmap 表示點陣圖影象,用於在canvas中繪製圖像,相比較於 Image 其延遲較低,因為在執行 texSubImage2D Image 繪製到紋理上時也會先將其轉為 ImageBitmap

不論是在 canvas 裡繪製2d影象,還是在 WebGL 中建立紋理,當使用影象時瀏覽器會把影象做一次解碼(decode)處理。這個解碼也就是把影象的原始格式(比如 jpeg、png 等)統一轉換為點陣圖,即每個畫素使用 RGB 或 RGBA 來描述。當圖片尺寸比較大的時候,解碼也會有一定的消耗,而且這個耗時是同步的。——《高效能 WebGL —— 使用 ImageBitmap 提升紋理效能》(http://www.jiazhengblog.com/blog/2019/03/24/3407/)

同時 WebGL 規範裡對 ImageBitmap 有一些特殊的描述,當介紹 pixelStorei 的三個引數:UNPACK_FLIP_Y_WEBGLUNPACK_PREMULTIPLY_ALPHA_WEBGL、UNPACK_COLORSPACE_CONVERSION_WEBGL 時,明確說明了其對ImageBitmap 無效,只能在建立 ImageBitmap 的時候就進行相應設定:

If the TexImageSource is an ImageBitmap, then these three parameters will be ignored. Instead the equivalent ImageBitmapOptions should be used to create an ImageBitmap with the desired format.

所以可以大膽猜測,pixelStorei 所指定的畫素儲存模式其實作用於將影象解碼轉為點陣圖的預處理過程。當我們直接將點陣圖繪製到紋理上時就沒有這個預處理過程了,所以 UNPACK_FLIP_Y_WEBGL 引數失效了。

小結

  • UNPACK_FLIP_Y_WEBGL 引數用於設定紋理畫素儲存模式中是否將Y軸翻轉,翻不翻取決於你的頂點模型的座標系方向,適合自己就好。在我們的應用場景裡,頂點模型和影象座標系是反的,所以需要將該引數設為1。
  • 使用 texSubImage2D 上傳圖片時同樣受到 UNPACK_FLIP_Y_WEBGL 引數的影響。
  • 如果上傳的影象是ImageBitmap物件,則在其建立時可通過 ImageBitmapOptions 中的 imageOrientationpremultiplyAlphacolorSpaceConversion 三個引數讓其與pixelStorei 中所設定的引數保持一致。

最終使用自定義柵格圖層實現手繪圖疊加到地圖上,完成效果如下:

產品推廣

騰訊位置服務已經支援個性化圖層使用,如需接入請檢視:個性化圖層編輯平臺,更多示例與開發文件,您也可以官網搜尋個性化圖層檢視!!!