實時DXT壓縮

                                                                               實時DXt壓縮算法

JMP van Waveren 
id Software,Inc.

 


NVIDIA公司

2008年2月7日

©2008,id Software,Inc.


翻譯此文,我覺得對於理解Doom3和rage的dxt壓縮算法,有很大的幫助,儘管翻譯的地方有所失誤,

原文下載地址,(注,原文寫的更好,強烈建議看原文)

 

使用今天的圖形硬件,普通地圖可以存儲在幾種壓縮格式中,這些格式在渲染過程中在硬件上即時解壓縮。對使用現有紋理壓縮格式的幾個對象空間和正切空間法線貼圖進行評估。雖然這些格式的解壓縮在渲染過程中在硬件中實時發生,但是使用現有的壓縮器可能需要大量時間的壓縮。提出了兩種高度優化的切線空間法線貼圖壓縮算法,可以在CPU和GPU上實現實時性能。


 

介紹

   凹凸映射使用紋理來擾亂表面法線,以使對象更具有幾何複雜的外觀,而不增加幾何圖元的數量。如Blinn [ 1 ] 最初描述的,凹凸貼圖在計算表面的照明之前,使用凹凸貼圖高度場的梯度來擾動表面衍生(方向向量)方向上的內插表面法線。通過改變表面法線,表面被點亮,好像它具有更多的細節,結果也被認爲比用於描述表面的幾何圖元具有更多的細節。

正態映射是凹凸映射的應用,由Peercy等引入 2 ]。雖然凹凸貼圖擾亂了對象的現有表面法線,但是正常映射完全取代了法線。普通地圖是存儲法線的紋理。這些法線通常存儲爲具有三個組件的單位長度向量:X,Y和Z.正常映射比凹凸貼圖具有顯着的性能優勢,因爲計算表面照明所需的操作更少。

通常的映射通常在兩個品種中找到:對象空間和正切空間法線映射。它們在測量和存儲法線的座標系中不同。對象空間法則映射相對於整個對象的位置和方向存儲法線。切線空間法線相對於三角形頂點的內插切線空間存儲。雖然物體空間法線可以是單位球體上的任何地方,但正切空間法線僅在表面前方的單位半球上,因爲法線總是指向表面。

對象空間法線貼圖(左)
和切線空間中相同法線貼圖(右)的示例

正常並不一定必須存儲爲具有組件X,Y和Z的向量。然而,從其他表示形式的渲染通常以性能成本來存儲。例如,通常可以存儲爲角度對(俯仰,偏航)。然而,該表示具有內插或濾波不能正常工作的問題,因爲存在可能不存在用於表示局部旋轉的角度的簡單變化的方向。在內插,濾波或計算該物體的表面照明之前,角度對必須轉換爲不同的表示,如矢量,這需要昂貴的三角函數。

儘管法線貼圖可以存儲爲浮點紋理,但是通常將法線貼圖存儲爲有符號或無符號整數紋理,因爲法向矢量的分量取值範圍很廣(通常爲[-1,+1] ),並且在不浪費浮點指數的任何位的情況下,在整個範圍內具有相同的精度是有益的。例如,要將法線貼圖存儲爲每個分量8位無符號整數紋理,X,Y和Z分量將從[-1,+1]範圍內的實數值重新整數到[0, 255]。這樣,實值向量[0,0,1]被轉換成整數向量[128,128,255],當被解釋爲RGB空間中的一個點時,是紫色/藍色主要在切線空間法線貼圖 爲了呈現存儲爲無符號整數紋理的法線貼圖,矢量分量首先從一個整數值映射到硬件中的浮點範圍[0,+1]。

    例如,在每個分量具有8位的紋理的情況下,通過與255分割將整數範圍[0,255]映射到浮點範圍[0,+1]。然後,組件通常從[0,+1]範圍在片段程序渲染過程中通過在與2相乘後減1後範圍爲[-1,+1]範圍。當使用有符號整數紋理時,從整數值到浮點數範圍[-1,+1]直接在硬件中執行。矢量分量首先從整數值映射到硬件中的浮點範圍[0,+1]。例如,在每個分量具有8位的紋理的情況下,通過與255分割將整數範圍[0,255]映射到浮點範圍[0,+1]。然後,組件通常從[0,+1]範圍在片段程序渲染過程中通過在與2相乘後減1後範圍爲[-1,+1]範圍。當使用有符號整數紋理時,從整數值到浮點數範圍[-1,+1]直接在硬件中執行。矢量分量首先從整數值映射到硬件中的浮點範圍[0,+1]。例如,在每個分量具有8位的紋理的情況下,通過與255分割將整數範圍[0,255]映射到浮點範圍[0,+1]。然後,組件通常從[0,+1]範圍在片段程序渲染過程中通過在與2相乘後減1後範圍爲[-1,+1]範圍。當使用有符號整數紋理時,從整數值到浮點數範圍[-1,+1]直接在硬件中執行。

    是否使用有符號或無符號整數紋理,一個根本的問題是不可能從二進制整數到浮點範圍[-1,+1] 導出線性映射,使得值-1,和+1表示準確。在先前的NVIDIA實現中使用的帶符號整數紋理的硬件映射並不完全代表+1。對於n位無符號整數分量,整數0映射到-1,整數2 n-1映射到0,最大整數值2 n -1映射到1 - 2 1-n換句話說,值-1和0被精確地表示,但值+1不是。用於DirectX 10類硬件的映射是非線性的。對於n位有符號整數分量,整數-2 n-1映射到-1,整數-2 n-1 +1也映射到-1,整數0映射到0,整數2 n-1 -1映射到+1。換句話說,值-1,0和+1都被精確地表示,但值-1表示兩次。

舊硬件不支持簽名紋理。此外,從二進制整數到範圍[-1,+1]的映射可能是硬件特定的。一些實現可能選擇不完全代表+1,而傳統的OpenGL映射指定-1和+1可以被精確地表示,但是0不能。其他實現可以選擇非線性映射,或允許超出範圍[-1,+1]的值,使得可以精確地表示所有三個值-1,0和+1。爲了覆蓋最廣泛的硬件範圍,沒有任何硬件特定的依賴關係,這裏使用的所有法線圖被假設存儲爲無符號整數紋理。從[0,+1]到[-1,+1]範圍內的映射在片段程序中通過在乘以2之後減1來執行。這可能導致附加的片段程序指令,當使用帶符號的紋理時,可以將其簡單地刪除這裏使用的映射與傳統的OpenGL映射相同,這導致值-1和+1的精確表示,但不是0。

整數法線貼圖通常可以按照每個法向量16(5:6:5),24(8:8:8),48(16:16:16)或96(32:32:32)位存儲。然而,今天的大部分普通地圖都不存在每個法向量不超過24(8:8:8)位。重要的是要認識到實際上接近單位長度的相對較少的8:8:8位向量。例如,在RGB空間中爲深藍色的整數向量[0,0,64]不表示單位長度的法向量(長度爲0.5而不是1.0)。下圖顯示了比單位長度小於特定百分比的可表示的8:8:8位向量的百分比。

例如,如果法向量不被認爲是單位長度大於5%的可接受的話,那麼只有大約15%的可表示的8:8:8位向量可用於表示法向量。如5:6:5位那樣精確度較低的位,接近單位長度的可表示向量的數量迅速減少。

爲了顯着增加可以使用的向量的數量,可以將每個法向量存儲爲不一定是單位長度的方向。然後,這個方向需要在片段程序中進行歸一化。然而,仍然有一些浪費,因爲只有83%的所有8:8:8位向量表示唯一的方向。例如,整數向量[0,0,32],[0,0,64]和[0,0,96]都指定完全相同的方向(它們是彼此的倍數)。此外,獨特的標準化方向不是均勻分佈在單位球體上。對於[-1,+1] x [-1,+1] x [-1,+1]向量空間的邊界框的四個對角線的方向,有更多的表示方式,到座標軸。例如,在向量[1,1,1]周圍的15度半徑內表示的方向比在向量[0,0,1]周圍15度半徑內的方向表示三倍。下圖顯示了投射到單位球體上的所有可表示的8:8:8位矢量的分佈。載體密度較低的區域爲綠色,密度較高的區域爲紅色。

8:8:8位向量
投影在單位球體上的分佈

在今天的圖形硬件上,普通地圖也可以以多種壓縮格式存儲,在渲染過程中實時解壓縮。由於減少的帶寬要求,壓縮的法線貼圖不僅要求顯卡內存少得多,而且通常還會比未壓縮的法線貼圖快。各種不同的方式來利用現有的紋理壓縮格式的正常地圖壓縮,已被建議在文獻[ 789 ]。這些正常的地圖壓縮技術中的幾個以及它們的擴展在第2節和第3節進行了評估。

雖然這些格式的解壓縮是在硬件中實時完成的,但對這些格式的壓縮可能需要相當長的時間。現有壓縮機設計爲高質量離線壓縮,而不是實時壓縮[ 202122 ]。然而,實時壓縮對於從不同格式的代碼轉換法線圖,動態生成的法線貼圖的壓縮以及壓縮的法線貼圖渲染目標來說是非常有用的。在第4節和第5節中,提出了兩種高度優化的切線空間法線貼圖壓縮算法,可用於實現CPU和GPU的實時性能。

對象空間正常圖

對象空間法則映射相對於整個對象的位置和方向存儲法線。物體空間中的常規可以在整個單位球體上的任何地方,並且通常存儲爲具有三個組件的向量:X,Y和Z.可以使用常規顏色紋理壓縮技術存儲對象空間法線貼圖,但是這些技術可能不會有效,因爲法線貼圖不具有與顏色紋理相同的屬性。

2.1對象空間DXT1

DXT1 [ 34 ],也被稱爲在DirectX 10 BC1 [ 5 ],是用於顏色紋理一種有損壓縮格式,以8:1的固定壓縮比爲2:1。DXT1格式設計用於在渲染過程中對顯卡硬件進行實時解壓縮。DXT1壓縮是塊截斷編碼(BTC)[ 6 ]的一種形式,其中圖像被劃分爲非重疊塊,並且每個塊中的像素被量化爲有限數量的值。4×4像素塊中的像素的顏色值通過RGB顏色空間在一行上的等距點近似。該行由兩個端點定義,對於4x4塊中的每個像素,2位索引存儲在該行上的一個等距點上。通過顏色空間的線的端點被量化爲16位5:6:5 RGB格式,並且通過插值生成一個或兩個中間點。DXT1格式允許通過根據終點的順序切換到不同的模式,其中僅生成一箇中間點並指定一個附加顏色,這是黑色和完全透明的。

雖然DXT1格式是爲顏色紋理設計的,但此格式也可用於存儲法線貼圖。爲了將法線貼圖壓縮成DXT1格式,法線向量的X,Y和Z分量映射到顏色紋理的RGB通道。特別是對於DXT1壓縮,每個法向量分量從[-1,+1]範圍映射到整數範圍[0,255]。在光柵化期間,DXT1格式在硬件中解壓縮,並且整數範圍[0,255]映射到硬件中的浮點範圍[0,1]。在片段程序中,範圍[0,1]將必須映射回範圍[-1,+1]以執行法向量的照明計算。以下片段程序顯示瞭如何使用單個指令實現此轉換。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = normal.z [0,1]
#input.w = 0
 
MAD正常,輸入,2.0,-1.0

將法線貼圖壓縮成DXT1格式通常會導致質量相當差。有明顯的阻塞和條帶僞影。每4×4塊只能編碼四個不同的法向量,這通常不足以準確地表示塊中的所有原始法向量。因爲每個塊中的法線在一行上用等距點近似,所以也不可能對每個4×4塊的四個不同的法向量編碼都是單位長度。每4x4塊只有兩個法向量可以一次接近單位長度,通常一個壓縮器通過向量空間來選擇一個最小化一些誤差度量的一條線,使得這些向量實際上不接近單位長度。

右側的DXT1壓縮法線貼圖
與左側的原始法線貼圖相比,顯示出明顯的塊狀僞影。

爲了提高質量,可以將每個法向量編碼爲不一定是單位長度的方向。然後,該方向必須在片段程序中重新歸一化。以下片段程序顯示了法向量如何重新歸一化。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = normal.z [0,1]
#input.w = 0
 
MAD正常,輸入,2.0,-1.0
DP3標尺,正常,正常
RSQ scale.x,scale.x
MUL正常,正常,scale.x

編碼方向使壓縮機更自由,因爲壓縮機不必擔心向量的大小,並且可以通過正常空間將所有可表示向量的大部分百分比用於線路的終點。然而,這種增加的自由使壓縮成爲一個更難的問題。

DXT1壓縮的法線貼圖與
右側的原始法線貼圖相比右側重新歸一化

以上圖片顯示,儘管質量好一點,質量一般還是比較差。無論是否在片段程序中重新規範化,DXT1壓縮對象空間法線貼圖的質量一般不被認爲是可以接受的。

2.2對象空間DXT5

所述DXT5格式[ 34 ],也被稱爲在DirectX 10 BC3 [ 5 ],存儲三個顏色通道中的相同的方式DXT1確實,但沒有1位alpha通道。代替1位alpha通道,DXT5格式存儲與DXT1色彩通道相似的單獨的Alpha通道。4x4塊中的alpha值通過α空間在一行上的等距點近似。通過α空間的線的端點存儲爲8位值,並且基於端點的順序,通過插值生成4或6箇中間點。對於4箇中間點的情況,生成兩個附加點,一個完全不透明,另一個用於完全透明。對於4x4塊中的每個像素,3位索引通過alpha空間或兩個附加點中的一個存儲到該行上的等距點之一,用於完全不透明或完全透明。使用相同數量的位來將alpha通道編碼爲三個DXT1顏色通道。因此,與三維顏色空間相反,α通道以比每個顏色通道更高的精度被存儲,因爲α空間是一維的。

     此外,總共有8個樣本表示4×4塊中的α值,而不是4個樣本來表示顏色值。由於額外的Alpha通道,DXT5格式消耗DXT1格式的內存量的兩倍。使用相同數量的位來將alpha通道編碼爲三個DXT1顏色通道。因此,與三維顏色空間相反,α通道以比每個顏色通道更高的精度被存儲,因爲α空間是一維的。

   此外,總共有8個樣本表示4×4塊中的α值,而不是4個樣本來表示顏色值。由於額外的Alpha通道,DXT5格式消耗DXT1格式的內存量的兩倍。使用相同數量的位來將alpha通道編碼爲三個DXT1顏色通道。因此,與三維顏色空間相反,α通道以比每個顏色通道更高的精度被存儲,因爲α空間是一維的。此外,總共有8個樣本表示4×4塊中的α值,而不是4個樣本來表示顏色值。由於額外的Alpha通道,DXT5格式消耗DXT1格式的內存量的兩倍。總共有8個樣本來表示4×4塊中的α值,而不是4個樣本來表示顏色值。由於額外的Alpha通道,DXT5格式消耗DXT1格式的內存量的兩倍。總共有8個樣本來表示4×4塊中的α值,而不是4個樣本來表示顏色值。由於額外的Alpha通道,DXT5格式消耗DXT1格式的內存量的兩倍。

  DXT5格式設計用於具有平滑alpha通道的顏色紋理。但是,此格式也可用於存儲對象空間法線貼圖。特別地,可以通過使用DXT5格式並將其中一個組件移動到Alpha通道來實現更好質量的法線貼圖壓縮。通過將其中一個組件移動到Alpha通道,該組件以更高的精度存儲。此外,通過僅對DXT5格式的DXT1塊中的兩個組件進行編碼,這些組件的存儲準確度通常也會得到改善。對於對象空間法線圖,由於法向量可以指向任何方向,因此所有元素都可以以相似的頻率發生,所以沒有明確的優點將任何特定的組件移動到alpha通道。

   當對象空間法線貼圖在特定方向上確實具有最多的向量時,將與該方向最正交的軸線映射到阿爾法通道顯然是一個好處。然而,由於每個編碼需要不同的片段程序,所以通常在每個法線貼圖上改變編碼是不切實際的。以下片段程序假設Z組件移動到alpha通道。片段程序顯示了組件如何從範圍[0,1]映射到範圍[-1,+1],而Z組件也從alpha通道移回原位。因爲每個編碼都需要一個不同的片段程序。以下片段程序假設Z組件移動到alpha通道。片段程序顯示了組件如何從範圍[0,1]映射到範圍[-1,+1],而Z組件也從alpha通道移回原位。因爲每個編碼都需要一個不同的片段程序。以下片段程序假設Z組件移動到alpha通道。片段程序顯示了組件如何從範圍[0,1]映射到範圍[-1,+1],而Z組件也從alpha通道移回原位。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = 0
#input.w = normal.z [0,1]
 
MAD normal,input.xywz,2.0,-1.0

就像DXT1一樣,沒有重新規範化,這種格式導致片段程序中的最小開銷。質量明顯優於對象空間法向貼圖的DXT1壓縮。然而,仍然存在明顯的阻塞和條紋僞像。

右側的DXT5壓縮法線貼圖
與左側原始法線貼圖相比較。

使用第三個通道來存儲比如從[ 24 ] 的YCoCg-DXT5壓縮完成的比例因子並不能提高質量。單個組件的動態範圍通常太大,或者不同的組件跨越不同的範圍,而組合的動態範圍只有一個比例因子。

就像對象空間法線圖的DXT1壓縮一樣,通過將法向量編碼爲不一定是單位長度的方向,可以提高質量。以下片段程序顯示如何執行swizzle和重新歸一化。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = 0
#input.w = normal.z [0,1]
 
MAD normal,input.xywz,2.0,-1.0
DP3標尺,正常,正常
RSQ scale.x,scale.x
MUL正常,正常,scale.x

編碼方向給予壓縮機更多的自由度,因爲壓縮器可以忽略向量的大小,並且可以通過正常空間將所有可表示向量的更大百分比用於線的端點。使用DXT5格式的DXT1塊和alpha通道對法線矢量進行編碼,其中Alpha通道的端點不存儲量化。因此,線的端點的潛在搜索空間可能非常大,並且高質量壓縮可能需要相當長的時間。

DXT5壓縮的法線貼圖與
右側的原始法線貼圖相比右側重新歸一化

在當前硬件上,片段程序中重新歸一化的DXT5格式導致對象空間法線貼圖的最佳質量壓縮。

切線 - 空間正常圖

切線空間法向量相對於三角形頂點的內插切線空間存儲。因爲動態範圍較低,切線空間法線貼圖的壓縮通常比對象空間法線貼圖的效果更好。向量只在表面前面的單位半球(法向量從不指向對象)。此外,大多數正常向量接近單位半球的尖端,Z接近1。

與使用對象空間法線圖相比,使用切線空間法線貼圖本身可以被認爲是一種壓縮形式。使用局部變換來改變矢量分量的頻域,從而降低其存儲要求。該變換確實需要將切向量存儲在三角形頂點處,並且因此成本。然而,與正常地圖的存儲要求相比,切線向量的存儲要求相對較小。

通過僅存儲單位長度法向量的X和Y分量以及導出Z分量,可以改善切線空間法線貼圖的壓縮。法向量總是向上指向表面,Z總是爲正。此外,法向量是單位長度,因此,可以如下導出Z。

Z = sqrt(1-X * X-Y * Y)

從X和Y重建Z的問題是它是一個非線性運算,並在雙線性濾波下分解。當在XY平面中的兩個法線之間進行插值時,這個問題最爲顯着。理想地,使用法向量的球面插值來放大法線貼圖,其中內插樣本以恆定速度跟隨單位球體上的最短大弧。三分法法線圖的雙線性濾波,在片段程序中進行重新歸一化,不會以恆定速度產生球面插值,但至少內插樣本遵循最短的大弧。然而,使用雙分量法線貼圖,其中Z從X和Y導出,內插樣本不再必須跟隨單位球上的最短大弧。例如,預計下圖中兩個向量之間的插值將跟隨虛線。然而,相反,內插樣本在單位球上上升的弧上。

幸運的是,現實世界的正常地圖通常與鄰近XY平面的鄰近矢量不具有很多尖銳的正常邊界,大多數法線指向直線。因此,在導出Z分量之前,雙線性或三線性過濾雙分量法線圖時通常不會有明顯的僞影。

僅存儲X和Y分量本質上是沿Z軸的法向量向XY平面的正投影。爲了重構原始法向量,通過從X和Y中導出Z分量,可以使用返回到單位半球的投影。代替該正投影,也可以使用立體投影。對於立體投影,X和Y分量如下劃分一個加Z,其中(pX,pY)是法向量的投影。

pX = X /(1 + Z)
pY = Y /(1 + Z)

通過將立體投影向量投影到單位半球上來重建原始法向量,如下。

denom = 2 /(1 + pX * pX + pY * pY)
X = pX * denom
Y = pY * denom
Z = denom  -  1

使用立體投影的優點是內插法線矢量在雙線性或三線性濾波下表現更好。內插的法向量仍然不在最短的大弧上,但是它們更接近,並且在單位半球上具有更少的上升趨勢。

立體投影還導致pX和pY分量的更均勻分佈與單位半球上的角度。雖然這似乎是可取的,但實際上並不是這樣,因爲大多數正切空間的法向量靠近單位半球的尖端。因此,使用正交投影實際上有一個優點,其導致Z接近1的向量的更多表示。下面討論的壓縮技術使用正投影,因爲對於大多數正常映射,其導致更好的質量壓縮。

3.1切線空間DXT1

使用正切空間法線圖,只有X和Y分量必須以DXT1格式存儲,Z分量可以在片段程序中導出。以下片段程序顯示如何從X和Y派生Z。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = 0
#input.w = 0
 
MAD正常,輸入,2.0,-1.0
DP4_SAT normal.z,正常,正常;
MAD正常,正常,{1,1,-1,0},{0,0,1,0};
RSQ temp,normal.z;
MUL normal.z,temp;

以下圖像顯示右側的XY_ DXT1壓縮法線貼圖,位於左側原始法線貼圖旁邊。DXT1壓縮的法線貼圖顯示明顯的阻塞和條紋僞影。

XY_ DXT1壓縮的法線貼圖
與左側原始法線貼圖相比較。

儘管起初似乎這種壓縮應該產生優質的質量,但通常可以通過存儲所有三個組件並在片段程序中重新歸一化來實現更好的質量壓縮,就像對象空間法線圖一樣。當只有X和Y分量以DXT1格式存儲時,通過導出Z分量來自動歸一化重構法線矢量。當X和Y分量由於DXT1壓縮而失真,其中所有點通過XY空間放置在直線上時,導出的Z中的誤差可能相當大。

下面顯示的用於重新歸一化DXT1壓縮法線的片段程序與用於具有重新歸一化的DXT1壓縮對象空間法線貼圖相同。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = normal.z [0,1]
#input.w = 0
 
MAD正常,輸入,2.0,-1.0
DP3標尺,正常,正常
RSQ scale.x,scale.x
MUL正常,正常,scale.x

以下圖像顯示了右側重新歸一化的DXT1壓縮法線貼圖,在左側的原始法線貼圖旁邊。

DXT1壓縮的法線貼圖與
右邊的正規化法相比,左邊是原來的法線貼圖。


無論哪種方式,無論是在DXT1中存儲兩個組件並導出Z,還是將DXT1格式中的所有三個組件存儲在片段程序中的重新歸一化,質量相當差。

3.2切線空間DXT5

就像對象空間法線圖一樣,所有三個組件都可以以DXT5格式存儲。通常在存儲_YZX數據時可以獲得最佳結果。換句話說,X分量被移動到alpha通道。這種技術也被稱爲RxGB壓縮,並被用於電腦遊戲DOOM III。通過將X分量移動到alpha通道,X和Y分量被分別編碼。這提高了質量,因爲X和Y組件在最大動態範圍內最獨立。Z始終爲正,通常接近1,因此,將Z分量與Y分量存儲在DXT5格式的DXT1部分中會導致Y分量幾乎不失真。存儲所有三個組件會導致片段程序中的最小開銷,如下所示。

#input.x = 0
#input.y = normal.y [0,1]
#input.z = normal.z [0,1]
#input.w = normal.x [0,1]
 
MAD normal,input.wyzx,2.0,-1.0

以下圖像顯示,雖然質量優於DXT1壓縮,但仍然有明顯的條帶僞像。

右側的DXT5壓縮法線貼圖
與左側原始法線貼圖相比較

就像對象空間法線圖一樣,可以通過存儲不一定是單位長度的方向來改善質量。通過將X組件移動到DXT5 alpha通道也可以實現最佳質量。以下片段程序顯示瞭如何在將X組件從alpha通道移回到原位後如何重新歸一化方向。

#input.x = normal.x [0,1]
#input.y = normal.y [0,1]
#input.z = 0
#input.w = normal.z [0,1]
 
MAD normal,input.wyzx,2.0,-1.0
DP3標尺,正常,正常
RSQ scale.x,scale.x
MUL正常,正常,scale.x

以下圖像顯示,在片段程序中具有重新歸一化的編碼方向減少了帶狀僞像,但是它們仍然非常顯着。

DXT5壓縮的法線貼圖與
右側的原始法線貼圖相比右側重新歸一化

對於大多數正切空間的法線貼圖,僅通過將X和Y組件存儲在DXT5格式中並獲得Z即可實現更好的質量壓縮。這也稱爲DXT5nm壓縮,並且在當今的電腦遊戲中是最受歡迎的。以下片段程序顯示了Z是如何從X和Y組件導出的。

#input.x = 0
#input.y = normal.y [0,1]
#input.z = 0
#input.w = normal.x [0,1]
 
MAD normal,input.wyzx,2.0,-1.0
DP4_SAT normal.z,正常,正常;
MAD正常,正常,{1,1,-1,0},{0,0,1,0};
RSQ temp,normal.z;
MUL normal.z,temp;

以下圖像顯示僅存儲X和Y並導出Z,進一步減少了帶狀僞像。

DXT5壓縮的法線貼圖只在
右邊的X和Y 與左邊原來的法線貼圖相比較。

當使用XY_ DXT1,_YZX DXT5或_Y_X DXT5壓縮用於切線空間法線貼圖時,至少有一個可用於存儲比例因子的備用通道,可用於對抗與YCoCg-DXT5類似的量化誤差壓縮機從[ 24 ]。然而,嘗試升級組件以對抗量化誤差不會提高質量(通常PSNR改善小於0.1 dB)。組件只有在動態範圍較小時才能進行放大。雖然大多數法線指向直線,並且大多數XY向量的幅度相對較小,但XY分量的動態範圍實際上仍然相當大。即使所有的法線都不會從直線偏離45度以上,那麼每個X或Y分量仍然可以映射到範圍[-cos(45°)+ + cos(45°)],其中cos(45°)≅0.707。換句話說,即使是從直線向下偏離小於45度的角度範圍的50%,每個部件仍然可以覆蓋超過最大動態範圍的70%。一方面,這是一件好事,因爲對於正切空間法向量的分量,這意味着動態範圍的最大部分涵蓋最常發生的值。另一方面,這意味着由於動態範圍相對較大而難以升級組件。因爲對於正切空間法向矢量的分量,這意味着動態範圍的最大部分涵蓋最常發生的值。另一方面,這意味着由於動態範圍相對較大而難以升級組件。因爲對於正切空間法向矢量的分量,這意味着動態範圍的最大部分涵蓋最常發生的值。另一方面,這意味着由於動態範圍相對較大而難以升級組件。

在_Y_X DXT5壓縮切線空間法線圖的情況下,有兩個未使用的通道,其中一個通道也可用於存儲偏置以使動態範圍居中。這顯着增加了4x4塊的數量,可以對這些塊進行放大(通常,所有4x4塊中的75%以上的塊通常使用至少爲2的比例因子)。然而,即使使用偏差來增加縮放的4×4塊的數量對提高質量沒有太大的幫助。真正的問題是DXT1塊的四個採樣點根本不足以準確地表示4x4塊中法線的所有Y分量。引入更多的採樣點將顯着提高質量,但這在DXT5格式中顯然是不可能的。

代替存儲偏差和比例,備用信道之一,也可用於存儲正常矢量的旋轉在繞Z軸的4×4塊,如[建議的1112 ]。可以使用這樣的旋轉來找到XY向量的更緊密的邊界框。特別是使用_Y_X DXT5壓縮這樣的旋轉可以用來確保具有最大動態範圍的軸映射到阿爾法通道,這樣被更精確地壓縮。爲了能夠將具有最大動態範圍的軸映射到Alpha通道,可能需要高達180度的旋轉。該旋轉可以作爲5位通道之一中的整個4x4塊的常數值存儲。代替存儲旋轉角度,可以存儲角度的餘弦,使得餘弦不必在片段程序中計算,其中矢量需要旋轉回其原始位置。在[0,180]度範圍內進行旋轉的正弦值總是爲正值,因此可以如下從片段程序中的餘弦中導出。

正弦= sqrt(1  - 餘弦*餘弦)

在4×4塊中旋轉法線的PSNR改善是顯着的,通常在2至3dB的範圍內。不幸的是,相鄰的4×4塊可能需要非常不同的旋轉,並且在雙線性或三線性濾波下,對於具有不同旋轉的兩個4×4塊之間的邊界處的濾波紋跡樣本,可能出現可見的僞像。在旋轉施加到X和Y分量之前,分別對X,Y和旋轉進行過濾。這樣,濾波的旋轉被施加到經過濾的X和Y分量,其與首先旋轉回其原始位置的濾波X和Y分量不同。換句話說,除非法線貼圖只是點採樣,使用旋轉也不是提高DXT1或DXT5法線貼圖質量的選擇。

當然,如[ 8 ] 所述,非歸一化值仍然可以存儲在一個備用通道中非正規化值用於縮小較低mip級別的法向量,使得鏡面亮度隨着距離而消失以減輕混疊僞影。

3.3切線空間3Dc

3Dc格式[ 10 ]專門設計用於切線空間法線貼圖壓縮,併產生比DXT1或DXT5法線貼圖壓縮更好的質量。3Dc格式僅存儲兩個通道,因此不能用於對象空間法線貼圖。格式基本上由每個4x4法線塊的兩個DXT5 alpha塊組成。換句話說,對於每個4×4塊,存在8個X分量的樣本,並且對於Y分量也有8個獨立樣本。Z分量必須在片段程序中導出。

DirectX 10中的3Dc格式也被稱爲BC5 [ 5 ]。在OpenGL中可以將相同的格式作爲LATC或RGTC加載。使用LATC格式,在所有三個RGB通道中複製亮度。這可以特別方便,因爲這種方式可以將LATC和_Y_X DXT5(DXT5nm)壓縮的法線貼圖用於相同的旋轉(和片段程序代碼)。換句話說,相同的片段程序可以用在不支持3Dc的硬件上。以下片段程序顯示當法線貼圖以RGTC格式存儲時Z如何從X和Y分量派生。

#normal.x = x [0,1]
#normal.y = y [0,1]
#normal.z = 0
#normal.w = 0
 
MAD正常,正常,2.0,-1.0
DP4 normal.z,正常,正常;
MAD正常,正常,{1.0,1.0,-1,0},{0,0,1,0};
RSQ temp,normal.z;
MUL normal.z,temp;

以下圖像顯示了與_Y_X DXT5(DXT5nm)相比,正常地圖的3Dc壓縮如何導致顯着更少的條帶。

右側的3Dc壓縮法線貼圖
與左側的原始法線貼圖相比

到的3Dc幾個擴展,提出在[ 11 ]和專門用於改進正常地圖壓縮設計了一種新格式在[呈現12 ]。但是,這些格式在當前的圖形硬件中不可用。在所有DirectX 10兼容的硬件上,3Dc(或BC5)格式可以產生最佳質量切線空間法線貼圖壓縮。在不實現3Dc的舊硬件上,通常使用_Y_X DXT5(DXT5nm)實現最佳質量。

4.實時壓縮CPU

雖然從前面部分描述的格式解壓縮是在硬件中實時完成的,但對這些格式的壓縮可能需要相當長的時間。現有壓縮機設計爲高質量離線壓縮,而不是實時壓縮[ 202122 ]。然而,實時壓縮對於以不同(更高效的)格式壓縮存儲在磁盤上的法線貼圖,並且壓縮動態生成的法線貼圖是非常有用的。

在今天的渲染引擎中,切線空間法線貼圖比對象空間法線貼圖更受歡迎。在當前的硬件上,沒有壓縮格式可用於非常適用的對象空間法線貼圖。第2節中描述的物體 - 空間法線貼圖壓縮技術全部導致明顯的僞像,或壓縮非常昂貴。

對象空間法線貼圖也不能在動畫對象上使用。當物體表面動畫化時,物體空間法向矢量保持指向相同的物體空間方向。另一方面,切線空間法線貼圖相對於三角形頂點的切線空間存儲法線。當物體的表面動畫並且切面矢量(存儲在三角形頂點處)與表面一起變換時,相對於這些切向矢量存儲的切線 - 空間法向量也將與表面一起動畫。因此,這裏的重點是實時壓縮切線空間法線貼圖。

在3Dc(BC5或LATC)格式不可用的硬件上,_Y_X DXT5(DXT5nm)格式通常會產生最佳質量切線空間法線貼圖壓縮。實時_Y_X DXT5壓縮器與[ 23 ] 的實時DXT5壓縮器非常相似

首先計算XY正常空間的邊界框。用於近似X和Y值的兩條線從最小值到該邊界框的最大值。爲了改善均方誤差(MSE),邊界框在任一端插入線上採樣點之間的距離的四分之一。Y組件存儲在“綠色”通道中,在“彩色”空間上有4個採樣點。因此,最小和最大Y值插入範圍的1/16。X組件存儲在“alpha”通道中,並且在“alpha”空間上有8個採樣點。因此,最小和最大X值是在範圍的1/32之間插入的。

僅使用“彩色”通道的單個通道來存儲法線矢量的Y分量。使用這種知識,[ 23 ] 的實時DXT5壓縮器可以進一步針對_Y_X DXT5壓縮進行優化。通過Y空間在線上的最佳匹配點可以以類似的方式找到,在[ 23 ] 的DXT5壓縮器中,通過“alpha”空間的線上的最佳匹配點可以找到首先計算一組交叉點,其中Y值從最接近一個採樣點到另一個採樣點。

字節mid =(max-min)/(2 * 3);
 
字節gb1 = max  -  mid;
字節gb2 =(2 * max + 1 * min)/ 3  -  mid;
字節gb3 =(1 * max + 2 * min)/ 3  -  mid;

然後可以測試AY值是否大於等於每個交叉點,並且這些比較的結果(0爲false,1爲真)可以加在一起以計算索引。這導致索引0到3從最小到最大值的順序如下。

 
指數: 0 1 2 3
值: (max + 2 * min)/ 3 (2 * max + min)/ 3 最大

但是,“顏色”採樣點按照DXT5格式進行不同的排序,如下所示。

 
指數: 0 1 2 3
值: 最大 (2 * max + min)/ 3 (max + 2 * min)/ 3

從四個減去比較的結果,並將結果與​​按位邏輯AND和3結合,導致以下順序。

 
指數: 0 1 2 3
值: 最大 (2 * max + min)/ 3 (max + 2 * min)/ 3

訂單接近正確,但最小和最大值仍然交換。以下代碼顯示瞭如何將Y值與交叉點進行比較,以及如何從比較結果計算指標,其中索引0和1在XOR-ing結束時通過比較結果進行交換(2>指數)。

unsigned int result = 0;
for(int i = 15; i> = 0; i--){
    結果<< = 2;
    字節g =塊[i * 4];
    int b1 =(g> = gb1);
    int b2 =(g> = gb2);
    int b3 =(g> = gb3);
    int index =(4-b1-b2-b3)&3;
    index ^ =(2> index);
    result | = index;
}

使用SIMD指令,每個字節比較會產生一個字節,全部爲零(當表達式爲假時)或全部1位(當表達式爲真時)。當被解釋爲有符號(兩個補碼)整數時,字節比較的結果等於數字0(對於假)或數字-1(對於真)。而不是明確地減去一個導致爲真的比較的1,比較的實際結果可以簡單地作爲帶符號整數加到值4中。

“alpha”通道的指標計算與[ 23 ]中實時DXT5壓縮機的計算非常相似然而,通過減法而不是相加選擇最佳匹配採樣點,可進一步優化計算。首先,計算X值從最接近一個採樣點到另一採樣點的一組交叉點。

字節mid =(max  -  min)/(2 * 7);
 
字節ab1 = max  -  mid;
字節ab2 =(6 * max + 1 * min)/ 7  -  mid;
字節ab3 =(5 * max + 2 * min)/ 7  -  mid;
字節ab4 =(4 * max + 3 * min)/ 7  -  mid;
字節ab5 =(3 * max + 4 * min)/ 7  -  mid;
字節ab6 =(2 * max + 5 * min)/ 7  -  mid;
字節ab7 =(1 * max + 6 * min)/ 7  -  mid;

然後可以測試X值大於等於每個交叉點,並且可以從8中減去這些比較的結果(0爲假,1爲真),並使用按位邏輯“和”7進行包裝計算索引。前兩個索引也通過比較(2>索引)的結果進行交換,如下面的代碼所示。

字節索引[16];
for(int i = 0; i <16; i ++){
    字節a =塊[i * 4];
    int b1 =(a> = ab1);
    int b2 =(a> = ab2);
    int b3 =(a> = ab3);
    int b4 =(a> = ab4);
    int b5 =(a> = ab5);
    int b6 =(a> = ab6);
    int b7 =(a> = ab7);
    int index =(8-b1  -  b2  -  b3  -  b4  -  b5  -  b6  -  b7)&7;
    indices [i] = index ^(2> index);
}

實時_Y_X DXT5壓縮機的全面實現可以在附錄A中找到。該實時壓縮機的MMX和SSE2實現分別在附錄B和C中找到。

在可用的地方,3Dc(BC5或LATC)格式導致最佳的質量正切空間法線貼圖壓縮。實時3Dc壓縮器首先像_Y_X DXT5壓縮器一樣計算XY正常空間的邊界框。用於近似X和Y值的兩條線從最小值到該邊界框的最大值。爲了改善均方誤差(MSE),邊界框在任一端插入線上採樣點之間的距離的四分之一。3Dc格式基本上存儲兩個具有相同編碼和8個採樣點的DXT5 Alpha通道。因此,在兩個軸上,邊界框的兩端插入範圍的1/32。與_Y_X DXT5壓縮相同的代碼也用於計算“alpha”通道索引,除了使用兩次。

5. GPU上的實時壓縮

也可以在GPU上執行切線空間法線貼圖的實時壓縮。這是可能的,因爲DX10類圖形硬件上可用的新功能,可以渲染整數紋理和使用按位和算術整數運算。

爲了壓縮法線貼圖,通過在整個目標表面上渲染四邊形,爲4x4紋素的每個塊使用片段程序。該片段程序的結果是一個壓縮的DXT塊,寫入整數紋理的紋素。DXT5和3DC塊都是128位,它等於一個RGBA紋素,每個元件有32位。因此,當將法線貼圖壓縮爲任一格式時,將使用無符號整數RGBA紋理作爲渲染目標。然後通過使用像素緩衝對象將該渲染目標的內容複製到相應的DXT紋理。該過程非常類似於在[ 24 ] 中更詳細描述的用於YCoCg-DXT5壓縮的過程

3DC壓縮紋理通過兩個不同的擴展在OpenGL中公開:GL_EXT_texture_compression_latc [ 25 ]和GL_EXT_texture_compression_rgtc [ 26 ]。前者將X和Y分量映射到亮度和α通道,而後者將X和Y分量分別映射到紅色和綠色,其餘的通道設置爲0。

在這裏描述的實現中,使用LATC格式。這是稍微更方便,因爲它允許共享用於正常重建的相同着色器代碼:

N.xy = 2 * tex2D(image,texcoord).wy  -  1;
Nz = sqrt(飽和(1-Nx * Nx-Ny * Ny));

當使用LATC時,亮度在RGB通道中被複制,所以WY旋轉將亮度和alpha分量映射到X和Y.類似地,當使用_Y_X DXT5時,WY旋轉將綠色和α分量映射到X和Y.

如[使用的相同的代碼24 ]以編碼用於YCoCg中-DXT5壓縮alpha通道,也可用於編碼X和用於壓縮的3Dc Y分量,和用於_Y_X DXT5壓縮的X分量。如第4節所示,_Y_X DXT5壓縮器也可以進行優化,以僅通過僅適配Y分量來計算DXT1塊。然而,如[ 23 ]所述,α空間是一維空間,並且通過α空間的線上的點是等距的,這允許通過劃分來計算每個原始α值的最接近的點。在CPU上,這需要一個相當慢的標量整數除法,因爲沒有可用於整數除法的MMX或SSE2指令。該分割可以被實現爲具有shift的整數乘法。然而,除數不是常數,這意味着需要查找表才能獲得乘數。乘法也增加了動態範圍,限制了通過SIMD指令集可以利用的並行度。在CPU上,通過在最小的可能元素(字節)上使用簡單的操作,而不增加動態範圍,可以很好地利用最大並行度。然而,在GPU上,使用標量浮點數學,分割和/或乘法相對便宜。因此,通過僅應用比例和偏差,可以將X和Y分量映射到各個指標。用於_Y_X DXT5格式的Y分量的索引計算的CG代碼如下:乘法也增加了動態範圍,限制了通過SIMD指令集可以利用的並行度。在CPU上,通過在最小的可能元素(字節)上使用簡單的操作,而不增加動態範圍,可以很好地利用最大並行度。然而,在GPU上,使用標量浮點數學,分割和/或乘法相對便宜。因此,通過僅應用比例和偏差,可以將X和Y分量映射到各個指標。用於_Y_X DXT5格式的Y分量的索引計算的CG代碼如下:乘法也增加了動態範圍,限制了通過SIMD指令集可以利用的並行度。在CPU上,通過在最小的可能元素(字節)上使用簡單的操作,而不增加動態範圍,可以很好地利用最大並行度。然而,在GPU上,使用標量浮點數學,分割和/或乘法相對便宜。因此,通過僅應用比例和偏差,可以將X和Y分量映射到各個指標。用於_Y_X DXT5格式的Y分量的索引計算的CG代碼如下:在CPU上,通過在最小的可能元素(字節)上使用簡單的操作,而不增加動態範圍,可以很好地利用最大並行度。然而,在GPU上,使用標量浮點數學,分割和/或乘法相對便宜。因此,通過僅應用比例和偏差,可以將X和Y分量映射到各個指標。用於_Y_X DXT5格式的Y分量的索引計算的CG代碼如下:在CPU上,通過在最小的可能元素(字節)上使用簡單的操作,而不增加動態範圍,可以很好地利用最大並行度。然而,在GPU上,使用標量浮點數學,分割和/或乘法相對便宜。因此,通過僅應用比例和偏差,可以將X和Y分量映射到各個指標。用於_Y_X DXT5格式的Y分量的索引計算的CG代碼如下:

const int GREEN_RANGE = 3;
 
float bias = maxGreen +(maxGreen  -  minGreen)/(2.0 * GREEN_RANGE);
浮標= 1.0f /(maxGreen  -  minGreen);
 
//計算索引
uint索引= 0;
for(int i = 0; i <16; i ++)
{
    uint index = saturate((bias-block [i] .y)* scale)* GREEN_RANGE;
    index | = index <<(i * 2);
}
 
uint i0 =(indices&0x55555555);
uint i1 =(indices&0xAAAAAAAA)>> 1;
indices =((i0 ^ i1)<< 1)| I1;

對於_Y_X DXT5格式的X分量以及3Dc格式的X和Y分量都可以這樣做:

const int ALPHA_RANGE = 7;
 
float bias = maxAlpha +(maxAlpha  -  minAlpha)/(2.0 * ALPHA_RANGE);
浮標= 1.0f /(maxAlpha  -  minAlpha);
 
uint2 indices = 0;
 
for(int i = 0; i <6; i ++)
{
    uint index = saturate((bias-block [i] .x)* scale)* ALPHA_RANGE;
    indices.x | = index <<(3 * i);
}
 
for(int i = 6; i <16; i ++)
{
    uint index = saturate((bias-block [i] .x)* scale)* ALPHA_RANGE;
    indices.y | = index <<(3 * i  -  18);
}
 
uint2 i0 =(indices >> 0)&0x09249249;
uint2 i1 =(indices >> 1)&0x09249249;
uint2 i2 =(indices >> 2)&0x09249249;
 
i2 ^ = i0&i1;
i1 ^ = i0;
i0 ^ =(i1 | i2);
 
indices.x =(i2.x << 2)| (i1.x << 1)| i0.x;
indices.y =(((i2.y << 2)|(i1.y << 1)| i0.y)<< 2)| (indices.x >> 16);
indices.x << = 16;

附錄D中可以找到實時_Y_X DXT5(DXT5nm)法線貼圖壓縮器和實時3Dc(BC5或LATC)法線貼圖壓縮器的完整Cg 2.0實現。

對CPU與GPU的壓縮

如前面章節所示,可以在CPU和GPU上實現高性能法線貼圖壓縮。壓縮是否最好在CPU或GPU上實現依賴於應用程序。

CPU上的實時壓縮對於在CPU上動態創建的法線圖很有用。對CPU進行壓縮對於以不能用於渲染的格式從磁盤流式傳輸的法線圖進行轉碼也特別有用。例如,法線圖或高度圖可以以JPEG格式存儲在磁盤上,因此不能直接用於呈現。JPEG解壓縮算法的一些部分目前可以在GPU上高效地實現。存儲器可以保存在圖形卡上,通過解壓縮原始數據並將其重新壓縮爲DXT格式,可以提高渲染性能。在CPU上重新壓縮紋理數據的優點是上傳到圖形卡的數據量很小。此外,當在CPU上執行壓縮時,完整的GPU可以用於渲染工作,因爲它不需要執行任何壓縮。對於當今CPU上越來越多的內核的確定趨勢,通常可以輕鬆地使用可用於紋理壓縮的免費內核。

因爲對於代碼轉換,實時壓縮可能不太有用,因爲用於上傳未壓縮紋理數據的帶寬需求增加,並且因爲GPU可能已經被昂貴的渲染工作所約束了。然而,GPU上的實時壓縮對壓縮的渲染目標非常有用。GPU上的壓縮可以用於在渲染到紋理時節省內存。此外,如果來自渲染目標的數據用於進一步渲染,則這樣的壓縮渲染目標可以提高性能。渲染目標被壓縮一次,而渲染過程中可能會多次訪問生成的數據。壓縮數據導致光柵化期間帶寬要求降低,因此可以顯着提高性能。

7.結果

7.1對象空間

對象空間法線貼圖壓縮技術已經用下面所示的對象空間法線圖進行了測試。

對象空間法向圖
商場 觸手 胸部 面對

在未加權的X,Y和Z值上計算了峯值信噪比(PSNR),存儲爲8位無符號整數。

PSNR
 
 圖片

XYZ 
DXT1

重新歸一化
XYZ 
DXT1


XY_Z 
DXT5

重新歸一化
XY_Z 
DXT5
 
 01_arcade    30.90    32.95    34.02    37.23 
 
 02_tentacle    36.68    38.29    41.04    41.62 
 
 03_chest    39.24    40.79    42.22    43.47 
 
 04_face    37.38    38.99    41.03    42.60 

7.2切線空間

已經用下面所示的切線空間法線圖測試了切線空間法線貼圖壓縮技術。

切線空間正常圖
dot1 dot2 dot3 dot4
塊狀 voronoi 烏龜 8. normalmap
金屬 皮膚 十九歲
街機 觸手 胸部 面對

在未加權的X,Y和Z值上計算了峯值信噪比(PSNR),存儲爲8位無符號整數。

PSNR
 
 圖片

XY_ 
DXT1

重新歸一化
XYZ 
DXT1


_YZX 
DXT5

重新歸一化
_YZX 
DXT5


_Y_X 
DXT5



的3Dc
 
 01_dot1    27.61    29.51    32.00    35.16    35.07    40.15 
 
 02_dot2    25.39    26.45    29.55    32.92    32.68    36.70 
 
 03_dot3    21.88    23.05    27.34    30.77    30.02    34.13 
 
 04_dot4    23.18    24.46    29.16    32.81    31.38    35.80 
 
 05_lumpy    30.54    31.13    34.70    37.15    37.73    41.92 
 
 06_voronoi    37.53    38.16    41.72    42.16    43.93    48.23 
 
 07_turtle    36.12    37.06    38.74    39.93    41.22    45.76 
 
 08_normalmap    35.57    36.36    37.78    38.95    40.00    44.49 
 
 09_metal    41.65    41.99    46.37    46.55    49.03    54.10 
 
 10_skin    28.95    29.48    34.68    36.20    36.83    41.37 
 
 11_onetile    29.08    29.82    34.17    35.98    36.76    41.14 
 
 12_barrel    29.93    31.67    33.15    36.79    37.03    40.20 
 
 13_arcade    32.31    33.63    36.86    39.24    39.81    44.61 
 
 14_tentacle    39.03    40.47    40.30    41.39    43.23    47.82 
 
 15_chest    38.92    41.03    41.64    42.29    42.87    46.52 
 
 16_face    38.27    39.58    41.59    42.55    43.71    48.61 

下圖使用3Dc格式顯示正交和立體投影之間的質量差異。立體投影導致更一致的結果,但對於大多數正常地圖,質量明顯較低。

以下圖表僅僅是理論上的興趣,因爲它顯示了在4x4塊中旋轉法線的質量改進,並將旋轉存儲在_Y_X DXT5格式的一個未使用的通道中。該圖表顯示僅用於點採樣的法線貼圖的質量改進,因爲過濾會導致4x4塊與不同旋轉之間紋素樣本的顯着僞像。

7.3實時切線空間

已經使用上面所示的相同的切線空間法線圖測試了實時切線空間法線貼圖壓縮機。在未加權的X,Y和Z值上計算了峯值信噪比(PSNR),存儲爲8位無符號整數。

PSNR
 
 圖片
離線
_Y_X 
DXT5

實時
_Y_X 
DXT5

離線

3Dc

實時

3Dc
 
 01_dot1    35.07    33.36    40.15    37.99 
 
 02_dot2    32.68    31.67    36.70    35.67 
 
 03_dot3    30.02    29.03    34.13    33.22 
 
 04_dot4    31.38    30.49    35.80    34.89 
 
 05_lumpy    37.73    36.63    41.92    40.63 
 
 06_voronoi    43.93    42.99    48.23    46.99 
 
 07_turtle    41.22    40.30    45.76    44.50 
 
 08_normalmap    40.00    38.99    44.49    43.26 
 
 09_metal    49.03    47.60    54.10    52.45 
 
 10_skin    36.83    35.69    41.37    40.20 
 
 11_onetile    36.76    35.67    41.14    39.92 
 
 12_barrel    37.03    35.51    40.20    39.11 
 
 13_arcade    39.81    38.05    44.61    42.18 
 
 14_tentacle    43.23    41.90    47.82    46.31 
 
 15_chest    42.87    41.95    46.52    45.38 
 
 16_face    43.71    42.85    48.61    47.53 

採用英特爾®2.8 GHz雙核至強®(“Paxville”90nm NetBurst微架構)和英特爾®2.9 GHz Core™2 Extreme(“Conroe”65nm Core 2微架構)測試了SIMD優化實時壓縮器的性能)。這些處理器中只有一個核心用於壓縮。由於紋理壓縮是基於塊的,所以壓縮算法可以容易地使用多個線程來利用這些處理器的所有核心。當使用多個內核時,可預期的線速度隨着可用內核的數量而增加。在NVIDIA GeForce 8600 GTS和NVIDIA GeForce 8800 GTX上,還對Cg 2.0實現的性能進行了測試。

下圖顯示了每秒可以壓縮到_Y_X DXT5格式的多個像素數(更高的MP / s =更好)。

下圖顯示了每秒可以壓縮到3Dc格式的更大像素數(更高的MP / s =更好)。

8.結論

現有的顏色紋理壓縮格式也可以用於存儲法線貼圖,但結果各不相同。最新的圖形硬件還實現了專門爲普通地圖壓縮設計的格式。雖然這些格式的解壓縮在渲染過程中在硬件中實時發生,但對這些格式的壓縮可能需要相當長的時間。現有壓縮機設計用於高質量離線壓縮,而不是實時壓縮。然而,以一些質量爲代價,普通地圖也可以在CPU和GPU上實時壓縮,這對於從不同格式轉碼法線貼圖和動態生成的法線貼圖進行壓縮是非常有用的。

參考文獻

1。 皺紋曲面的模擬。
James F. Blinn 
In Proceedings of SIGGRAPH,vol。12,#3,
pp。286-292,1978在線可用:http://portal.acm.org/citation.cfm?id=507101
2。 高效的凸點映射硬件。
Mark Peercy,John Airey,Brian Cabral 
Computer Graphics,vol。31,
pp。303-306,1997 Available online:http://citeseer.ist.psu.edu/peercy97efficient.html
3。 S3紋理壓縮
Pat Brown 
NVIDIA Corporation,2001年11月
在線提供:http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
4。 壓縮紋理資源(Direct3D 9)
Microsoft開發人員網絡
MSDN,2007年11月
在線可用:http://msdn2.microsoft.com/en-us/library/bb204843.aspx
5。 塊壓縮(Direct3D 10)
Microsoft開發人員網絡
MSDN,2007年11月
在線可用:http://msdn2.microsoft.com/en-us/library/bb694531.aspx
6。 使用塊截斷編碼的圖像編碼
E.J. Delp,OR Mitchell 
IEEE Transactions on Communications,vol。27(9),pp。1335-1342,1979年
9月在線可用:http://scholarsmine.umr.edu/post_prints/01094560_09007dcc8030cc78.html
7。 Bump地圖壓縮
Simon Green 
NVIDIA技術報告,2001年10月
在線提供:http://developer.nvidia.com/object/bump_map_compression.html
8。 正常地圖壓縮
ATI Technologies Inc 
ATI,2003年8月
在線提供:http://www.ati.com/developer/NormalMapCompression.pdf
9。 正常地圖壓縮
Jakub Klarowicz 
着色器X2:使用DirectX 9的着色器編程提示和技巧
10。
3DC 白皮書 ATI Technologies Inc 
ATI,2004年4月
在線提供:http://ati.de/products/radeonx800/3DcWhitePaper.pdf
11。 高品質正常地圖壓縮
Jacob Munkberg,Tomas Akenine-Möller,JacobStrömGraphics 
Hardware 2006 
在線可用:http://graphics.cs.lth.se/research/papers/normals2006/
12。 緊幀正常地圖壓縮
Jacob Munkberg,Ola Olsson,JacobStröm,Tomas Akenine-MöllerGraphics 
Hardware 2007 
在線可用:http://graphics.cs.lth.se/research/papers/2007/tightframe/
13。 基於矢量量化的快速有效的正態圖壓縮
T. Yamasaki,
K.Aizawa In Proceedings of ICASSP(2006),vol。2,
pp。2-12在線可用:http://www.ee.columbia.edu/~dpwe/LabROSA/proceeds/icassp/2006/pdfs/0200009.pdf
14。 混合自適應法線貼圖壓縮算法
B. Yang,
Z.Pan In International Conference on Artificial Reality and Telexistence(2006),IEEE Computer Society,
pp。349-354 Available online:http://doi.ieeecomputersociety.org/10.1109 /ICAT.2006.11
15。 基於統一條件的正常地圖壓縮的數學誤差分析
Toshihiko Yamasaki,Kazuya Hayase,Kiyoharu Aizawa 
IEEE International Conference on Image Processing,vol。2,pp。253-6,2005年9月
在線提供:http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=1530039
16。 壓縮正常圖和渲染3D圖像之間的數學PSNR預測模型
Toshihiko Yamasaki,Kazuya Hayase,Kiyoharu Aizawa 
Pacific Rim多媒體會議(PCM2005)LNCS 3768,第584-594頁,韓國濟州島,2005年11月13-16日
可用在線:http://www.springerlink.com/content/f3707080w8553g3l/
17。 實時渲染具有不連續性的正常地圖
Evgueni Parilov,Ilya Rosenberg,Denis 
Zorin CIMS技術報告,TR2005-872,2005年8月
可在線獲取:http://csdocs.cs.nyu.edu/Dienst/UI/2.0/Describe/ ncstrl.nyu_cs%2FTR2005-872t
18。 Mipmapping法線貼圖
M. Toksvig 
圖形工具雜誌10,3,65-71 2005 
在線可用:http://developer.nvidia.com/object/mipmapping_normal_maps.html
19。 頻域正態圖過濾
Charles Han,Bo Sun,Ravi Ramamoorthi,Eitan Grinspun 
SIGGRAPH 2007 
在線提供:http://www.cs.columbia.edu/cg/normalmap/normalmap.pdf
20。 ATI Compressonator Library
Seth Sowerby,Daniel Killebrew 
ATI Technologies Inc,The Compressonator版本1.27.1066,2006年3月
在線提供:http://www.ati.com/developer/compressonator.html
21。 NVIDIA DDS工具
NVIDIA 
NVIDIA DDS工具,2006年4月
在線提供:http://developer.nvidia.com/object/nv_texture_tools.html
22。 NVIDIA紋理工具
NVIDIA 
NVIDIA紋理工具,2007年9月
在線提供:http://developer.nvidia.com/object/texture_tools.html
23。 實時DXT壓縮
J.MP van Waveren 
英特爾軟件網絡,2006年10月
在線提供:http://www.intel.com/cd/ids/developer/asmo-na/eng/324337.htm
24。 實時YCoCg-DXT壓縮
J.MP van Waveren,IgnacioCastañoNVIDIA,
2007年10月
在線提供:http://news.developer.nvidia.com/2007/10/real-time-ycocg.html
25。 GL_EXT_texture_compression_latc
可在線獲得:http://www.opengl.org/registry/specs/EXT/texture_compression_latc.txt
26。 GL_EXT_texture_compression_rgtc
可在線獲得:http://www.opengl.org/registry/specs/EXT/texture_compression_rgtc.txt


 

 

 

 

 









Appendix A

/*
    Real-Time Normal Map Compression (C++)
    Copyright (C) 2008 Id Software, Inc.
    Written by J.M.P. van Waveren
 
    This code is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.
 
    This code is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
*/
 
typedef unsigned char   byte;
typedef unsigned short  word;
typedef unsigned int    dword;
 
#define INSET_COLOR_SHIFT       4       // inset color channel
#define INSET_ALPHA_SHIFT       5       // inset alpha channel
 
#define C565_5_MASK             0xF8    // 0xFF minus last three bits
#define C565_6_MASK             0xFC    // 0xFF minus last two bits
 
byte *globalOutData;
 
void EmitByte( byte b ) {
    globalOutData[0] = b;
    globalOutData += 1;
}
 
void EmitWord( word s ) {
    globalOutData[0] = ( s >>  0 ) & 255;
    globalOutData[1] = ( s >>  8 ) & 255;
    globalOutData += 2;
}
 
void EmitDoubleWord( dword i ) {
    globalOutData[0] = ( i >>  0 ) & 255;
    globalOutData[1] = ( i >>  8 ) & 255;
    globalOutData[2] = ( i >> 16 ) & 255;
    globalOutData[3] = ( i >> 24 ) & 255;
    globalOutData += 4;
}
 
word NormalYTo565( byte y ) {
    return ( ( y >> 2 ) << 5 );
}
 
void ExtractBlock( const byte *inPtr, const int width, byte *block ) {
    for ( int j = 0; j < 4; j++ ) {
        memcpy( &block[j*4*4], inPtr, 4*4 );
        inPtr += width * 4;
    }
}
 
void GetMinMaxNormalsBBox( const byte *block, byte *minNormal, byte *maxNormal ) {
 
    minNormal[0] = minNormal[1] = 255;
    maxNormal[0] = maxNormal[1] = 0;
 
    for ( int i = 0; i < 16; i++ ) {
        if ( block[i*4+0] < minNormal[0] ) {
            minNormal[0] = block[i*4+0];
        }
        if ( block[i*4+1] < minNormal[1] ) {
            minNormal[1] = block[i*4+1];
        }
        if ( block[i*4+0] > maxNormal[0] ) {
            maxNormal[0] = block[i*4+0];
        }
        if ( block[i*4+1] > maxNormal[1] ) {
            maxNormal[1] = block[i*4+1];
        }
    }
}
 
void InsetNormalsBBoxDXT5( byte *minNormal, byte *maxNormal ) {
    int inset[4];
    int mini[4];
    int maxi[4];
 
    inset[0] = ( maxNormal[0] - minNormal[0] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
    inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1);
 
    mini[0] = ( ( minNormal[0] << INSET_ALPHA_SHIFT ) + inset[0] ) >> INSET_ALPHA_SHIFT;
    mini[1] = ( ( minNormal[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT;
 
    maxi[0] = ( ( maxNormal[0] << INSET_ALPHA_SHIFT ) - inset[0] ) >> INSET_ALPHA_SHIFT;
    maxi[1] = ( ( maxNormal[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT;
 
    mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0;
    mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
 
    maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255;
    maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
 
    minNormal[0] = mini[0];
    minNormal[1] = ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 );
 
    maxNormal[0] = maxi[0];
    maxNormal[1] = ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 );
}
 
void InsetNormalsBBox3Dc( byte *minNormal, byte *maxNormal ) {
    int inset[4];
    int mini[4];
    int maxi[4];
 
    inset[0] = ( maxNormal[0] - minNormal[0] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
    inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1);
 
    mini[0] = ( ( minNormal[0] << INSET_ALPHA_SHIFT ) + inset[0] ) >> INSET_ALPHA_SHIFT;
    mini[1] = ( ( minNormal[1] << INSET_ALPHA_SHIFT ) + inset[1] ) >> INSET_ALPHA_SHIFT;
 
    maxi[0] = ( ( maxNormal[0] << INSET_ALPHA_SHIFT ) - inset[0] ) >> INSET_ALPHA_SHIFT;
    maxi[1] = ( ( maxNormal[1] << INSET_ALPHA_SHIFT ) - inset[1] ) >> INSET_ALPHA_SHIFT;
 
    mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0;
    mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0;
 
    maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255;
    maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255;
 
    minNormal[0] = mini[0];
    minNormal[1] = mini[1];
 
    maxNormal[0] = maxi[0];
    maxNormal[1] = maxi[1];
}
 
void EmitAlphaIndices( const byte *block, const int offset, const byte minAlpha, const byte maxAlpha ) {
    byte mid = ( maxAlpha - minAlpha ) / ( 2 * 7 );
 
    byte ab1 = maxAlpha - mid;
    byte ab2 = ( 6 * maxAlpha + 1 * minAlpha ) / 7 - mid;
    byte ab3 = ( 5 * maxAlpha + 2 * minAlpha ) / 7 - mid;
    byte ab4 = ( 4 * maxAlpha + 3 * minAlpha ) / 7 - mid;
    byte ab5 = ( 3 * maxAlpha + 4 * minAlpha ) / 7 - mid;
    byte ab6 = ( 2 * maxAlpha + 5 * minAlpha ) / 7 - mid;
    byte ab7 = ( 1 * maxAlpha + 6 * minAlpha ) / 7 - mid;
    
    block += offset;
 
    byte indices[16];
    for ( int i = 0; i < 16; i++ ) {
        byte a = block[i*4];
        int b1 = ( a >= ab1 );
        int b2 = ( a >= ab2 );
        int b3 = ( a >= ab3 );
        int b4 = ( a >= ab4 );
        int b5 = ( a >= ab5 );
        int b6 = ( a >= ab6 );
        int b7 = ( a >= ab7 );
        int index = ( 8 - b1 - b2 - b3 - b4 - b5 - b6 - b7 ) & 7;
        indices[i] = index ^ ( 2 > index );
    }
 
    EmitByte( (indices[ 0] >> 0) | (indices[ 1] << 3) | (indices[ 2] << 6) );
    EmitByte( (indices[ 2] >> 2) | (indices[ 3] << 1) | (indices[ 4] << 4) | (indices[ 5] << 7) );
    EmitByte( (indices[ 5] >> 1) | (indices[ 6] << 2) | (indices[ 7] << 5) );
 
    EmitByte( (indices[ 8] >> 0) | (indices[ 9] << 3) | (indices[10] << 6) );
    EmitByte( (indices[10] >> 2) | (indices[11] << 1) | (indices[12] << 4) | (indices[13] << 7) );
    EmitByte( (indices[13] >> 1) | (indices[14] << 2) | (indices[15] << 5) );
}
 
void EmitGreenIndices( const byte *block, const int offset, const byte minGreen, const byte maxGreen ) {
    byte mid = ( maxGreen - minGreen ) / ( 2 * 3 );
 
    byte gb1 = maxGreen - mid;
    byte gb2 = ( 2 * maxGreen + 1 * minGreen ) / 3 - mid;
    byte gb3 = ( 1 * maxGreen + 2 * minGreen ) / 3 - mid;
 
    block += offset;
 
    unsigned int result = 0;
    for ( int i = 15; i >= 0; i-- ) {
        result <<= 2;
        byte g = block[i*4];
        int b1 = ( g >= gb1 );
        int b2 = ( g >= gb2 );
        int b3 = ( g >= gb3 );
        int index = ( 4 - b1 - b2 - b3 ) & 3;
        index ^= ( 2 > index );
        result |= index;
    }
 
    EmitUInt( result );
}
 
void CompressNormalMapDXT5( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    byte block[64];
    byte normalMin[4];
    byte normalMax[4];
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox( block, normalMin, normalMax );
            InsetNormalsBBoxDXT5( normalMin, normalMax );
 
            // Write out Nx into alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices( block, 0, normalMin[0], normalMax[0] );
 
            // Write out Ny into green channel.
            EmitUShort( NormalYTo565( normalMax[1] ) );
            EmitUShort( NormalYTo565( normalMin[1] ) );
            EmitGreenIndices( block, 1, normalMin[1], normalMax[1] );
        }
    }
  
    outputBytes = outData - outBuf;
}
 
void CompressNormalMap3Dc( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    byte block[64];
    byte normalMin[4];
    byte normalMax[4];
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox( block, normalMin, normalMax );
            InsetNormalsBBox3Dc( normalMin, normalMax );
 
            // Write out Nx as an alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices( block, 0, normalMin[0], normalMax[0] );
 
            // Write out Ny as an alpha channel.
            EmitByte( normalMax[1] );
            EmitByte( normalMin[1] );
            EmitAlphaIndices( block, 1, normalMin[1], normalMax[1] );
        }
    }
 
    outputBytes = outData - outBuf;
}

Appendix B

/*
    Real-Time Normal Map Compression (MMX)
    Copyright (C) 2008 Id Software, Inc.
    Written by J.M.P. van Waveren
 
    This code is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.
 
    This code is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
*/
 
#define ALIGN16( x )                __declspec(align(16)) x
#define R_SHUFFLE_D( x, y, z, w )   (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 ))
 
ALIGN16( static dword SIMD_MMX_dword_byte_mask[2] ) = { 0x000000FF, 0x000000FF };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask0[2] ) = { 7<<0, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask1[2] ) = { 7<<3, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask2[2] ) = { 7<<6, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask3[2] ) = { 7<<9, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask4[2] ) = { 7<<12, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask5[2] ) = { 7<<15, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask6[2] ) = { 7<<18, 0 };
ALIGN16( static dword SIMD_MMX_dword_alpha_bit_mask7[2] ) = { 7<<21, 0 };
ALIGN16( static word SIMD_MMX_word_0[4] ) = { 0x0000, 0x0000, 0x0000, 0x0000 };
ALIGN16( static word SIMD_MMX_word_div_by_3[4] ) = { (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1 };
ALIGN16( static word SIMD_MMX_word_div_by_7[4] ) = { (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1 };
ALIGN16( static word SIMD_MMX_word_div_by_14[4] ) = { (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1 };
ALIGN16( static word SIMD_MMX_word_scale654[4] ) = { 6, 5, 4, 0 };
ALIGN16( static word SIMD_MMX_word_scale123[4] ) = { 1, 2, 3, 0 };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5Round[4] ) = { ((1<<(INSET_ALPHA_SHIFT-1))-1), ((1<<(INSET_COLOR_SHIFT-1))-1), 0, 0 };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5Mask[4] ) = { 0xFFFF, 0xFFFF, 0x0000, 0x0000 };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5ShiftUp[4] ) = { 1 << INSET_ALPHA_SHIFT, 1 << INSET_COLOR_SHIFT, 1, 1 };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5ShiftDown[4] ) = { 1 << ( 16 - INSET_ALPHA_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 0, 0 };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5QuantMask[4] ) = { 0xFF, C565_6_MASK, 0xFF, 0xFF };
ALIGN16( static word SIMD_MMX_word_insetNormalDXT5Rep[4] ) = { 0, 1 << ( 16 - 6 ), 0, 0 };
ALIGN16( static word SIMD_MMX_word_insetNormal3DcRound[4] ) = { ((1<<(INSET_ALPHA_SHIFT-1))-1), ((1<<(INSET_ALPHA_SHIFT-1))-1), 0, 0 };
ALIGN16( static word SIMD_MMX_word_insetNormal3DcMask[4] ) = { 0xFFFF, 0xFFFF, 0x0000, 0x0000 };
ALIGN16( static word SIMD_MMX_word_insetNormal3DcShiftUp[4] ) = { 1 << INSET_ALPHA_SHIFT, 1 << INSET_ALPHA_SHIFT, 1, 1 };
ALIGN16( static word SIMD_MMX_word_insetNormal3DcShiftDown[4] ) = { 1 << ( 16 - INSET_ALPHA_SHIFT ), 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0 };
ALIGN16( static byte SIMD_MMX_byte_0[8] ) = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
ALIGN16( static byte SIMD_MMX_byte_1[8] ) = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
ALIGN16( static byte SIMD_MMX_byte_2[8] ) = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 };
ALIGN16( static byte SIMD_MMX_byte_7[8] ) = { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 };
ALIGN16( static byte SIMD_MMX_byte_8[8] ) = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 };
ALIGN16( static byte SIMD_MMX_byte_not[8] ) = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
 
void ExtractBlock_MMX( const byte *inPtr, int width, byte *block ) {
    __asm {
        mov         esi, inPtr
        mov         edi, block
        mov         eax, width
        shl         eax, 2
        movq        mm0, qword ptr [esi+0]
        movq        qword ptr [edi+ 0], mm0
        movq        mm1, qword ptr [esi+8]
        movq        qword ptr [edi+ 8], mm1
        movq        mm2, qword ptr [esi+eax+0]
        movq        qword ptr [edi+16], mm2
        movq        mm3, qword ptr [esi+eax+8]
        movq        qword ptr [edi+24], mm3
        movq        mm4, qword ptr [esi+eax*2+0]
        movq        qword ptr [edi+32], mm4
        movq        mm5, qword ptr [esi+eax*2+8]
        add         esi, eax
        movq        qword ptr [edi+40], mm5
        movq        mm6, qword ptr [esi+eax*2+0]
        movq        qword ptr [edi+48], mm6
        movq        mm7, qword ptr [esi+eax*2+8]
        movq        qword ptr [edi+56], mm7
        emms
    }
}
 
void GetMinMaxNormalsBBox_MMX( const byte *block, byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         eax, block
        mov         esi, minNormal
        mov         edi, maxNormal
        pshufw      mm0, qword ptr [eax+ 0], R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm1, qword ptr [eax+ 0], R_SHUFFLE_D( 0, 1, 2, 3 )
        pminub      mm0, qword ptr [eax+ 8]
        pmaxub      mm1, qword ptr [eax+ 8]
        pminub      mm0, qword ptr [eax+16]
        pmaxub      mm1, qword ptr [eax+16]
        pminub      mm0, qword ptr [eax+24]
        pmaxub      mm1, qword ptr [eax+24]
        pminub      mm0, qword ptr [eax+32]
        pmaxub      mm1, qword ptr [eax+32]
        pminub      mm0, qword ptr [eax+40]
        pmaxub      mm1, qword ptr [eax+40]
        pminub      mm0, qword ptr [eax+48]
        pmaxub      mm1, qword ptr [eax+48]
        pminub      mm0, qword ptr [eax+56]
        pmaxub      mm1, qword ptr [eax+56]
        pshufw      mm6, mm0, R_SHUFFLE_D( 2, 3, 2, 3 )
        pshufw      mm7, mm1, R_SHUFFLE_D( 2, 3, 2, 3 )
        pminub      mm0, mm6
        pmaxub      mm1, mm7
        movd        dword ptr [esi], mm0
        movd        dword ptr [edi], mm1
        emms
    }
}
 
void InsetNormalsBBoxDXT5_MMX( byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         esi, minNormal
        mov         edi, maxNormal
        movd        mm0, dword ptr [esi]
        movd        mm1, dword ptr [edi]
        punpcklbw   mm0, SIMD_MMX_byte_0
        punpcklbw   mm1, SIMD_MMX_byte_0
        movq        mm2, mm1
        psubw       mm2, mm0
        psubw       mm2, SIMD_MMX_word_insetNormalDXT5Round
        pand        mm2, SIMD_MMX_word_insetNormalDXT5Mask
        pmullw      mm0, SIMD_MMX_word_insetNormalDXT5ShiftUp
        pmullw      mm1, SIMD_MMX_word_insetNormalDXT5ShiftUp
        paddw       mm0, mm2
        psubw       mm1, mm2
        pmulhw      mm0, SIMD_MMX_word_insetNormalDXT5ShiftDown
        pmulhw      mm1, SIMD_MMX_word_insetNormalDXT5ShiftDown
        pmaxsw      mm0, SIMD_MMX_word_0
        pmaxsw      mm1, SIMD_MMX_word_0
        pand        mm0, SIMD_MMX_word_insetNormalDXT5QuantMask
        pand        mm1, SIMD_MMX_word_insetNormalDXT5QuantMask
        movq        mm2, mm0
        movq        mm3, mm1
        pmulhw      mm2, SIMD_MMX_word_insetNormalDXT5Rep
        pmulhw      mm3, SIMD_MMX_word_insetNormalDXT5Rep
        por         mm0, mm2
        por         mm1, mm3
        packuswb    mm0, mm0
        packuswb    mm1, mm1
        movd        dword ptr [esi], mm0
        movd        dword ptr [edi], mm1
        emms
    }
}
 
void InsetNormalsBBox3Dc_MMX( byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         esi, minNormal
        mov         edi, maxNormal
        movd        mm0, dword ptr [esi]
        movd        mm1, dword ptr [edi]
        punpcklbw   mm0, SIMD_MMX_byte_0
        punpcklbw   mm1, SIMD_MMX_byte_0
        movq        mm2, mm1
        psubw       mm2, mm0
        psubw       mm2, SIMD_MMX_word_insetNormal3DcRound
        pand        mm2, SIMD_MMX_word_insetNormal3DcMask
        pmullw      mm0, SIMD_MMX_word_insetNormal3DcShiftUp
        pmullw      mm1, SIMD_MMX_word_insetNormal3DcShiftUp
        paddw       mm0, mm2
        psubw       mm1, mm2
        pmulhw      mm0, SIMD_MMX_word_insetNormal3DcShiftDown
        pmulhw      mm1, SIMD_MMX_word_insetNormal3DcShiftDown
        pmaxsw      mm0, SIMD_MMX_word_0
        pmaxsw      mm1, SIMD_MMX_word_0
        packuswb    mm0, mm0
        packuswb    mm1, mm1
        movd        dword ptr [esi], mm0
        movd        dword ptr [edi], mm1
        emms
    }
}
 
void EmitAlphaIndices_MMX( const byte *block, const int channelBitOffset, const int minAlpha, const int maxAlpha ) {
    ALIGN16( byte alphaBlock[16] );
    ALIGN16( byte ab1[8] );
    ALIGN16( byte ab2[8] );
    ALIGN16( byte ab3[8] );
    ALIGN16( byte ab4[8] );
    ALIGN16( byte ab5[8] );
    ALIGN16( byte ab6[8] );
    ALIGN16( byte ab7[8] );
 
    __asm {
        movd        mm7, channelBitOffset
 
        mov         esi, block
        movq        mm0, qword ptr [esi+ 0]
        movq        mm5, qword ptr [esi+ 8]
        movq        mm6, qword ptr [esi+16]
        movq        mm4, qword ptr [esi+24]
 
        psrld       mm0, mm7
        psrld       mm5, mm7
        psrld       mm6, mm7
        psrld       mm4, mm7
 
        pand        mm0, SIMD_MMX_dword_byte_mask
        pand        mm5, SIMD_MMX_dword_byte_mask
        pand        mm6, SIMD_MMX_dword_byte_mask
        pand        mm4, SIMD_MMX_dword_byte_mask
 
        packuswb    mm0, mm5
        packuswb    mm6, mm4
 
        packuswb    mm0, mm6
        movq        alphaBlock+0, mm0
 
        movq        mm0, qword ptr [esi+32]
        movq        mm5, qword ptr [esi+40]
        movq        mm6, qword ptr [esi+48]
        movq        mm4, qword ptr [esi+56]
 
        psrld       mm0, mm7
        psrld       mm5, mm7
        psrld       mm6, mm7
        psrld       mm4, mm7
 
        pand        mm0, SIMD_MMX_dword_byte_mask
        pand        mm5, SIMD_MMX_dword_byte_mask
        pand        mm6, SIMD_MMX_dword_byte_mask
        pand        mm4, SIMD_MMX_dword_byte_mask
 
        packuswb    mm0, mm5
        packuswb    mm6, mm4
 
        packuswb    mm0, mm6
        movq        alphaBlock+8, mm0
 
        movd        mm0, maxAlpha
        pshufw      mm0, mm0, R_SHUFFLE_D( 0, 0, 0, 0 )
        movq        mm1, mm0
 
        movd        mm2, minAlpha
        pshufw      mm2, mm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        movq        mm3, mm2
 
        movq        mm4, mm0
        psubw       mm4, mm2
        pmulhw      mm4, SIMD_MMX_word_div_by_14
 
        movq        mm5, mm0
        psubw       mm5, mm4
        packuswb    mm5, mm5
        movq        ab1, mm5
 
        pmullw      mm0, SIMD_MMX_word_scale654
        pmullw      mm1, SIMD_MMX_word_scale123
        pmullw      mm2, SIMD_MMX_word_scale123
        pmullw      mm3, SIMD_MMX_word_scale654
        paddw       mm0, mm2
        paddw       mm1, mm3
        pmulhw      mm0, SIMD_MMX_word_div_by_7
        pmulhw      mm1, SIMD_MMX_word_div_by_7
        psubw       mm0, mm4
        psubw       mm1, mm4
 
        pshufw      mm2, mm0, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufw      mm3, mm0, R_SHUFFLE_D( 1, 1, 1, 1 )
        pshufw      mm4, mm0, R_SHUFFLE_D( 2, 2, 2, 2 )
        packuswb    mm2, mm2
        packuswb    mm3, mm3
        packuswb    mm4, mm4
        movq        ab2, mm2
        movq        ab3, mm3
        movq        ab4, mm4
 
        pshufw      mm2, mm1, R_SHUFFLE_D( 2, 2, 2, 2 )
        pshufw      mm3, mm1, R_SHUFFLE_D( 1, 1, 1, 1 )
        pshufw      mm4, mm1, R_SHUFFLE_D( 0, 0, 0, 0 )
        packuswb    mm2, mm2
        packuswb    mm3, mm3
        packuswb    mm4, mm4
        movq        ab5, mm2
        movq        ab6, mm3
        movq        ab7, mm4
 
        pshufw      mm0, alphaBlock+0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm1, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm2, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm3, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pmaxub      mm1, ab1
        pmaxub      mm2, ab2
        pmaxub      mm3, ab3
        pmaxub      mm4, ab4
        pmaxub      mm5, ab5
        pmaxub      mm6, ab6
        pmaxub      mm7, ab7
        pcmpeqb     mm1, mm0
        pcmpeqb     mm2, mm0
        pcmpeqb     mm3, mm0
        pcmpeqb     mm4, mm0
        pcmpeqb     mm5, mm0
        pcmpeqb     mm6, mm0
        pcmpeqb     mm7, mm0
        pshufw      mm0, SIMD_MMX_byte_8, R_SHUFFLE_D( 0, 1, 2, 3 )
        paddsb      mm0, mm1
        paddsb      mm2, mm3
        paddsb      mm4, mm5
        paddsb      mm6, mm7
        paddsb      mm0, mm2
        paddsb      mm4, mm6
        paddsb      mm0, mm4
        pand        mm0, SIMD_MMX_byte_7
        pshufw      mm1, SIMD_MMX_byte_2, R_SHUFFLE_D( 0, 1, 2, 3 )
        pcmpgtb     mm1, mm0
        pand        mm1, SIMD_MMX_byte_1
        pxor        mm0, mm1
        pshufw      mm1, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm2, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm3, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm1,  8- 3
        psrlq       mm2, 16- 6
        psrlq       mm3, 24- 9
        psrlq       mm4, 32-12
        psrlq       mm5, 40-15
        psrlq       mm6, 48-18
        psrlq       mm7, 56-21
        pand        mm0, SIMD_MMX_dword_alpha_bit_mask0
        pand        mm1, SIMD_MMX_dword_alpha_bit_mask1
        pand        mm2, SIMD_MMX_dword_alpha_bit_mask2
        pand        mm3, SIMD_MMX_dword_alpha_bit_mask3
        pand        mm4, SIMD_MMX_dword_alpha_bit_mask4
        pand        mm5, SIMD_MMX_dword_alpha_bit_mask5
        pand        mm6, SIMD_MMX_dword_alpha_bit_mask6
        pand        mm7, SIMD_MMX_dword_alpha_bit_mask7
        por         mm0, mm1
        por         mm2, mm3
        por         mm4, mm5
        por         mm6, mm7
        por         mm0, mm2
        por         mm4, mm6
        por         mm0, mm4
        mov         esi, globalOutData
        movd        [esi+0], mm0
 
        pshufw      mm0, alphaBlock+8, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm1, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm2, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm3, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pmaxub      mm1, ab1
        pmaxub      mm2, ab2
        pmaxub      mm3, ab3
        pmaxub      mm4, ab4
        pmaxub      mm5, ab5
        pmaxub      mm6, ab6
        pmaxub      mm7, ab7
        pcmpeqb     mm1, mm0
        pcmpeqb     mm2, mm0
        pcmpeqb     mm3, mm0
        pcmpeqb     mm4, mm0
        pcmpeqb     mm5, mm0
        pcmpeqb     mm6, mm0
        pcmpeqb     mm7, mm0
        pshufw      mm0, SIMD_MMX_byte_8, R_SHUFFLE_D( 0, 1, 2, 3 )
        paddsb      mm0, mm1
        paddsb      mm2, mm3
        paddsb      mm4, mm5
        paddsb      mm6, mm7
        paddsb      mm0, mm2
        paddsb      mm4, mm6
        paddsb      mm0, mm4
        pand        mm0, SIMD_MMX_byte_7
        pshufw      mm1, SIMD_MMX_byte_2, R_SHUFFLE_D( 0, 1, 2, 3 )
        pcmpgtb     mm1, mm0
        pand        mm1, SIMD_MMX_byte_1
        pxor        mm0, mm1
        pshufw      mm1, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm2, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm3, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm1,  8- 3
        psrlq       mm2, 16- 6
        psrlq       mm3, 24- 9
        psrlq       mm4, 32-12
        psrlq       mm5, 40-15
        psrlq       mm6, 48-18
        psrlq       mm7, 56-21
        pand        mm0, SIMD_MMX_dword_alpha_bit_mask0
        pand        mm1, SIMD_MMX_dword_alpha_bit_mask1
        pand        mm2, SIMD_MMX_dword_alpha_bit_mask2
        pand        mm3, SIMD_MMX_dword_alpha_bit_mask3
        pand        mm4, SIMD_MMX_dword_alpha_bit_mask4
        pand        mm5, SIMD_MMX_dword_alpha_bit_mask5
        pand        mm6, SIMD_MMX_dword_alpha_bit_mask6
        pand        mm7, SIMD_MMX_dword_alpha_bit_mask7
        por         mm0, mm1
        por         mm2, mm3
        por         mm4, mm5
        por         mm6, mm7
        por         mm0, mm2
        por         mm4, mm6
        por         mm0, mm4
        movd        dword ptr [esi+3], mm0
 
        emms
    }
 
    globalOutData += 6;
}
 
void EmitGreenIndices_MMX( const byte *block, const int channelBitOffset, const int minGreen, const int maxGreen ) {
    ALIGN16( byte greenBlock[16] );
 
    __asm {
        movd        mm7, channelBitOffset
 
        mov         esi, block
        movq        mm0, qword ptr [esi+ 0]
        movq        mm5, qword ptr [esi+ 8]
        movq        mm6, qword ptr [esi+16]
        movq        mm4, qword ptr [esi+24]
 
        psrld       mm0, mm7
        psrld       mm5, mm7
        psrld       mm6, mm7
        psrld       mm4, mm7
 
        pand        mm0, SIMD_MMX_dword_byte_mask
        pand        mm5, SIMD_MMX_dword_byte_mask
        pand        mm6, SIMD_MMX_dword_byte_mask
        pand        mm4, SIMD_MMX_dword_byte_mask
 
        packuswb    mm0, mm5
        packuswb    mm6, mm4
 
        packuswb    mm0, mm6
        movq        greenBlock+0, mm0
 
        movq        mm0, qword ptr [esi+32]
        movq        mm5, qword ptr [esi+40]
        movq        mm6, qword ptr [esi+48]
        movq        mm4, qword ptr [esi+56]
 
        psrld       mm0, mm7
        psrld       mm5, mm7
        psrld       mm6, mm7
        psrld       mm4, mm7
 
        pand        mm0, SIMD_MMX_dword_byte_mask
        pand        mm5, SIMD_MMX_dword_byte_mask
        pand        mm6, SIMD_MMX_dword_byte_mask
        pand        mm4, SIMD_MMX_dword_byte_mask
 
        packuswb    mm0, mm5
        packuswb    mm6, mm4
 
        packuswb    mm0, mm6
        movq        greenBlock+8, mm0
 
        movd        mm2, maxGreen
        pshufw      mm2, mm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        movq        mm1, mm2
 
        movd        mm3, minGreen
        pshufw      mm3, mm3, R_SHUFFLE_D( 0, 0, 0, 0 )
 
        movq        mm4, mm2
        psubw       mm4, mm3
        pmulhw      mm4, SIMD_MMX_word_div_by_6
 
        psllw       mm2, 1
        paddw       mm2, mm3
        pmulhw      mm2, SIMD_MMX_word_div_by_3
        psubw       mm2, mm4
        packuswb    mm2, mm2                        // gb2
 
        psllw       mm3, 1
        paddw       mm3, mm1
        pmulhw      mm3, SIMD_MMX_word_div_by_3
        psubw       mm3, mm4
        packuswb    mm3, mm3                        // gb3
 
        psubw       mm1, mm4
        packuswb    mm1, mm1                        // gb1
 
        pshufw      mm0, greenBlock+0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pmaxub      mm5, mm1
        pmaxub      mm6, mm2
        pmaxub      mm7, mm3
        pcmpeqb     mm5, mm0
        pcmpeqb     mm6, mm0
        pcmpeqb     mm7, mm0
        pshufw      mm0, SIMD_MMX_byte_4, R_SHUFFLE_D( 0, 1, 2, 3 )
        paddsb      mm0, mm5
        paddsb      mm6, mm7
        paddsb      mm0, mm6
        pand        mm0, SIMD_MMX_byte_3
        pshufw      mm4, SIMD_MMX_byte_2, R_SHUFFLE_D( 0, 1, 2, 3 )
        pcmpgtb     mm4, mm0
        pand        mm4, SIMD_MMX_byte_1
        pxor        mm0, mm4
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm4,  8- 2
        psrlq       mm5, 16- 4
        psrlq       mm6, 24- 6
        psrlq       mm7, 32- 8
        pand        mm4, SIMD_MMX_dword_color_bit_mask1
        pand        mm5, SIMD_MMX_dword_color_bit_mask2
        pand        mm6, SIMD_MMX_dword_color_bit_mask3
        pand        mm7, SIMD_MMX_dword_color_bit_mask4
        por         mm5, mm4
        por         mm7, mm6
        por         mm7, mm5
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm4, 40-10
        psrlq       mm5, 48-12
        psrlq       mm6, 56-14
        pand        mm0, SIMD_MMX_dword_color_bit_mask0
        pand        mm4, SIMD_MMX_dword_color_bit_mask5
        pand        mm5, SIMD_MMX_dword_color_bit_mask6
        pand        mm6, SIMD_MMX_dword_color_bit_mask7
        por         mm4, mm5
        por         mm0, mm6
        por         mm7, mm4
        por         mm7, mm0
        mov         esi, gobalOutPtr
        movd        [esi+0], mm7
 
        pshufw      mm0, greenBlock+8, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pmaxub      mm5, mm1
        pmaxub      mm6, mm2
        pmaxub      mm7, mm3
        pcmpeqb     mm5, mm0
        pcmpeqb     mm6, mm0
        pcmpeqb     mm7, mm0
        pshufw      mm0, SIMD_MMX_byte_4, R_SHUFFLE_D( 0, 1, 2, 3 )
        paddsb      mm0, mm5
        paddsb      mm6, mm7
        paddsb      mm0, mm6
        pand        mm0, SIMD_MMX_byte_3
        pshufw      mm4, SIMD_MMX_byte_2, R_SHUFFLE_D( 0, 1, 2, 3 )
        pcmpgtb     mm4, mm0
        pand        mm4, SIMD_MMX_byte_1
        pxor        mm0, mm4
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm7, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm4,  8- 2
        psrlq       mm5, 16- 4
        psrlq       mm6, 24- 6
        psrlq       mm7, 32- 8
        pand        mm4, SIMD_MMX_dword_color_bit_mask1
        pand        mm5, SIMD_MMX_dword_color_bit_mask2
        pand        mm6, SIMD_MMX_dword_color_bit_mask3
        pand        mm7, SIMD_MMX_dword_color_bit_mask4
        por         mm5, mm4
        por         mm7, mm6
        por         mm7, mm5
        pshufw      mm4, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm5, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        pshufw      mm6, mm0, R_SHUFFLE_D( 0, 1, 2, 3 )
        psrlq       mm4, 40-10
        psrlq       mm5, 48-12
        psrlq       mm6, 56-14
        pand        mm0, SIMD_MMX_dword_color_bit_mask0
        pand        mm4, SIMD_MMX_dword_color_bit_mask5
        pand        mm5, SIMD_MMX_dword_color_bit_mask6
        pand        mm6, SIMD_MMX_dword_color_bit_mask7
        por         mm4, mm5
        por         mm0, mm6
        por         mm7, mm4
        por         mm7, mm0
        movd        [esi+2], mm7
        emms
    }
 
    globalOutData += 4;
}
 
void CompressNormalMapDXT5_MMX( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    ALIGN16( byte block[64] );
    ALIGN16( byte normalMin[4] );
    ALIGN16( byte normalMax[4] );
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock_MMX( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox_MMX( block, normalMin, normalMax );
            InsetNormalsBBoxDXT5_MMX( normalMin, normalMax );
 
            // Write out Nx into alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices_MMX( block, 0*8, normalMin[0], normalMax[0] );
 
            // Write out Ny into green channel.
            EmitUShort( NormalYTo565( normalMax[1] ) );
            EmitUShort( NormalYTo565( normalMin[1] ) );
            EmitGreenIndices_MMX( block, 1*8, normalMin[1], normalMax[1] );
        }
    }
 
    outputBytes = outData - outBuf;
}
 
void CompressNormalMap3Dc_MMX( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    ALIGN16( byte block[64] );
    ALIGN16( byte normalMin[4] );
    ALIGN16( byte normalMax[4] );
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock_MMX( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox_MMX( block, normalMin, normalMax );
            InsetNormalsBBox3Dc_MMX( normalMin, normalMax );
 
            // Write out Nx as an alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices_MMX( block, 0*8, normalMin[0], normalMax[0] );
 
            // Write out Ny as an alpha channel.
            EmitByte( normalMax[1] );
            EmitByte( normalMin[1] );
            EmitAlphaIndices_MMX( block, 1*8, normalMin[1], normalMax[1] );
        }
    }
 
    outputBytes = outData - outBuf;
}

Appendix C

/*
    Real-Time Normal Map Compression (SSE2)
    Copyright (C) 2008 Id Software, Inc.
    Written by J.M.P. van Waveren
 
    This code is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.
 
    This code is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
*/
 
#define ALIGN16( x )                __declspec(align(16)) x
#define R_SHUFFLE_D( x, y, z, w )   (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 ))
 
ALIGN16( static dword SIMD_SSE2_dword_byte_mask[4] ) = { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask0[4] ) = { 7<<0, 0, 7<<0, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask1[4] ) = { 7<<3, 0, 7<<3, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask2[4] ) = { 7<<6, 0, 7<<6, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask3[4] ) = { 7<<9, 0, 7<<9, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask4[4] ) = { 7<<12, 0, 7<<12, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask5[4] ) = { 7<<15, 0, 7<<15, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask6[4] ) = { 7<<18, 0, 7<<18, 0 };
ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask7[4] ) = { 7<<21, 0, 7<<21, 0 };
ALIGN16( static word SIMD_SSE2_word_0[8] ) = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
ALIGN16( static word SIMD_SSE2_word_div_by_3[8] ) = { (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1 };
ALIGN16( static word SIMD_SSE2_word_div_by_7[8] ) = { (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1, (1<<16)/7+1 };
ALIGN16( static word SIMD_SSE2_word_div_by_14[8] ) = { (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1 };
ALIGN16( static word SIMD_SSE2_word_scale66554400[8] ) = { 6, 6, 5, 5, 4, 4, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_scale11223300[8] ) = { 1, 1, 2, 2, 3, 3, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Round[8] ) = { ((1<<(INSET_ALPHA_SHIFT-1))-1), ((1<<(INSET_COLOR_SHIFT-1))-1), 0, 0, 0, 0, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Mask[8] ) = { 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5ShiftUp[8] ) = { 1 << INSET_ALPHA_SHIFT, 1 << INSET_COLOR_SHIFT, 1, 1, 1, 1, 1, 1 };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5ShiftDown[8] ) = { 1 << ( 16 - INSET_ALPHA_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 0, 0, 0, 0, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5QuantMask[8] ) = { 0xFF, C565_6_MASK, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Rep[8] ) = { 0, 1 << ( 16 - 6 ), 0, 0, 0, 0, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_insetNormal3DcRound[8] ) = { ((1<<(INSET_ALPHA_SHIFT-1))-1), ((1<<(INSET_ALPHA_SHIFT-1))-1), 0, 0, 0, 0, 0, 0 };
ALIGN16( static word SIMD_SSE2_word_insetNormal3DcMask[8] ) = { 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
ALIGN16( static word SIMD_SSE2_word_insetNormal3DcShiftUp[8] ) = { 1 << INSET_ALPHA_SHIFT, 1 << INSET_ALPHA_SHIFT, 1, 1, 1, 1, 1, 1 };
ALIGN16( static word SIMD_SSE2_word_insetNormal3DcShiftDown[8] ) = { 1 << ( 16 - INSET_ALPHA_SHIFT ), 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0, 0, 0, 0, 0 };
ALIGN16( static byte SIMD_SSE2_byte_0[16] ) = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
ALIGN16( static byte SIMD_SSE2_byte_1[16] ) = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
ALIGN16( static byte SIMD_SSE2_byte_2[16] ) = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 };
ALIGN16( static byte SIMD_SSE2_byte_7[16] ) = { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 };
 
void ExtractBlock_SSE2( const byte *inPtr, int width, byte *block ) {
    __asm {
        mov         esi, inPtr
        mov         edi, block
        mov         eax, width
        shl         eax, 2
        movdqa      xmm0, [esi]
        movdqa      xmmword ptr [edi+ 0], xmm0
        movdqa      xmm1, xmmword ptr [esi+eax]
        movdqa      xmmword ptr [edi+16], xmm1
        movdqa      xmm2, xmmword ptr [esi+eax*2]
        add         esi, eax
        movdqa      xmmword ptr [edi+32], xmm2
        movdqa      xmm3, xmmword ptr [esi+eax*2]
        movdqa      xmmword ptr [edi+48], xmm3
    }
}
 
void GetMinMaxNormalsBBox_SSE2( const byte *block, byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         eax, block
        mov         esi, minNormal
        mov         edi, maxNormal
        movdqa      xmm0, xmmword ptr [eax+ 0]
        movdqa      xmm1, xmmword ptr [eax+ 0]
        pminub      xmm0, xmmword ptr [eax+16]
        pmaxub      xmm1, xmmword ptr [eax+16]
        pminub      xmm0, xmmword ptr [eax+32]
        pmaxub      xmm1, xmmword ptr [eax+32]
        pminub      xmm0, xmmword ptr [eax+48]
        pmaxub      xmm1, xmmword ptr [eax+48]
        pshufd      xmm3, xmm0, R_SHUFFLE_D( 2, 3, 2, 3 )
        pshufd      xmm4, xmm1, R_SHUFFLE_D( 2, 3, 2, 3 )
        pminub      xmm0, xmm3
        pmaxub      xmm1, xmm4
        pshuflw     xmm6, xmm0, R_SHUFFLE_D( 2, 3, 2, 3 )
        pshuflw     xmm7, xmm1, R_SHUFFLE_D( 2, 3, 2, 3 )
        pminub      xmm0, xmm6
        pmaxub      xmm1, xmm7
        movd        dword ptr [esi], xmm0
        movd        dword ptr [edi], xmm1
    }
}
 
void InsetNormalsBBoxDXT5_SSE2( byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         esi, minNormal
        mov         edi, maxNormal
        movd        xmm0, dword ptr [esi]
        movd        xmm1, dword ptr [edi]
        punpcklbw   xmm0, SIMD_SSE2_byte_0
        punpcklbw   xmm1, SIMD_SSE2_byte_0
        movdqa      xmm2, xmm1
        psubw       xmm2, xmm0
        psubw       xmm2, SIMD_SSE2_word_insetNormalDXT5Round
        pand        xmm2, SIMD_SSE2_word_insetNormalDXT5Mask
        pmullw      xmm0, SIMD_SSE2_word_insetNormalDXT5ShiftUp
        pmullw      xmm1, SIMD_SSE2_word_insetNormalDXT5ShiftUp
        paddw       xmm0, xmm2
        psubw       xmm1, xmm2
        pmulhw      xmm0, SIMD_SSE2_word_insetNormalDXT5ShiftDown
        pmulhw      xmm1, SIMD_SSE2_word_insetNormalDXT5ShiftDown
        pmaxsw      xmm0, SIMD_SSE2_word_0
        pmaxsw      xmm1, SIMD_SSE2_word_0
        pand        xmm0, SIMD_SSE2_word_insetNormalDXT5QuantMask
        pand        xmm1, SIMD_SSE2_word_insetNormalDXT5QuantMask
        movdqa      xmm2, xmm0
        movdqa      xmm3, xmm1
        pmulhw      xmm2, SIMD_SSE2_word_insetNormalDXT5Rep
        pmulhw      xmm3, SIMD_SSE2_word_insetNormalDXT5Rep
        por         xmm0, xmm2
        por         xmm1, xmm3
        packuswb    xmm0, xmm0
        packuswb    xmm1, xmm1
        movd        dword ptr [esi], xmm0
        movd        dword ptr [edi], xmm1
    }
}
 
void InsetNormalsBBox3Dc_SSE2( byte *minNormal, byte *maxNormal ) {
    __asm {
        mov         esi, minNormal
        mov         edi, maxNormal
        movd        xmm0, dword ptr [esi]
        movd        xmm1, dword ptr [edi]
        punpcklbw   xmm0, SIMD_SSE2_byte_0
        punpcklbw   xmm1, SIMD_SSE2_byte_0
        movdqa      xmm2, xmm1
        psubw       xmm2, xmm0
        psubw       xmm2, SIMD_SSE2_word_insetNormal3DcRound
        pand        xmm2, SIMD_SSE2_word_insetNormal3DcMask
        pmullw      xmm0, SIMD_SSE2_word_insetNormal3DcShiftUp
        pmullw      xmm1, SIMD_SSE2_word_insetNormal3DcShiftUp
        paddw       xmm0, xmm2
        psubw       xmm1, xmm2
        pmulhw      xmm0, SIMD_SSE2_word_insetNormal3DcShiftDown
        pmulhw      xmm1, SIMD_SSE2_word_insetNormal3DcShiftDown
        pmaxsw      xmm0, SIMD_SSE2_word_0
        pmaxsw      xmm1, SIMD_SSE2_word_0
        packuswb    xmm0, xmm0
        packuswb    xmm1, xmm1
        movd        dword ptr [esi], xmm0
        movd        dword ptr [edi], xmm1
    }
}
 
void EmitAlphaIndices_SSE2( const byte *block, const int channelBitOffset, const int minAlpha, const int maxAlpha ) {
    __asm {
        movd        xmm7, channelBitOffset
 
        mov         esi, block
        movdqa      xmm0, xmmword ptr [esi+ 0]
        movdqa      xmm5, xmmword ptr [esi+16]
        movdqa      xmm6, xmmword ptr [esi+32]
        movdqa      xmm4, xmmword ptr [esi+48]
 
        psrld       xmm0, xmm7
        psrld       xmm5, xmm7
        psrld       xmm6, xmm7
        psrld       xmm4, xmm7
 
        pand        xmm0, SIMD_SSE2_dword_byte_mask
        pand        xmm5, SIMD_SSE2_dword_byte_mask
        pand        xmm6, SIMD_SSE2_dword_byte_mask
        pand        xmm4, SIMD_SSE2_dword_byte_mask
 
        packuswb    xmm0, xmm5
        packuswb    xmm6, xmm4
 
        movd        xmm5, maxAlpha
        pshuflw     xmm5, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufd      xmm5, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
        movdqa      xmm7, xmm5
 
        movd        xmm2, minAlpha
        pshuflw     xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufd      xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        movdqa      xmm3, xmm2
 
        movdqa      xmm4, xmm5
        psubw       xmm4, xmm2
        pmulhw      xmm4, SIMD_SSE2_word_div_by_14
        movdqa      xmm1, xmm5
        psubw       xmm1, xmm4
        packuswb    xmm1, xmm1                              // ab1
 
        pmullw      xmm5, SIMD_SSE2_word_scale66554400
        pmullw      xmm7, SIMD_SSE2_word_scale11223300
        pmullw      xmm2, SIMD_SSE2_word_scale11223300
        pmullw      xmm3, SIMD_SSE2_word_scale66554400
        paddw       xmm5, xmm2
        paddw       xmm7, xmm3
        pmulhw      xmm5, SIMD_SSE2_word_div_by_7
        pmulhw      xmm7, SIMD_SSE2_word_div_by_7
        psubw       xmm5, xmm4
        psubw       xmm7, xmm4
 
        pshufd      xmm2, xmm5, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufd      xmm3, xmm5, R_SHUFFLE_D( 1, 1, 1, 1 )
        pshufd      xmm4, xmm5, R_SHUFFLE_D( 2, 2, 2, 2 )
        packuswb    xmm2, xmm2                              // ab2
        packuswb    xmm3, xmm3                              // ab3
        packuswb    xmm4, xmm4                              // ab4
 
        packuswb    xmm0, xmm6
 
        pshufd      xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 )
        pshufd      xmm6, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 )
        pshufd      xmm7, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 )
        packuswb    xmm5, xmm5                              // ab5
        packuswb    xmm6, xmm6                              // ab6
        packuswb    xmm7, xmm7                              // ab7
 
        pmaxub      xmm1, xmm0
        pmaxub      xmm2, xmm0
        pmaxub      xmm3, xmm0
        pcmpeqb     xmm1, xmm0
        pcmpeqb     xmm2, xmm0
        pcmpeqb     xmm3, xmm0
        pmaxub      xmm4, xmm0
        pmaxub      xmm5, xmm0
        pmaxub      xmm6, xmm0
        pmaxub      xmm7, xmm0
        pcmpeqb     xmm4, xmm0
        pcmpeqb     xmm5, xmm0
        pcmpeqb     xmm6, xmm0
        pcmpeqb     xmm7, xmm0
        movdqa      xmm0, SIMD_SSE2_byte_8
        paddsb      xmm0, xmm1
        paddsb      xmm2, xmm3
        paddsb      xmm4, xmm5
        paddsb      xmm6, xmm7
        paddsb      xmm0, xmm2
        paddsb      xmm4, xmm6
        paddsb      xmm0, xmm4
        pand        xmm0, SIMD_SSE2_byte_7
        movdqa      xmm1, SIMD_SSE2_byte_2
        pcmpgtb     xmm1, xmm0
        pand        xmm1, SIMD_SSE2_byte_1
        pxor        xmm0, xmm1
        movdqa      xmm1, xmm0
        movdqa      xmm2, xmm0
        movdqa      xmm3, xmm0
        movdqa      xmm4, xmm0
        movdqa      xmm5, xmm0
        movdqa      xmm6, xmm0
        movdqa      xmm7, xmm0
        psrlq       xmm1,  8- 3
        psrlq       xmm2, 16- 6
        psrlq       xmm3, 24- 9
        psrlq       xmm4, 32-12
        psrlq       xmm5, 40-15
        psrlq       xmm6, 48-18
        psrlq       xmm7, 56-21
        pand        xmm0, SIMD_SSE2_dword_alpha_bit_mask0
        pand        xmm1, SIMD_SSE2_dword_alpha_bit_mask1
        pand        xmm2, SIMD_SSE2_dword_alpha_bit_mask2
        pand        xmm3, SIMD_SSE2_dword_alpha_bit_mask3
        pand        xmm4, SIMD_SSE2_dword_alpha_bit_mask4
        pand        xmm5, SIMD_SSE2_dword_alpha_bit_mask5
        pand        xmm6, SIMD_SSE2_dword_alpha_bit_mask6
        pand        xmm7, SIMD_SSE2_dword_alpha_bit_mask7
        por         xmm0, xmm1
        por         xmm2, xmm3
        por         xmm4, xmm5
        por         xmm6, xmm7
        por         xmm0, xmm2
        por         xmm4, xmm6
        por         xmm0, xmm4
        mov         esi, globalOutData
        movd        [esi+0], xmm0
        pshufd      xmm1, xmm0, R_SHUFFLE_D( 2, 3, 0, 1 )
        movd        [esi+3], xmm1
    }
 
    globalOutData += 6;
}
 
void EmitGreenIndices_SSE2( const byte *block, const int channelBitOffset, const int minGreen, const int maxGreen ) {
    __asm {
        movd        xmm7, channelBitOffset
 
        mov         esi, block
        movdqa      xmm0, xmmword ptr [esi+ 0]
        movdqa      xmm5, xmmword ptr [esi+16]
        movdqa      xmm6, xmmword ptr [esi+32]
        movdqa      xmm4, xmmword ptr [esi+48]
 
        psrld       xmm0, xmm7
        psrld       xmm5, xmm7
        psrld       xmm6, xmm7
        psrld       xmm4, xmm7
 
        pand        xmm0, SIMD_SSE2_dword_byte_mask
        pand        xmm5, SIMD_SSE2_dword_byte_mask
        pand        xmm6, SIMD_SSE2_dword_byte_mask
        pand        xmm4, SIMD_SSE2_dword_byte_mask
 
        packuswb    xmm0, xmm5
        packuswb    xmm6, xmm4
 
        movd        xmm2, maxGreen
        pshuflw     xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufd      xmm2, xmm2, R_SHUFFLE_D( 0, 0, 0, 0 )
        movdqa      xmm1, xmm2
 
        movd        xmm3, minGreen
        pshuflw     xmm3, xmm3, R_SHUFFLE_D( 0, 0, 0, 0 )
        pshufd      xmm3, xmm3, R_SHUFFLE_D( 0, 0, 0, 0 )
 
        movdqa      xmm4, xmm2
        psubw       xmm4, xmm3
        pmulhw      xmm4, SIMD_SSE2_word_div_by_6
 
        psllw       xmm2, 1
        paddw       xmm2, xmm3
        pmulhw      xmm2, SIMD_SSE2_word_div_by_3
        psubw       xmm2, xmm4
        packuswb    xmm2, xmm2                          // gb2
 
        psllw       xmm3, 1
        paddw       xmm3, xmm1
        pmulhw      xmm3, SIMD_SSE2_word_div_by_3
        psubw       xmm3, xmm4
        packuswb    xmm3, xmm3                          // gb3
 
        psubw       xmm1, xmm4
        packuswb    xmm1, xmm1                          // gb1
 
        packuswb    xmm0, xmm6
 
        pmaxub      xmm1, xmm0
        pmaxub      xmm2, xmm0
        pmaxub      xmm3, xmm0
        pcmpeqb     xmm1, xmm0
        pcmpeqb     xmm2, xmm0
        pcmpeqb     xmm3, xmm0
        movdqa      xmm0, SIMD_SSE2_byte_4
        paddsb      xmm0, xmm1
        paddsb      xmm2, xmm3
        paddsb      xmm0, xmm2
        pand        xmm0, SIMD_SSE2_byte_3
        movdqa      xmm4, SIMD_SSE2_byte_2
        pcmpgtb     xmm4, xmm0
        pand        xmm4, SIMD_SSE2_byte_1
        pxor        xmm0, xmm4
        movdqa      xmm4, xmm0
        movdqa      xmm5, xmm0
        movdqa      xmm6, xmm0
        movdqa      xmm7, xmm0
        psrlq       xmm4,  8- 2
        psrlq       xmm5, 16- 4
        psrlq       xmm6, 24- 6
        psrlq       xmm7, 32- 8
        pand        xmm4, SIMD_SSE2_dword_color_bit_mask1
        pand        xmm5, SIMD_SSE2_dword_color_bit_mask2
        pand        xmm6, SIMD_SSE2_dword_color_bit_mask3
        pand        xmm7, SIMD_SSE2_dword_color_bit_mask4
        por         xmm5, xmm4
        por         xmm7, xmm6
        por         xmm7, xmm5
        movdqa      xmm4, xmm0
        movdqa      xmm5, xmm0
        movdqa      xmm6, xmm0
        psrlq       xmm4, 40-10
        psrlq       xmm5, 48-12
        psrlq       xmm6, 56-14
        pand        xmm0, SIMD_SSE2_dword_color_bit_mask0
        pand        xmm4, SIMD_SSE2_dword_color_bit_mask5
        pand        xmm5, SIMD_SSE2_dword_color_bit_mask6
        pand        xmm6, SIMD_SSE2_dword_color_bit_mask7
        por         xmm4, xmm5
        por         xmm0, xmm6
        por         xmm7, xmm4
        por         xmm7, xmm0
        mov         esi, globalOutData
        movd        [esi+0], xmm7
        pshufd      xmm6, xmm7, R_SHUFFLE_D( 2, 3, 0, 1 )
        movd        [esi+2], xmm6
    }
 
    globalOutData += 4;
}
 
bool CompressNormalMapDXT5_SSE2( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    ALIGN16( byte block[64] );
    ALIGN16( byte normalMin[4] );
    ALIGN16( byte normalMax[4] );
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock_SSE2( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox_SSE2( block, normalMin, normalMax );
            InsetNormalsBBoxDXT5_SSE2( normalMin, normalMax );
 
            // Write out Nx into alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices_SSE2( block, 0*8, normalMin[0], normalMax[0] );
 
            // Write out Ny into green channel.
            EmitUShort( NormalYTo565( normalMax[1] ) );
            EmitUShort( NormalYTo565( normalMin[1] ) );
            EmitGreenIndices_SSE2( block, 1*8, normalMin[1], normalMax[1] );
        }
    }
 
    outputBytes = outData - outBuf;
}
 
void CompressNormalMap3Dc_SSE2( const byte *inBuf, byte *outBuf, int width, int height, int &outputBytes ) {
    ALIGN16( byte block[64] );
    ALIGN16( byte normalMin[4] );
    ALIGN16( byte normalMax[4] );
 
    globalOutData = outBuf;
 
    for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) {
        for ( int i = 0; i < width; i += 4 ) {
 
            ExtractBlock_SSE2( inBuf + i * 4, width, block );
 
            GetMinMaxNormalsBBox_SSE2( block, normalMin, normalMax );
            InsetNormalsBBox3Dc_SSE2( normalMin, normalMax );
 
            // Write out Nx as an alpha channel.
            EmitByte( normalMax[0] );
            EmitByte( normalMin[0] );
            EmitAlphaIndices_SSE2( block, 0*8, normalMin[0], normalMax[0] );
 
            // Write out Ny as an alpha channel.
            EmitByte( normalMax[1] );
            EmitByte( normalMin[1] );
            EmitAlphaIndices_SSE2( block, 1*8, normalMin[1], normalMax[1] );
        }
    }
 
    outputBytes = outData - outBuf;
}

Appendix D

/*
    Real-time DXT1 & YCoCg DXT5 compression (Cg 2.0)
    Copyright (c) NVIDIA Corporation.
    Written by: Ignacio Castano 
 
    Thanks to JMP van Waveren, Simon Green, Eric Werness, Simon Brown
 
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.
*/
 
// vertex program
void compress_vp(float4 pos : POSITION,
                      float2 texcoord : TEXCOORD0,
                      out float4 hpos : POSITION,
                      out float2 o_texcoord : TEXCOORD0
                      )
{
    o_texcoord = texcoord;
    hpos = pos;
}
 
typedef unsigned int uint;
typedef unsigned int2 uint2;
typedef unsigned int4 uint4;
 
void ExtractColorBlockXY(out float2 col[16], sampler2D image, float2 texcoord, float2 imageSize)
{
#if 0
    float2 texelSize = (1.0f / imageSize);
    texcoord -= texelSize * 2;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            col[i*4+j] = tex2D(image, texcoord + float2(j, i) * texelSize).rg;
        }
    }
#else
    // use TXF instruction (integer coordinates with offset)
    // note offsets must be constant
    //int4 base = int4(wpos*4-2, 0, 0);
    int4 base = int4(texcoord * imageSize - 1.5, 0, 0);
    col[0] = tex2Dfetch(image, base, int2(0, 0)).rg;
    col[1] = tex2Dfetch(image, base, int2(1, 0)).rg;
    col[2] = tex2Dfetch(image, base, int2(2, 0)).rg;
    col[3] = tex2Dfetch(image, base, int2(3, 0)).rg;
    col[4] = tex2Dfetch(image, base, int2(0, 1)).rg;
    col[5] = tex2Dfetch(image, base, int2(1, 1)).rg;
    col[6] = tex2Dfetch(image, base, int2(2, 1)).rg;
    col[7] = tex2Dfetch(image, base, int2(3, 1)).rg;
    col[8] = tex2Dfetch(image, base, int2(0, 2)).rg;
    col[9] = tex2Dfetch(image, base, int2(1, 2)).rg;
    col[10] = tex2Dfetch(image, base, int2(2, 2)).rg;
    col[11] = tex2Dfetch(image, base, int2(3, 2)).rg;
    col[12] = tex2Dfetch(image, base, int2(0, 3)).rg;
    col[13] = tex2Dfetch(image, base, int2(1, 3)).rg;
    col[14] = tex2Dfetch(image, base, int2(2, 3)).rg;
    col[15] = tex2Dfetch(image, base, int2(3, 3)).rg;
#endif
}
 
// find minimum and maximum colors based on bounding box in color space
void FindMinMaxColorsBox(float2 block[16], out float2 mincol, out float2 maxcol)
{
    mincol = block[0];
    maxcol = block[0];
    
    for (int i = 1; i < 16; i++) {
        mincol = min(mincol, block[i]);
        maxcol = max(maxcol, block[i]);
    }
}
 
void InsetNormalsBBoxDXT5(in out float2 mincol, in out float2 maxcol)
{
    float2 inset;
    inset.x = (maxcol.x - mincol.x) / 32.0 - (16.0 / 255.0) / 32.0;      // ALPHA scale-bias.
    inset.y = (maxcol.y - mincol.y) / 16.0 - (8.0 / 255.0) / 16;      // GREEN scale-bias.
    mincol = saturate(mincol + inset);
    maxcol = saturate(maxcol - inset);
}
 
void InsetNormalsBBoxLATC(in out float2 mincol, in out float2 maxcol)
{
    float2 inset = (maxcol - mincol) / 32.0 - (16.0 / 255.0) / 32.0;  // ALPHA scale-bias.
    mincol = saturate(mincol + inset);
    maxcol = saturate(maxcol - inset);
}
 
uint EmitGreenEndPoints(in out float ming, in out float maxg)
{
    uint c0 = round(ming * 63);
    uint c1 = round(maxg * 63);
 
    ming = float((c0 << 2) | (c0 >> 4)) * (1.0 / 255.0);
    maxg = float((c1 << 2) | (c1 >> 4)) * (1.0 / 255.0);
 
    return (c0 << 21) | (c1 << 5);
}
 
#if 1
 
uint EmitGreenIndices(float2 block[16], float minGreen, float maxGreen)
{
    const int GREEN_RANGE = 3;
 
    float bias = maxGreen + (maxGreen - minGreen) / (2.0 * GREEN_RANGE);
    float scale = 1.0f / (maxGreen - minGreen);
 
    // Compute indices
    uint indices = 0;
    for (int i = 0; i < 16; i++)
    {
        uint index = saturate((bias - block[i].y) * scale) * GREEN_RANGE;
        indices |= index << (i * 2);
    }
 
    uint i0 = (indices & 0x55555555);
    uint i1 = (indices & 0xAAAAAAAA) >> 1;
    indices = ((i0 ^ i1) << 1) | i1;
 
    // Output indices
    return indices;
}
 
#else
 
uint EmitGreenIndices(float2 block[16], float minGreen, float maxGreen)
{
    const int GREEN_RANGE = 3;
 
    float mid = (maxGreen - minGreen) / (2 * GREEN_RANGE);
 
    float yb1 = minGreen + mid;
    float yb2 = (2 * maxGreen + 1 * minGreen) / GREEN_RANGE + mid;
    float yb3 = (1 * maxGreen + 2 * minGreen) / GREEN_RANGE + mid;
 
    // Compute indices
    uint indices = 0;
    for (int i = 0; i < 16; i++)
    {
        float y = block[i].y;
 
        uint index = (y <= yb1);
        index += (y <= yb2);
        index += (y <= yb3);
 
        indices |= index << (i * 2);
    }
 
    uint i0 = (indices & 0x55555555);
    uint i1 = (indices & 0xAAAAAAAA) >> 1;
    indices = ((i0 ^ i1) << 1) | i1;
 
    // Output indices
    return indices;
}
 
#endif
 
uint EmitAlphaEndPoints(float mincol, float maxcol)
{
    uint c0 = round(mincol * 255);
    uint c1 = round(maxcol * 255);
 
    return (c0 << 8) | c1;
}
 
uint2 EmitAlphaIndices(float2 block[16], float minAlpha, float maxAlpha)
{
    const int ALPHA_RANGE = 7;
 
    float bias = maxAlpha + (maxAlpha - minAlpha) / (2.0 * ALPHA_RANGE);
    float scale = 1.0f / (maxAlpha - minAlpha);
 
    uint2 indices = 0;
 
    for (int i = 0; i < 6; i++)
    {
        uint index = saturate((bias - block[i].x) * scale) * ALPHA_RANGE;
        indices.x |= index << (3 * i);
    }
 
    for (int i = 6; i < 16; i++)
    {
        uint index = saturate((bias - block[i].x) * scale) * ALPHA_RANGE;
        indices.y |= index << (3 * i - 18);
    }
 
    uint2 i0 = (indices >> 0) & 0x09249249;
    uint2 i1 = (indices >> 1) & 0x09249249;
    uint2 i2 = (indices >> 2) & 0x09249249;
 
    i2 ^= i0 & i1;
    i1 ^= i0;
    i0 ^= (i1 | i2);
 
    indices.x = (i2.x << 2) | (i1.x << 1) | i0.x;
    indices.y = (((i2.y << 2) | (i1.y << 1) | i0.y) << 2) | (indices.x >> 16);
    indices.x <<= 16;
 
    return indices;
}
 
uint2 EmitLuminanceIndices(float2 block[16], float minAlpha, float maxAlpha)
{
    const int ALPHA_RANGE = 7;
 
    float bias = maxAlpha + (maxAlpha - minAlpha) / (2.0 * ALPHA_RANGE);
    float scale = 1.0f / (maxAlpha - minAlpha);
 
    uint2 indices = 0;
 
    for (int i = 0; i < 6; i++)
    {
        uint index = saturate((bias - block[i].y) * scale) * ALPHA_RANGE;
        indices.x |= index << (3 * i);
    }
 
    for (int i = 6; i < 16; i++)
    {
        uint index = saturate((bias - block[i].y) * scale) * ALPHA_RANGE;
        indices.y |= index << (3 * i - 18);
    }
 
    uint2 i0 = (indices >> 0) & 0x09249249;
    uint2 i1 = (indices >> 1) & 0x09249249;
    uint2 i2 = (indices >> 2) & 0x09249249;
 
    i2 ^= i0 & i1;
    i1 ^= i0;
    i0 ^= (i1 | i2);
 
    indices.x = (i2.x << 2) | (i1.x << 1) | i0.x;
    indices.y = (((i2.y << 2) | (i1.y << 1) | i0.y) << 2) | (indices.x >> 16);
    indices.x <<= 16;
 
    return indices;
}
 
// compress a 4x4 block to DXT5nm format
// integer version, renders to 4 x int32 buffer
uint4 compress_NormalDXT5_fp(float2 texcoord : TEXCOORD0,
                      uniform sampler2D image,
                      uniform float2 imageSize = { 512.0, 512.0 }
                      ) : COLOR
{
    // read block
    float2 block[16];
    ExtractColorBlockXY(block, image, texcoord, imageSize);
 
    // find min and max colors
    float2 mincol, maxcol;
    FindMinMaxColorsBox(block, mincol, maxcol);
    InsetNormalsBBoxDXT5(mincol, maxcol);
 
    uint4 output;
 
    // Output X in DXT5 green channel.
    output.z = EmitGreenEndPoints(mincol.y, maxcol.y);
    output.w = EmitGreenIndices(block, mincol.y, maxcol.y);
 
    // Output Y in DXT5 alpha block.
    output.x = EmitAlphaEndPoints(mincol.x, maxcol.x);
 
    uint2 indices = EmitAlphaIndices(block, mincol.x, maxcol.x);
    output.x |= indices.x;
    output.y = indices.y;
 
    return output;
}
 
// compress a 4x4 block to LATC format
// integer version, renders to 4 x int32 buffer
uint4 compress_NormalLATC_fp(float2 texcoord : TEXCOORD0,
                      uniform sampler2D image,
                      uniform float2 imageSize = { 512.0, 512.0 }
                      ) : COLOR
{
    //imageSize = tex2Dsize(image, texcoord);
 
    // read block
    float2 block[16];
    ExtractColorBlockXY(block, image, texcoord, imageSize);
 
    // find min and max colors
    float2 mincol, maxcol;
    FindMinMaxColorsBox(block, mincol, maxcol);
    InsetNormalsBBoxLATC(mincol, maxcol);
 
    uint4 output;
 
    // Output Ny as an alpha block.
    output.x = EmitAlphaEndPoints(mincol.y, maxcol.y);
 
    uint2 indices = EmitLuminanceIndices(block, mincol.y, maxcol.y);
    output.x |= indices.x;
    output.y = indices.y;
 
    // Output Nx as an alpha block.
    output.z = EmitAlphaEndPoints(mincol.x, maxcol.x);
 
    indices = EmitAlphaIndices(block, mincol.x, maxcol.x);
    output.z |= indices.x;
    output.w = indices.y;
 
    return output;
}
 
uniform float3 lightDirection;
uniform bool reconstructNormal = true;
uniform bool displayNormal = true;
uniform bool displayError = false;
uniform float errorScale = 4.0f;
 
uniform sampler2D image : TEXUNIT0;
uniform sampler2D originalImage : TEXUNIT1;
 
float3 shadeNormal(float3 N)
{
    float3 L = normalize(lightDirection);
    float3 R = reflect(float3(0, 0, -1), N);
 
    float diffuse = saturate(dot (N, L));
    float specular = pow(saturate(dot(R, L)), 12);
    
    return 0.7 * diffuse + 0.5 * specular;
}
 
// Draw reconstructed normals.
float4 display_fp(float2 texcoord : TEXCOORD0) : COLOR
{
    float3 N;
    if (reconstructNormal)
    {
        N.xy = 2 * tex2D(image, texcoord).wy - 1;
        N.z = sqrt(saturate(1 - N.x * N.x - N.y * N.y));
    }
    else
    {
        N = normalize(2 * tex2D(image, texcoord).xyz - 1);
    }
 
    if (displayError)
    {
        float3 originalNormal = normalize(2 * tex2D(originalImage, texcoord).xyz - 1);
 
        if (displayNormal)
        {
            float3 diff = (N - originalNormal) * errorScale;
            return float4(diff, 1);
        }
        else
        {
            float3 diff = abs(shadeNormal(N) - shadeNormal(originalNormal)) * errorScale;
            return float4(diff, 1);
        }
    }
    else
    {
        if (displayNormal)
        {
            return float4(0.5 * N + 0.5, 1);
        }
        else
        {
            return float4(shadeNormal(N), 1);
        }
    }
}
 
// Draw geometry normals.
uniform float4x4 mvp : ModelViewProjection;
uniform float3x3 mvit : ModelViewInverseTranspose;
 
void display_object_vp(float4 pos : POSITION,
                      float3 normal : NORMAL,
                      out float4 hpos : POSITION,
                      out float3 o_normal : TEXCOORD0)
{
    hpos = mul(pos, mvp);
    o_normal = mul(normal, mvit);
}
 
float4 display_object_fp(float3 N : TEXCOORD0) : COLOR
{
    N = normalize(N);
    return float4(0.5 * N + 0.5, 1);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章