虛擬貼圖理論之貼圖壓縮
虛擬貼圖無論對記憶體頻寬還是視訊記憶體頻寬,都造成了巨大的壓力,因此我們需要使用壓縮格式的檔案。從工程的角度來說我們需要了解各種壓縮演算法的壓縮比,壓縮效果,以及編碼譯碼的速度,從而選擇最佳方案。為了能夠更好的使用壓縮格式,我這裡簡單的瞭解了三種壓縮格式,分別是代表貼圖壓縮的jpg格式,代表紋理壓縮的dxt格式,以及代表通用壓縮的zip格式。在討論這三個壓縮格式之前,我們先了一下常見的底層壓縮演算法。
RLE壓縮演算法
比如下面的文字資訊“[data][data][data]",假定上述文字使用的是ASCII碼,那麼上面的字串佔用了12個位元組的記憶體(不考慮[])。我們第一個想法就是將重複的資訊用一種編碼表示,比如[5][data]。其中一個位元組代表塊重複的次數,另一個位元組代表重複的內容。這種壓縮演算法就是RLE壓縮演算法。
LZ77演算法
如上圖紅黑兩條豎線代表一個視窗,這個視窗不停的向前移動,演算法的目的就是找到後面的字元與視窗中重複的字串,然後按照(偏移起始地址,偏移量)的方式儲存資訊。
哈夫曼編碼
上面的LZ77演算法我們用(偏移起始地址,偏移量)儲存檔案資訊。假如偏移地址有1,2,3,4。我們如何編碼去儲存這些數字呢?如果用定長編碼去儲存,肯定會浪費空間,我們希望使用變長編碼,變長編碼需要滿足兩個條件:
1.出現頻率最高的數字,使用的編碼越少
2.每一個編碼都是字首無歧義編碼,也就說任意一個編碼不能是其他編碼的字首
哈夫曼樹剛好滿足這兩個條件,因此根據編碼出現的頻率生成一棵哈夫曼樹,然後對其進行編碼就形成了哈夫曼編碼。比如A,B,C,D,E,F出現的次數分別是2,3,7,9,18,25,最後生成的哈夫曼樹如下圖:
JPEG圖片壓縮原理概述
上面講的都是常見的壓縮演算法,它們都是無損的,如果想要深入的瞭解這方面的知識可以參考《Data Compression The Complete Reference》這本書,裡面很詳細的介紹了各種壓縮演算法。並不是所有資源都可以進行有失真壓縮,比如遊戲安裝包,小說等等。但是對於貼圖我們是可以犧牲一些圖片質量為檔案瘦身。JPEG 壓縮是有失真壓縮,但這個損失的部分是人的視覺不容易察覺到的部分,它充分利用了人眼對計算機色彩中的高頻資訊部分不敏感的特點,來大大節省了需要處理的資料資訊。
1.色度抽樣-有損
通常我們遊戲中使用的色彩空間是RGB,除了RGB色彩空間還有很多色彩空間比如YUV,YCbCr,YCoCg,這些色彩空間都可以和RGB空間進行無損轉換。為什麼會有這麼多的色彩空間呢?存在即合理,它們的存在是有一定的歷史原因的。比如YUV空間是為了同時支援黑白電視以及彩色電視。YUV空間將顏色分成亮度訊號Y以及色度訊號U和V。Y就是我們常用的亮度圖,黑白電視就使用Y進行解析。JPEG使用YCbCr空間進行壓縮,因為人眼睛中分為兩種細胞,柱狀細胞以及椎狀細胞,柱狀細胞分管亮度,椎狀細胞分管色彩,而柱狀細胞的數量是椎狀細胞的20倍,也就是說人的眼睛對亮度資訊特別敏感,而對色彩資訊不敏感。這也是為什麼一個遊戲效果的好壞要看光影表現。JPEG使用YCbCr空間,可以保留訊號Y而對訊號CbCr進行降取樣,比如按照4:2:0,4:2:2,4:1:1等等,如下圖:
JPEG格式的第一步壓縮就是色度抽樣,比如YUV 4:2:0的碼流為:
Y0 U0 Y1 Y2 U1 Y3
Y4 V0 Y5 Y6 V1 Y7
對映成8個畫素為:
[Y0 U0 V0] [Y1 U0 V0] [Y2 U1 V1] [Y3 U1 V1]
[Y4 U0 V0] [Y5 U0 V0] [Y6 U1 V1] [Y7 U1 V1]
可以看到壓縮比為4:1,因為丟失了UV訊號所以是有失真壓縮。
2.離散餘弦變化(DCT)- 無損
為了獲取高頻資訊,我們需要將色彩域轉換到頻域上,而這個轉換方法用到的數學理論就是DCT(類似傅立葉變換)。首先影像被分成了8*8的畫素組,每個畫素組可以被 8*8 個餘弦波精確表示。這64個餘弦波,可以組合成任意 8*8 的圖形。我們只要用係數對這64個餘弦波進行加權,就可以表示出任何的圖形。比如下圖:
經過 DCT 轉換後的頻率係數矩陣分別對應64個餘弦波在 8*8 圖形中的權重。可以看出,左上部分低頻區的係數比較大,右下高頻區的係數較小。鑑於人眼對高頻區的識別不敏感,所以在下面量化部分可以捨棄一些高頻區的資料。這裡的 DCT 變化還沒開始壓縮。
3.量化-有損
在 DCT 變化後,捨棄高頻區資料的過程稱為量化。有兩份量化表可供選擇,分別為亮度量化表和色度量化表:
上表分別為亮度量化表和色彩量化表,表示 50% 的影像質量。這兩張表中的資料基於人眼對不同頻率的敏感程度制定。
量化表是控制 JPEG 壓縮比的關鍵,可以根據輸出圖片的質量來自定義量化表,通常自定義量化表與標準量化表呈比例關係,表中數字越大則質量越低,壓縮率越高。PhotoShop 有12張量化表。
量化過程為,使用量化矩陣與前面得到的 DCT 矩陣逐項相除並取整。之前為亮度矩陣,顧使用亮度量化表:
量化是有損的,在解碼時,反量化會乘回量化表的相應值。由於存在取整,低頻段會有所損失,高頻段的0欄位則會被捨棄,最終導致影像質量降低。
4.熵編碼(zigzag scan & 霍夫曼編碼)- 無損
得到量化後的矩陣就要開始編碼過程了,首先要把二維矩陣變為一維陣列,這裡採用了 zigzag 排列,將相似頻率組在一起:
得出的序列我們可以使用RLE編碼進行壓縮,最後再使用哈夫曼編碼進一步壓縮,如下圖:
至此,這個亮度 8*8 的畫素組壓縮編碼完畢。以上步驟就是jpeg編碼的大體步驟,在實際工程中通常我們會用第三方工具進行壓縮和解壓。jpeg的壓縮率很高,可以大大降低磁碟IO讀取的速度,但是它的解碼太複雜,無法做為gpu支援的紋理格式,因此,我們在專案中還是要用cpu進行解碼,然後讀取資料生成紋理格式。在解壓的時候不僅增加了cpu的開銷,還增加了記憶體的使用量。而且對於視訊記憶體的頻寬沒有任何優化作用。為此我們還需要了解一種gpu支援的紋理壓縮格式比如dxt。
DXT紋理壓縮
DXT紋理壓縮格式之所以能被硬體支援,原因是它的原理非常簡單,如下圖:
DXT1格式的工作原理就是將貼圖分成4*4的塊,然後保留兩個畫素的顏色,另外再插值生成兩個16位畫素的顏色。然後用16個2bit的索引,索引這16個畫素,就像頂點索引一樣。DXT1的壓縮率為16 * 16 / (16*2 + 2*16) = 4。
DXT1格式的圖片質量損失還是挺嚴重的,主要是首先RGB24為被壓縮成了16為,然後就是隻選取兩個畫素,如果畫素之間差距比較大,就會有明顯的畫素丟失,如下圖:
通常這種情況發生在比較細的邊界中,對於這種情況我們可以提高貼圖的解析度來提高貼圖的質量,因為RGB對應DXT1的壓縮比是6,可以適當的提高解析度,增加貼圖的質量。
DXT1支援1位的alpha test,如果color_0 > color_1代表不使用alpha通道,這種情況和上面的描述一致,如果color_0 < color_1,代表開啟alphat test,之前的四個畫素,變成了三個畫素,索引裡面的11代表透明的畫素。
DXT2和DXT3中多了64位儲存alpha值,每個畫素分配4位,DXT2中顏色是已經完成了Premultiplied by alpha操作(已完成顏色與alpha的混合,當透明度發生改變時,直接改變整體顏色值,不必再單獨複合),DXT3的Alpha資訊則是相對獨立的,之所以要區分開了則是為了適應不同的需要,因為有些場合需要獨立的Alpha資訊。
DXT4和DXT5也是用於表示具有複雜的透明資訊的貼圖,與2和3不同的是4和5的Alpha資訊是通過線性插值計算所得,類似於DXT1的顏色資訊。同樣的,每4×4的畫素塊的透明資訊佔用64位,所不同的是,64位中採用了2個8位的alpha值和16個3位的索引值,既然每個畫素的索引佔3位,那麼可以表示8種不同的透明狀態。在這裡插值的方法有兩種,一種用於表示具有完全透明和完全不透明的狀態,另一種則是僅在給出的極端值alpha_0和alpha_1中進行插值。區分的方法也是通過比較alpha_0和alpha_1的大小來實現的,如果alpha_0大於alpha_1,則通過插值計算剩下的6箇中間alpha值;否則,只通過插值計算4箇中間alpha值,alpha_6直接賦值0,alpha_7直接賦值255。DXT4和DXT5的區別同DXT2和DXT3的區別相同,DXT4的顏色值是理解為已經完成Premultiplied by alpha操作的。另外需要注意的是,所有的壓縮紋理格式都是2的冪,因為紋理壓縮的單位是4×4畫素,所以如果貼圖的大小為16×2或者8×1這樣的比例,系統會同樣採用4×4的單位進行壓縮,會造成一定的空間浪費,同樣的大小會被佔用,只是不會參與使用而已。
為了提高壓縮紋理的表現效果,我們可以使用YCoCg色彩空間配合DXT5的壓縮格式,實現高質量的壓縮紋理。我們使用DXT5的Alpha通道儲存亮度(Y),用色彩通道儲存色差資訊。然後在ps中將YCoCg空間轉換到RGB空間,YCoCg空間轉換到RGB空間的演算法非常高效只需要移位操作和加減操作。
ZIP壓縮
zip壓縮說簡單也簡單,說複雜也複雜,複雜在細節,簡單是因為它底層使用的就是lz系列演算法+哈夫曼編碼的變種。我這裡就不班門弄斧了。
總結
最優的壓縮方案可能就是:
YCoCg空間+DXT5壓縮+zip壓縮
這一套組合拳即保證了磁碟IO,又保證了視訊記憶體IO,還降低了記憶體和視訊記憶體,而且還保證了質量。
相關文章
- 遊戲包體太大,記憶體不足?Unity貼圖壓縮注意這幾點遊戲記憶體Unity
- 圖片拼貼處理軟體Posterino for macMac
- 簡單的 OpenGL 紋理貼圖不起作用?
- Nginx網路壓縮 CSS壓縮 圖片壓縮 JSON壓縮NginxCSSJSON
- 法線貼圖那些事兒
- threejs 貼圖動畫總結JS動畫
- WebGL學習之法線貼圖Web
- word貼上圖片到ckeitor
- 前端圖片壓縮 - H5&Uni-App圖片壓縮前端H5APP
- canvas 壓縮圖片Canvas
- 圖片壓縮20201109
- ??圖片壓縮CanvasCanvas
- 實用、易用的截圖與貼圖工具:SnipasteAST
- 使用SVG做模型貼圖的思路SVG模型
- ARKit 如何給SCNNode貼Gif圖片CNN
- ffmpeg-圖片壓縮旋轉等處理
- 圖論-有向圖縮點圖論
- Android 圖片處理之固定視框中的等比例壓縮Android
- 前端圖片壓縮方案前端
- Photoshop壓縮png圖片
- iOS 圖片壓縮方法iOS
- Android高德地圖貼合圖片完成手繪地圖展示Android地圖
- Unity 分離貼圖 alpha 通道實踐Unity
- vue 實現貼上上傳圖片Vue
- Mac圖片拼貼編輯器:PosterinoMac
- 容器flappybird遊戲——圖文操作指引貼APP遊戲
- 前端的圖片壓縮image-compressor(可在圖片上傳前實現圖片壓縮)前端
- Laravel editor.md 支援截圖 / 貼上上傳圖片Laravel
- SmallImage for Mac(圖片壓縮工具)Mac
- js圖片壓縮推薦JS
- js上傳圖片壓縮JS
- JNI實現圖片壓縮
- png格式如何壓縮,圖片壓縮工具哪個好
- Marvelous Designer基礎操作3 - 貼圖匯出
- 在輸入框裡直接貼上圖片
- flameshot 在centos stream 9上貼圖不生效CentOS
- DirectX11 With Windows SDK--34 位移貼圖Windows
- unreal engine 4 如何建立地形、地表貼圖。Unreal