WebGL-Y軸翻轉踩坑實錄

案例背景

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

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

而後出現了一系列問題:1. 瓦片錯亂:瓦片1的位置顯示了瓦片4的內容;2. 瓦片內容倒置。瀏覽器

問題分析

根據調試定位,發現問題的根源在於Y軸翻轉。bash

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

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

紋理座標系與頂點座標系的Y軸方向不一樣,進行座標映射的時候會不方便,因此若是將紋理座標系的Y軸翻轉則能使座標映射更容易(上圖右)。性能

WebGL 也提供了相應接口實現該功能, WebGLRenderingContext.pixelStorei() 是 WebGL 中用於描述像素存儲模式的函數,其中 UNPACK_FLIP_Y_WEBGL 能夠用於設置Y軸是否翻轉:ui

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

問題2: 爲何Y軸翻轉會致使瓦片錯亂呢?

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

// 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),以此類推。firefox

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

因此實際上大紋理應該長以下這樣:

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

問題3: 爲何瓦片會倒置?

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

瓦片在屏幕上顯示出來是上下顛倒的,且這種狀況只出如今chrome/firefox裏,由於在這兩個瀏覽器中咱們使用了 createImageBitmap 將blob格式的圖片轉爲了位圖,而在safari瀏覽器(不支持 createImageBitmap)中咱們將blob格式轉爲了 Image 對象,最終致使了這種差別,因此咱們從 ImageBitmap 着手去定位問題緣由。

ImageBitmap 表示位圖圖像,用於在canvas中繪製圖像,相比較於 Image 其延遲較低,由於在執行 texSubImage2DImage 繪製到紋理上時也會先將其轉爲 ImageBitmap

不管是在 canvas 裏繪製2d圖像,仍是在 WebGL 中建立紋理,當使用圖像時瀏覽器會把圖像作一次解碼(decode)處理。這個解碼也就是把圖像的原始格式(好比 jpeg、png 等)統一轉換爲位圖,即每一個像素使用 RGB 或 RGBA 來描述。當圖片尺寸比較大的時候,解碼也會有必定的消耗,並且這個耗時是同步的。——《高性能 WebGL —— 使用 ImageBitmap 提高紋理性能》(www.jiazhengblog.com/blog/2019/0…

同時 WebGL 規範裏對 ImageBitmap 有一些特殊的描述,當介紹 pixelStorei 的三個參數:UNPACK_FLIP_Y_WEBGLUNPACK_PREMULTIPLY_ALPHA_WEBGLUNPACK_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 參數失效了。

小結

  1. UNPACK_FLIP_Y_WEBGL 參數用於設置紋理像素存儲模式中是否將Y軸翻轉,翻不翻取決於你的頂點模型的座標系方向,適合本身就好。在咱們的應用場景裏,頂點模型和圖像座標系是反的,因此須要將該參數設爲1。
  2. 使用 texSubImage2D 上傳圖片時一樣受到 UNPACK_FLIP_Y_WEBGL 參數的影響。
  3. 若是上傳的圖像是 ImageBitmap 對象,則在其建立時可經過 ImageBitmapOptions 中的 imageOrientationpremultiplyAlphacolorSpaceConversion 三個參數讓其與pixelStorei 中所設置的參數保持一致。

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

相關文章
相關標籤/搜索