以下內(nèi)容轉(zhuǎn)載自totoro的文章《WebGL-Y軸翻轉(zhuǎn)踩坑實(shí)錄》
作者:totoro
鏈接:https://blog.totoroxiao.com/webgl-flipY/
來源:https://blog.totoroxiao.com/
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
前言
自定義柵格圖層 是指用戶可以通過特定軟件,將自定義的圖像按照上文所述的方式切割為瓦片,并生成圖片,然后按照瓦片坐標(biāo)拼接形成地圖的圖層。常用于手繪地圖、衛(wèi)星圖、地形圖等。
案例背景
基于 WebGL 的地圖渲染API,實(shí)現(xiàn)自定義柵格圖層(將地圖切分為等大的正方形,并以圖片進(jìn)行拼接渲染)時(shí),為了節(jié)省紋理上傳的開銷,將柵格瓦片集中繪制到一張紋理上,然后繪制時(shí)根據(jù)瓦片各自的紋理坐標(biāo)取各自的紋理,大概示意圖如下:
瓦片根據(jù)加載的先后順序依次排列繪制到大紋理上,占位寬度一致,豎向排列。比如若瓦片大小為256px,那么瓦片1的位置為{x:0, y:0}, 瓦片2的位置為{x:0, y:256}。
然后出現(xiàn)了一系列問題:
- 瓦片錯(cuò)亂:瓦片1的位置顯示了瓦片4的內(nèi)容;
- 瓦片內(nèi)容倒置。
問題分析
根據(jù)調(diào)試定位,發(fā)現(xiàn)問題的根源在于Y軸翻轉(zhuǎn)。
問題1: Y軸翻轉(zhuǎn)是什么?為什么要翻轉(zhuǎn)?
先看看沒有任何處理的情況下如何繪制紋理,我們繪制瓦片的基本頂點(diǎn)模型是一個(gè)中心在原點(diǎn)的正方形,對(duì)于每個(gè)頂點(diǎn)坐標(biāo),需要映射到一個(gè)紋理坐標(biāo)(下圖左),傳給片元著色器,再使用 texture2D() 取紋理像素,這種情況下左上角頂點(diǎn)(-1,1)對(duì)應(yīng)的紋理坐標(biāo)為(0,0)。
紋理坐標(biāo)系與頂點(diǎn)坐標(biāo)系的Y軸方向不同,進(jìn)行坐標(biāo)映射的時(shí)候會(huì)不方便,所以如果將紋理坐標(biāo)系的Y軸翻轉(zhuǎn)則能使坐標(biāo)映射更容易(上圖右)。
WebGL 也提供了相應(yīng)接口實(shí)現(xiàn)該功能, WebGLRenderingContext.pixelStorei() 是 WebGL 中用于描述像素存儲(chǔ)模式的函數(shù),其中 UNPACK_FLIP_Y_WEBGL 可以用于設(shè)置Y軸是否翻轉(zhuǎn):
// 1表示翻轉(zhuǎn),0表示不翻轉(zhuǎn)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
問題2: 為什么Y軸翻轉(zhuǎn)會(huì)導(dǎo)致瓦片錯(cuò)亂呢?
如上文所述,首先需要通過 texImage2D 創(chuàng)建一個(gè)大紋理,然后使用 texSubImage2D 將瓦片繪制到大紋理上:
// x, y 表示偏移量
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, image);
這個(gè)接口用于改變紋理中指定子區(qū)域的數(shù)據(jù),可以類比于 CanvasRenderingContext2D.drawImage() ,我們平常使用drawImage 時(shí)都是以左上角為原點(diǎn)進(jìn)行偏移,所以想象中的大紋理是如下圖所示的那樣,瓦片1的左上角對(duì)應(yīng)紋理坐標(biāo)(0, 1),左下角為(0, 0.75),以此類推。
但實(shí)際上Y軸翻轉(zhuǎn)并不只作用在片元著色器的紋理中,使用 texImage2D 創(chuàng)建大紋理時(shí)其像素存儲(chǔ)模式就已經(jīng)確定了,當(dāng)執(zhí)行texSubImage2D時(shí)也會(huì)對(duì)image的像素存儲(chǔ)位置進(jìn)行反轉(zhuǎn),其執(zhí)行過程是這樣:
所以實(shí)際上大紋理應(yīng)該長(zhǎng)如下這樣:
所以當(dāng)使用紋理坐標(biāo)左上角(0, 1)+左下角(0, 0.75)時(shí),我們?nèi)〉降氖峭咂?的紋理,最終導(dǎo)致了瓦片錯(cuò)亂。
問題3: 為什么瓦片會(huì)倒置?
正確取得紋理坐標(biāo)后,又出現(xiàn)了新的問題:
瓦片在屏幕上顯示出來是上下顛倒的,且這種情況只出現(xiàn)在chrome/firefox里,因?yàn)樵谶@兩個(gè)瀏覽器中我們使用了 createImageBitmap 將blob格式的圖片轉(zhuǎn)為了位圖,而在safari瀏覽器(不支持 createImageBitmap)中我們將blob格式轉(zhuǎn)為了Image 對(duì)象,最終導(dǎo)致了這種差異,所以我們從ImageBitmap 著手去定位問題原因。
ImageBitmap表示位圖圖像,用于在canvas中繪制圖像,相比較于Image 其延遲較低,因?yàn)樵趫?zhí)行texSubImage2D 將Image 繪制到紋理上時(shí)也會(huì)先將其轉(zhuǎn)為ImageBitmap:
不論是在 canvas 里繪制2d圖像,還是在 WebGL 中創(chuàng)建紋理,當(dāng)使用圖像時(shí)瀏覽器會(huì)把圖像做一次解碼(decode)處理。這個(gè)解碼也就是把圖像的原始格式(比如 jpeg、png 等)統(tǒng)一轉(zhuǎn)換為位圖,即每個(gè)像素使用 RGB 或 RGBA 來描述。當(dāng)圖片尺寸比較大的時(shí)候,解碼也會(huì)有一定的消耗,而且這個(gè)耗時(shí)是同步的?!陡咝阅?WebGL —— 使用 ImageBitmap 提升紋理性能》(http://www.jiazhengblog.com/blog/2019/03/24/3407/)
同時(shí) WebGL 規(guī)范里對(duì) ImageBitmap 有一些特殊的描述,當(dāng)介紹 pixelStorei 的三個(gè)參數(shù):UNPACK_FLIP_Y_WEBGL、UNPACK_PREMULTIPLY_ALPHA_WEBGL、UNPACK_COLORSPACE_CONVERSION_WEBGL時(shí),明確說明了其對(duì)ImageBitmap 無效,只能在創(chuàng)建 ImageBitmap 的時(shí)候就進(jìn)行相應(yīng)設(shè)置:
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.
所以可以大膽猜測(cè),pixelStorei 所指定的像素存儲(chǔ)模式其實(shí)作用于將圖像解碼轉(zhuǎn)為位圖的預(yù)處理過程。當(dāng)我們直接將位圖繪制到紋理上時(shí)就沒有這個(gè)預(yù)處理過程了,所以UNPACK_FLIP_Y_WEBGL 參數(shù)失效了。
小結(jié)
-
UNPACK_FLIP_Y_WEBGL參數(shù)用于設(shè)置紋理像素存儲(chǔ)模式中是否將Y軸翻轉(zhuǎn),翻不翻取決于你的頂點(diǎn)模型的坐標(biāo)系方向,適合自己就好。在我們的應(yīng)用場(chǎng)景里,頂點(diǎn)模型和圖像坐標(biāo)系是反的,所以需要將該參數(shù)設(shè)為1。 - 使用
texSubImage2D上傳圖片時(shí)同樣受到UNPACK_FLIP_Y_WEBGL參數(shù)的影響。 - 如果上傳的圖像是
ImageBitmap對(duì)象,則在其創(chuàng)建時(shí)可通過ImageBitmapOptions中的imageOrientation、premultiplyAlpha、colorSpaceConversion三個(gè)參數(shù)讓其與pixelStorei中所設(shè)置的參數(shù)保持一致。
最終使用自定義柵格圖層實(shí)現(xiàn)手繪圖疊加到地圖上,完成效果如下:
產(chǎn)品推廣
騰訊位置服務(wù)已經(jīng)支持個(gè)性化圖層使用,如需接入請(qǐng)查看:個(gè)性化圖層編輯平臺(tái),更多示例與開發(fā)文檔,您也可以官網(wǎng)搜索個(gè)性化圖層查看?。。?/p>