挺久以前有個騰訊的面試, 也是基于游戲引擎開發東西的, 問到一個圖片是怎樣加載渲染的, 我最后說到壓縮的圖片直接進到GPU, 通過GPU解壓獲取最終像素, 然后被連著問了兩次你確定?
然后我就有點迷糊了, 不是嗎? 然后他也沒說他為啥這樣問, 或者想要的是什么答案, 就略了
現在回想起來, 可能是對于程序的理解或者平臺的不同, 也會造成雞同鴨講的結果的吧...這里再把圖片從創建資源到加載渲染的流程捋一遍.
最原始的位圖檔案BMP, 常見的壓縮格式 JPG / PNG , 不常用格式 ETC / PVR 這些 :

它們都是同一張圖片, 只是存的型別不一樣, 位圖檔案就是RGB直接存的, 大小直接就能算出來 :

627 * 310 * 3Byte = 583,110 B => 571KB
可是相比壓縮格式它占的空間實在太大了, 跟 JPG 差了幾十倍, 不過 JPG 是有損壓縮方式壓縮的, 當解壓回來后影像會損失資訊(網路一些圖片盜來盜去, 越來越綠就是這個原因), PNG 是無損壓縮的, 解壓回來還是那個樣子, 所以根據不同情況基本這兩種壓縮就涵蓋了有損和無損區別了.
而 ETC / PVR 這些也是有損壓縮, 而它們的計算邏輯跟 JPG 也是不同的, ETC / PVR 對應于 GPU 硬體解壓邏輯, 我們都知道GPU是并行單元的, 就像超市存包的鎖柜, 每個柜子直接用鑰匙打開就能取東西了, 可以同時 存/取 N個包, ECT / PVR 的解壓是基于一個全域查找表邏輯的, 取出來的結果對照查找表然后進行調制就能得到像素值了:
BMP -> ETC / PVR
在記憶體上相當于縮小的BMP, 差不多除以6
定長壓縮, 直接就能找到對應像素點的 Block, 因為 GPU 很猛, 解壓計算很快, 就跟直接讀取位圖一樣沒有差別. 所以一般 ETC / PVR 圖片還是保持壓縮格式躺在 GPU 的記憶體里. 下面是非常直觀的PVRTC還原像素程序 :

而 JPG / PNG 這些非定長壓縮的, 你并不能直接從像素推斷獲取它對應像素點的資料在記憶體中的位置, GPU 就沒有辦法并行的去找記憶體然后解壓對應像素資訊了, 比如 JPG 自帶了資料壓縮 :

所以一個 JPG 的解壓步驟很多, 把這些用 GPU 實作的話相當困難, 主要是演算法復雜 :

上面說的是格式的壓縮, 還有另外一個檔案的壓縮.
GPU 部分的差別大概就是這些了, 然后打包游戲資源的時候, 一般還會把這些壓縮格式圖片進行一次打包, 而這個打包也會進行資料壓縮, 看看打包壓縮對它們的影響 :

可以看到壓縮對它們還是有很大影響的, JPG / PNG 自帶了去冗余程序, 可壓縮量不大, 甚至 PNG 反而檔案變更大了, 而原始 BMP 檔案可壓縮量很可觀, 當然 ETC / PVR 這些跟BMP 有點類似, 也是能壓縮的 :

這是PC上的 DXT 壓縮格式打的 AssetBundle 包, 它的 DXT 記憶體大小是顯示在檢視面板上的 :

而如果不壓縮, 它在記憶體上的大小是這樣的 :

這樣看下來, 其實解壓就有兩種意思了, 一個是檔案解壓, 像上面的 zip 解壓出圖片檔案, 然后是像素解壓, 是把壓縮格式的圖片還原為像素的程序, 比如 NPG 檔案還原為 BMP 格式然后傳入 GPU, GPU 就躺著讀取就行了, 還有就是 GPU 支持的壓縮格式 DXT / ETC / PVR 等, 只需要把壓縮格式圖片傳給GPU, 讓 GPU 大大的干活就行了.
然后檔案解壓階段, 必然是由 CPU 進行的, 比如加載 AssetBundle, 它就會在讀取完檔案之后進行解壓操作(一般情況), 如果基于塊壓縮的(ChunkBasedCompression)它可能在讀取相關資源的時候再對指定資源解壓( AssetBundle.LoadAsset() ).
最后是 GPU 解壓階段 :
1. 如果 GPU 不支持你的壓縮格式, 引擎會用 CPU 把圖片解壓成位圖檔案, 然后再傳給 GPU, 這樣又浪費 CPU 資源, 又浪費總線帶寬
2. 如果支持的話, 就直接把壓縮格式的圖片傳輸給 GPU 了, 可以說是由 GPU 進行解壓光柵化.
3. 復雜的壓縮格式 JPG / PNG 基本不可能被 GPU 支持, 幾乎都由 CPU 解壓成位圖傳遞給 GPU的
所以游戲引擎才會直接把扔進去的圖片都進行對應平臺的轉換, 因為追求的是效率和壓榨硬體性能, 而 Web 網頁這種跨平臺又是網路傳輸的, 一般都使用 JPG / PNG 這些自帶壓縮的格式, 雖然解壓程序比較復雜. 下面是總結的流程圖 :

左邊就是 PNG 之類的, 右邊是 Unity 下的 PNG 轉換為 PVR, 左邊直接在 CPU 階段解壓成 24/32 bit 的位圖了, 傳輸到 GPU, 右邊的一直以壓縮格式傳輸.
這樣回到最初的那個問答, 本地資源或是 AssetBundle 中讀取出來的圖片, 正常來說還是那樣, 圖片保持壓縮格式進入 GPU 解壓, 或者叫 Decode 得到像素值, 不過如果從 WWW 之類的獲取到的 PNG / JPG 圖片, 那就會在 CPU 中進行解壓, 然后位圖傳入 GPU.
再加一個 WWW 下載來的圖片跟本地圖片加載后的差別吧, 上面的已經看到了本地圖片記憶體占用 64KB(DXT), 而本地位圖的話, 占570KB, 同樣的圖片放到服務器然后看看 :
public class Test : MonoBehaviour { public UnityEngine.UI.RawImage img; void Start() { StartCoroutine(Load(@"http://127.0.0.1:44599/StreamingAssets/PNG001.PNG")); } IEnumerator Load(string url) { var www = new WWW(url); yield return www; img.texture = www.texture; img.texture.name = "PNG001.PNG"; } }

變成2M了, 這是發布后的 exe 運行得到的資料.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/3044.html
標籤:其他
