實時DXt壓縮算法
JMP van Waveren
|
|
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位向量 投影在單位球體上的分佈 |
在今天的圖形硬件上,普通地圖也可以以多種壓縮格式存儲,在渲染過程中實時解壓縮。由於減少的帶寬要求,壓縮的法線貼圖不僅要求顯卡內存少得多,而且通常還會比未壓縮的法線貼圖快。各種不同的方式來利用現有的紋理壓縮格式的正常地圖壓縮,已被建議在文獻[ 7,8,9 ]。這些正常的地圖壓縮技術中的幾個以及它們的擴展在第2節和第3節進行了評估。
雖然這些格式的解壓縮是在硬件中實時完成的,但對這些格式的壓縮可能需要相當長的時間。現有壓縮機設計爲高質量離線壓縮,而不是實時壓縮[ 20,21,22 ]。然而,實時壓縮對於從不同格式的代碼轉換法線圖,動態生成的法線貼圖的壓縮以及壓縮的法線貼圖渲染目標來說是非常有用的。在第4節和第5節中,提出了兩種高度優化的切線空間法線貼圖壓縮算法,可用於實現CPU和GPU的實時性能。
對象空間正常圖
對象空間法則映射相對於整個對象的位置和方向存儲法線。物體空間中的常規可以在整個單位球體上的任何地方,並且通常存儲爲具有三個組件的向量:X,Y和Z.可以使用常規顏色紋理壓縮技術存儲對象空間法線貼圖,但是這些技術可能不會有效,因爲法線貼圖不具有與顏色紋理相同的屬性。
2.1對象空間DXT1
DXT1 [ 3,4 ],也被稱爲在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格式[ 3,4 ],也被稱爲在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塊,如[建議的11,12 ]。可以使用這樣的旋轉來找到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
雖然從前面部分描述的格式解壓縮是在硬件中實時完成的,但對這些格式的壓縮可能需要相當長的時間。現有壓縮機設計爲高質量離線壓縮,而不是實時壓縮[ 20,21,22 ]。然而,實時壓縮對於以不同(更高效的)格式壓縮存儲在磁盤上的法線貼圖,並且壓縮動態生成的法線貼圖是非常有用的。
在今天的渲染引擎中,切線空間法線貼圖比對象空間法線貼圖更受歡迎。在當前的硬件上,沒有壓縮格式可用於非常適用的對象空間法線貼圖。第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從最小到最大值的順序如下。
|
但是,“顏色”採樣點按照DXT5格式進行不同的排序,如下所示。
|
從四個減去比較的結果,並將結果與按位邏輯AND和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); }