(轉)紋理映射過濾

轉自:http://blog.csdn.net/i_dovelemon/article/details/27839279 算法

過濾器(Filters)

          在3D空間中,紋理圖的大小每每並不老是和定義的三角形一樣的大小。也就是說,咱們須要對紋理進行放大和縮小,也就是進行縮放操做。那麼咱們如何對紋理進行操做,纔可以讓紋理可以放大和縮小以後,不會變的混亂不堪了?app

         在這裏,使用的就是各類採樣方法,對紋理圖進行採樣,爲了更好的效果可能還須要再使用濾波器進行過濾。在DirectX中,支持三種不一樣種類的採樣方法。下面一一介紹他們。測試

        首先給出這三種採樣方式的名稱,他們分別是:優化

 

  •        點採樣
  •        雙線性採樣
  •         三線性紋理濾波採樣

        咱們知道,在上面定義了幾個頂點的紋理座標,可是對於一個三角形來講,它是一個平面,它須要將紋理整個的映射到這個平面上來。也就是說,如何經過這三個頂點的紋理座標,來鋪滿整個平面?       讀者,可能注意到這個問題,和咱們之前討論的如何經過頂點的顏色,而後將整個三角形進行着色的問題很類似。對頭,這裏,咱們也是使用插值的方式來獲取每個像素點的紋理座標,而後經過這個紋理座標,採用不一樣的採樣方式來獲取紋理圖中的像素值,使用這個採集的值來填充這個像素。spa

        咱們知道了如何進行紋理的插值(插值方法和前面介紹的顏色插值一致,這裏再也不贅述)以後,就要肯定到底使用哪一種採樣方法來進行採樣。下面將一一介紹不一樣的採樣方法。.net

點採樣

       點採樣,故名示意,就是使用咱們進行插值後的紋理座標來將它擴展到與紋理圖相應的尺寸大小(還記的前面說過的,紋理座標其實是歸一化的座標),而後將這個變換後的紋理座標進行取整,也就是截取小數部分,只保留整數部分,而後就使用這個整數的座標來獲取紋理圖中對應的紋素值。htm

      好比下面的數據:blog

      咱們紋理圖的尺寸是128*128 ;遊戲

      咱們通過插值計算後的某個像素點的紋理座標爲(0,70,0.55)ip

      那麼咱們將這個紋理座標進行變化,使得紋理座標的尺寸和紋理圖的尺寸一致,即:

     0.70 * 128 = 89.6,  0.55 * 128 = 70.4

      再進行取整操做獲得最後的紋理圖上的座標爲(89, 70)

      而後,咱們就使用這樣的座標,來獲取紋理圖中第89列,第70行的那個像素的值,用這個值來填充咱們計算的那個像素點的顏色。

 

     讀者能夠看出,因爲咱們截取了小數部分,因此失去的部分的信息,這樣的採樣方法效果確定是很不理想的。一種稍微改進點的方法就是保留小數部分,而將採起紋理圖中相鄰的兩個像素的值,使用小數部分做爲權值來進行採樣。

     拿上面的例子來講吧,咱們計算後的保留小數的紋理圖座標爲(89.6, 70.4),而取整以後的數據爲(89,70)。

     那麼咱們能夠發現,這個像素實際上佔用的空間是89列和90列這兩個像素的位置,也就是說它有0.6的(89,70)位置像素值,有0.4的(90,70)的像素值,因此最後的像素值應該爲:

     0.6 * Texel(89,70) + 0.4 * Texel(90, 70)

      經過這樣的方式,咱們能稍微的改進點採樣方法的效果。

      點採樣方法效果不好,可是因爲操做簡單,因此效率會很高。

 

雙線性採樣

       讀者可能發現,咱們上面討論改進版的點採樣方法時,故意沒有考慮v座標的跨度關係。也就是說,我只考慮了u座標,在相鄰兩個座標上的權值關係。因此,若是將v座標上的權值關係也考慮進去,效果是否更加的逼真了呢?

      的確,這就是所謂的雙線性採樣理論。經過在u和v兩種維度上,都考慮權值關係,來獲取最後的像素值。

      仍是拿上面的關係舉例,很明顯,這個紋理座標牽涉到了四個像素,他們的座標分別是(89,70), (90,70), (89,71)和(90,71)。

      咱們知道了它是和這四個像素點相關的,那麼只要獲取每個像素點上的權值,咱們天然就可使用權值平均的方法來獲取最後的像素值了。爲了明確該紋理座標,在這四個像素上所佔有的權值,咱們使用圖示的方式來闡釋:

      

       咱們就能夠經過下面的公式來計算各個像素的權值,這個公式能夠很容易的從上圖中推導來:

      (89,70) : (89.6 - 89) * (70.4 - 70) = 0.6 * 0.4 = 0.24 ;

      (90,70) : (90 - 86.6) * (70.4 - 70) = 0.4 * 0.4 = 0.16 ;

      (89,71) : (89.6 - 89) * (71 - 70.4) = 0.6 * 0.6 = 0.36 ;

      (90,71) : (90 - 89.6) * (71 - 70.4) = 0.4 * 0.6 = 0.24 ;

      咱們將上面計算出來的權值相加,即0.24 + 0.16 + 0.36 + 0.24 = 1.0 ,也就是說完整的表述了這個像素值。

      而後咱們用上面的權值分別乘以每個像素的值,來獲取最後的像素:

      0.24 * Texel(89, 70) + 0.16 * Texel(90, 70) + 0.36 * Texel(89, 71) + 0.24 * Texel(90, 71)

      好了,經過上面的方法,咱們就可以獲得最後的像素值了,並且這個方法可以基本上徹底保留紋理座標的信息,因此效果十分的不錯(之因此說基本上保留,是由於在進行插值計算的時候,使用浮點數,老是會存在一點偏差,因此會損失一點信息)。不少遊戲,都是採用這樣的方法來進行紋理的縮放的。

 

Mipmap鏈

      在講解三線性紋理濾波採樣以前,先來說解下什麼是Mipmap,以及使用Mipmap來作什麼用途。

      咱們知道,在3D空間中,紋理圖老是要被縮放的,而咱們在本來紋理圖上進行採樣,並不老是那麼可靠。好比說,紋理圖的大小其實是128*128的尺寸。而咱們在3D程序中,咱們僅僅須要一個4*4的紋理圖就能夠了。若是,咱們在這個大圖上,獲取這個4*4的小圖的話,操做複雜,並且效果不理想。因此,若是,咱們可以預先使用這個大圖,來建立一些尺寸較小的圖,那麼在進行採樣的時候,咱們能夠選取,與須要的尺寸最接近的紋理圖來進行採樣。經過這樣的方式,不只可以提升效率,也能某種程度上改善效果。

      Mipmap的做用就是這樣的。在DirectX中,你加載紋理的時候,它老是爲你建立了Mipmap鏈。若是你加載的是一個128*128的紋理圖,那麼它會爲你建立一個64*64, 32*32, 16*16, 8*8, 4*4 , 2*2, 1*1的紋理圖。

      建立這些紋理圖的方法,就是須要進行採樣,一樣的,它是使用前面介紹的雙線性採樣方法進行採樣的。

      當在3D空間中,某一個三角形須要一個紋理圖的時候,咱們先來判斷,它最接近的紋理圖是哪個。好比說,它須要的其實是50*50的紋理圖,那麼咱們就會發現,使用64*64的紋理圖,來進行採樣,效果會更好。實際上,選擇哪個Mip等級,有不少不一樣的實現方法,我並不知道DirectX是使用哪一種方式的,可是,它的原理無外乎就是選取最接近該紋理的紋理等級。

 

三次線性濾波採樣

        好了,在講述完了上面的Mipmap以後,就能夠來說解如何實現三次線性濾波採樣了。通常來講,三次線性濾波,已是紋理濾波的極限了,沒有辦法作的比它更好了。實際上,這個濾波方式,就是結合了前面介紹的Mipmap和雙線性採樣理論來共同實現。

       咱們首先經過某種方法來獲取最終的Mipmap等級,多是經過面積計算,也多是經過其餘的方式來獲取,而最終獲取到的Mipmap等級值,會是像4.3這樣的帶有小數的值。這個小數的意思就是,咱們將要使用Mipmap等級爲4和5的這兩個紋理來進行紋理採樣,採樣的方法就是使用雙線性紋理濾波採樣來進行。經過這樣的方法,咱們可以獲得更加平滑的效果。

      實現方式,將不會以代碼的形式來提供給你們,若是讀者感興趣的話,能夠本身寫個軟引擎,而後測試一下這個算法。可是不要指望,在軟件引擎中大量的使用此種算法,這樣的算法消耗將是很是巨大的。只可以少許的使用。

 

轉自:http://dev.gameres.com/Program/Visual/3D/Bilinear.htm

 

紋理映射的雙線性插值濾波


  當你作紋理映射的時候,是否常常會注意到屏幕上顯示出的那些明顯鋸齒,並且你用的紋理像素化得太明顯了?如今,咱們將談論如何來解決這個問題,而咱們使用的方法就是對你的紋理進行濾波。下面咱們將介紹幾種經常使用的濾波方法,最後再詳細介紹雙線性插值濾波的具體實現。

Bi-linear Interpolation

  雙線性插值是經過對紋理中的相鄰像素進行處理來平滑掉屏幕輸出像素間的鋸齒的。使用雙線性插值會使屏幕輸出的圖像顯得更平滑。下面先來看看它的基本計算公式。
double texture[N][M];  // x = [0, N), y = [0, M)
double xReal;          // xReal = [0, N - 1]
double yReal;          // yReal = [0, M - 1]

int x0 = int(xReal), y0 = int(yReal);
double dx = xReal - x0, dy = yReal - y0,
omdx = 1 - dx, omdy = 1 - dy;

double bilinear = omdx * omdy * texture[x0][y0] +
omdx * dy * texture[x0][y0+1] +
dx * omdy * texture[x0+1][y0] +
dx * dy * texture[x0+1][y0+1];
  觀察這段公式,你會看出,咱們頗有效地使用了紋理座標的小數部分來對四個紋理中的相鄰像素進行插值。咱們按對應像素的距離來決定各個像素所佔的權重。也就是說,當紋理的U座標的小數部分增長時,左邊相鄰像素的權重就會減小,減小出來的權重會增長右邊的相鄰像素上去。對垂直方向的V座標的狀況也同此相似。

  在實際應用中,直接按這段公式來計算顯然會很慢,你能夠用定點整數和查表法來取消浮點和整型的混合運算以及去掉乘法。(提示:針對A、B兩種顏色的混合創建 x*A+(1-x)*B 的結果表)

Mip-Mapping

  我第一次看到Mip-mapping技術是在遊戲QUAKE裏,而如今這種技術早已經是隨處可見了。這種技術是由Williams在1983年發明的,「Mip」這個名稱起源於「multum in parvo」,大概就是在一小塊地方有不少東西的意思。

  具體說來,Mip-Mapping的思想就是構建一套紋理,總共須要大約1.3倍的內存。其中,每塊子紋理是經過對父紋理過濾而獲得,它的長和寬都是其父紋理的1/2,其面積爲父紋理的1/4。接下來,在應用的時候,你根據距離選取最合適的一塊來進行映射,實踐證實,這種技術雖然簡單,但對提升紋理映射的質量確實很是有效。

  經過Mip-Mapping,能夠爲較小的多邊形映射上面積較小的紋理,這對減小紋理的擾動大有好處。舉個例子,你有一塊256x256大小的紋理,當它開始向遠離觀察者的方向開始移動時,你會看到它開始閃爍和顫動。這種現象的出現是由於咱們把一大塊紋理映射到一個很小的區域而引發的。你可能在上一幀時,畫的是紋理中(50,20)處的像素,到了下一幀,卻畫的是紋理中(60,30)處的像素。若是這兩個像素相差很大,你就會觀察到前面所說的現象了。總的來講,這種劇烈的紋理座標的變化,會損害圖像的品質,而且影響CACHE的效率,而Mip-Mapping無疑是解決這個問題的好辦法。

Tri-linear Interpolation

  在介紹了雙線性插值和Mip-Mapping之後,該來說講三線性插值了。其實三線性插值也很簡單,它就是前兩種技術的結合。它在對Mip-Mapping的每塊紋理作雙線性插值的同時,還要對Mip-Mapping中相鄰的兩塊紋理按距離再作一次插值。既算出較大的一塊紋理上的某點雙線性插值像素值和較小的一塊紋理上的某點雙線性插值像素值,再按目標同兩塊紋理的距離作一次相似的插值。

  使用三線性插值,能夠消除Mip-Mapping裏紋理切換(既上一幀時用的是某個大小的一塊紋理,而下一幀時又換了一塊的狀況)時的忽然變化,從而能夠提供很平暢的高質圖像輸出。

  同前兩種技術相比,三線性插值的運算量很是大,目前只能依靠硬件來實現。

雙線性插值紋理映射的實現

  下面,咱們經過一段描述性代碼來簡單看看雙線性插值紋理映射是如何實現的。
 此處略去各類初始化代碼,直接觀察咱們最關心的部分 其中:U和V是16.16格式的定點整數 du和dv是浮點數 du = (U & 0xFFFF) / 65536.0 dv = (V & 0xFFFF) / 65536.0 invdu = 1.0 - du invdv = 1.0 - dv // 根據到相鄰四個像素的距離計算各自的權重 Weight1 = invdu*invdv Weight2 = invdu*dv Weight3 = du*invdv Weight4 = du*dv // 求得各個像素的RGB顏色份量 r00 = Texture[V >> 16][U >> 16].Red g00 = Texture[V >> 16][U >> 16].Green b00 = Texture[V >> 16][U >> 16].Blue r01 = Texture[(V >> 16) + 1][U >> 16].Red g01 = Texture[(V >> 16) + 1][U >> 16].Green b01 = Texture[(V >> 16) + 1][U >> 16].Blue r10 = Texture[V >> 16][(U >> 16) + 1].Red g10 = Texture[V >> 16][(U >> 16) + 1].Green b10 = Texture[V >> 16][(U >> 16) + 1].Blue r11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Red g11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Green b11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Blue // 按權重混合RGB顏色份量 Red = Weight1*r00 + Weight2*r01 + Weight3*r10 + Weight4*r11 Green = Weight1*g00 + Weight2*g01 + Weight3*g10 + Weight4*g11 Blue = Weight1*b00 + Weight2*b01 + Weight3*b10 + Weight4*b11 // 按最後求得的RGB顏色份量畫點 PutPixel(X, Y, Pack(Red, Green, Blue))
  這段代碼顯然未經優化(起碼不要去用那個PutPixel),若是你程序功力不夠,可能會沒法達到理想的優化目標,這時你能夠直接使用硬件去實現(新的3D硬件都能支援這些功能)。但我相信你在理解了雙線性插值濾波的思想之後,必定能觸類旁通,利用它爲你的遊戲圖像更添魅力。
相關文章
相關標籤/搜索