
1. 圖片格式和紋理格式
圖片格式,如 jpg/png 等
紋理格式,如 ETC1/ETC2/PVRTC/ASTC等
1.1 圖片
電腦硬盤上的圖片文件用來保存圖形或圖像的信息,包括大小、顏色等。圖片的格式很多,但總體上可以分為點陣圖和矢量圖兩種。
- 點陣圖:也叫位圖,記錄畫面上每一個像素的顏色信息,當對點陣圖進行縮放時會失真
- 矢量圖:也叫向量圖,矢量圖不記錄畫面中每個像素的信息,而是記錄元素的形狀和顏色的算法,當矢量圖被打開時,查看軟件根據(jù)矢量圖中的信息進行運算,展示運算的結(jié)果。無論顯示畫面是大還是小,畫面對應(yīng)的算法是不變的,所以對畫面進行倍數(shù)相當大的縮放,其顯示效果不會失真
常見的位圖格式:
- bmp 無壓縮位圖格式
- png 使用 lz77 壓縮算法對位圖進行了無損壓縮,帶 alpha 通道
- jpg 有損壓縮格式,不帶 alpha 通道
1.2 點陣圖文件大小因素
考慮無壓縮的 bmp 格式位圖,文件在硬盤上的大小和以下幾個因素有關(guān)
顏色的豐富度
假如一張位圖只有黑白兩種顏色,每個像素使用一位數(shù)字即可表示,若使用一個字節(jié)(8位)來表示一個像素的顏色,則可以表示=256種顏色,最用的位圖,將像素顏色的RGBA分量都使用一個字節(jié)來表示,一個像素需要4個字節(jié)表示
像素數(shù)量
像素數(shù)量越多,需要越大的空間來表示,像素的數(shù)量由圖片的寬度和高度決定計算公式
常用的位圖(RGBA32)文件大小計算公式jpg 和 png 格式圖片的大小
jpg 和 png 因為針對圖片的像素數(shù)據(jù)進行了壓縮,節(jié)省了空間,但因為壓縮算法的原因,壓縮比與圖片本身的顏色分布有關(guān),圖片的大小沒有固定的計算方法,一個大概的趨勢是:顏色的分布越有序,圖片文件越小,也就是相鄰像素的顏色相差越小,文件越小。關(guān)于 jpg 和 png 的壓縮算法,可以參考PNG圖片壓縮原理解析和PNG原理以及jpeg壓縮算法
1.3 紋理
圖片文件被加載到內(nèi)存后,被轉(zhuǎn)換為顯卡能夠識別的紋理提交到顯存中,供 GPU 進行采樣,紋理采樣是根據(jù) uv 坐標獲得對應(yīng)紋理中紋素顏色值的過程。
不同的顯卡和圖形API對紋理格式的支持不一樣,RGBA32被顯卡和圖形API完全支持,DirectX特有的DXTx只有微軟的部分設(shè)備支持,Android設(shè)備廣泛支持ETC系列,iOS則支持 PVRTC 系列。
1.4 加載紋理的過程
CPU將圖片從硬盤加載到內(nèi)存中,進行解碼得到 RGBA32 格式位圖,然后在 CPU 端進行編碼,得到GPU支持的紋理格式,提交到顯存供 GPU 采樣訪問。

這個過程中需要進行解碼、重新編碼,非常消耗 CPU,為了減少 CPU 的消耗,因此可以把圖片預(yù)先轉(zhuǎn)換成某種紋理格式放到硬盤上,CPU 加載后不需要解碼和重新編碼,直接提交給 GPU。大大節(jié)省了紋理從加載到使用的性能。
1.5 Unity 中的處理
在Unity中,任何圖片文件格式都存在一個導(dǎo)入過程,導(dǎo)入后的文件格式都是Texture2D,在Texture2D的導(dǎo)入設(shè)置選項中需要針對不同平臺設(shè)置紋理壓縮格式,在構(gòu)建時,會直接將轉(zhuǎn)換后的紋理格式構(gòu)建到安裝包或 assetbundle 中。
2. 問題
2.1 紋理為什么需要壓縮,為什么不直接使用 RGBA32 格式?
使用紋理壓縮格式的好處
- 減少外存大小,也就是在硬盤或安裝包的大小,使用RGBA32格式,一個像素的紋理占用4個字節(jié),1024x1024的圖片將占用4MB的安裝包空間,使用壓縮格式的紋理可以顯著減少空間占用
- 減少內(nèi)存大小,紋理被加載到內(nèi)存時,占用的內(nèi)存空間和外存一樣大,不需要解壓成RGBA32格式,GPU端也不需要解壓,可以直接訪問
- 帶寬消耗,從CPU傳遞給GPU的是壓縮數(shù)據(jù),數(shù)據(jù)量小,在移動平臺上,GPU和CPU共享內(nèi)存,GPU芯片讀取內(nèi)存同樣消耗帶寬,帶寬消耗過大通常是設(shè)備發(fā)熱的最大元兇
2.2 為什么不直接將 jpeg/png 作為紋理格式提交給 GPU
GPU 通常并行處理若干數(shù)據(jù),無法預(yù)測紋理中紋素被訪問的先后順序,因此紋理格式必須支持GPU的隨機訪問:
隨機訪問:任意給定像素坐標,能夠快速算出它在圖像數(shù)據(jù)中的地址,從而取得圖像數(shù)據(jù)
幾乎所有的紋理壓縮算法都以塊為單位壓縮和存儲紋素,可以理解為,每個紋素塊(通常為4x4)占用的字節(jié)數(shù)是確定的,所以當我們需要訪問某一個坐標的紋素時,可以明確得到該紋素所在塊的數(shù)據(jù),GPU紋理采樣單元針對該塊進行解壓,讀取對應(yīng)紋素數(shù)據(jù)即完成采樣。
而jpeg/png這類壓縮算法考慮了圖片的整體數(shù)據(jù),像素數(shù)據(jù)之間互相依賴,無法針對其中某一塊進行解壓,必須全部解壓才能正確訪問紋素,所以不適宜作為紋理格式。
png/jpeg 等壓縮方法通常用來優(yōu)化文件的存儲和傳輸
2.3 紋理壓縮有損嗎?
紋理壓縮算法幾乎都是有損壓縮,通常情況下有限度的信息損失換到的性能上的提升是值得的。
2.4 如果使用了 GPU 和圖形 API 不支持的紋理壓縮格式,會發(fā)生什么?
如果使用了硬件不支持的紋理壓縮格式,Unity在運行時會對紋理進行解壓所到無壓縮格式,而這種無壓縮格式只是格式上是無壓縮的,但是因為原始數(shù)據(jù)是有損壓縮,所以視覺上紋理精度和壓縮格式是一致的,因此單從視覺上很難察覺,而且占用的內(nèi)存是雙份紋理的開銷,而帶寬開銷是無壓縮格式的紋理大小所占用的開銷。
例如:ETC格式在PC端Dx11的顯卡是不支持的,如果使用,開銷計算如下
1024 x 1024 ETC2 8bit的格式在PC端,會產(chǎn)生5M的內(nèi)存開銷(1M壓縮的原圖+4M解壓出來的RGBA32bit圖)
1024 x 1024 ETC 4bit格式在PC端,會產(chǎn)生4.5M的內(nèi)存開銷(0.5M壓縮的原圖+4M解壓出來的RGBA32bit圖)
而如果貼圖格式在目標設(shè)備上支持,則不會發(fā)生內(nèi)存中的解壓縮操作,壓縮過的圖大小是多少,在內(nèi)存中的設(shè)備上大小就是多少:
1024 x 1024 ETC2 8bit的格式在Android端,會產(chǎn)生1M的內(nèi)存開銷(1M壓縮的原圖)
1024 x 1024 ETC 4bit格式在Android端,會產(chǎn)生0.5M的內(nèi)存開銷(0.5M壓縮的原圖)
2.5 紋理壓縮和解壓是速度重要嗎?
紋理壓縮是一次性的工作,例如 Unity 在導(dǎo)入資源時會有一段轉(zhuǎn)換時間,圖片被處理成對應(yīng)的紋理格式后存儲了下來,此時紋理壓縮的速度慢一些是可以接受的
GPU 對紋理進行采樣時,通常只會解壓一個紋素塊,不會全部解壓,這個過程本身就是很快的