1. 程式人生 > >OGL(教程16)——基礎貼圖對映

OGL(教程16)——基礎貼圖對映

原文地址:http://ogldev.atspace.co.uk/www/tutorial16/tutorial16.html

背景知識:
貼圖的對映的意思是應用任何型別的圖到3D模型的多個面上。這個圖叫做紋理,它可以是任何東西。如磚頭、樹葉、貧瘠的土地,使用這些貼圖增加場景的逼真程度。比如,看下面的兩個圖:
在這裡插入圖片描述

為了使紋理對映正確,你需要做三件事情。載入一個圖片到OpenGL,提供頂點對應的紋理座標,執行紋理取樣操作,取樣的使用的紋理座標,以得到一個畫素的顏色。由於三角形是被縮放、旋轉、平移變換過的,最終才會被投影到螢幕上,而且在不同的攝像機視角來觀察會有不同的效果。GPU所需要做的是,使貼圖隨著頂點的移動而一起移動。這樣才能看起來逼真。為了實現這個,開發者需要提供一系列的座標,被稱之為紋理座標,每個頂點都有對應的紋理座標。由於GPU光柵化了三角形,它同樣在三角形的表面上對紋理座標也進行插值計算,然後在片段著色器中使用紋理座標也取樣貼圖。這個動作稱之為取樣,取樣的結果是一個單位紋理。這個單位紋理通常包含的是顏色,用來對一個畫素點進行著色。在接下來的章節中,我們會看到單溫紋理可以包含不同的資料,用來產生不同的效果。

OpenGL支援幾種不同型別的貼圖,如1D、2D、3D、立方體貼圖等,用以實現不同的技術。我們現在從2D開始。一個2D的紋理有寬度和高度,可以是任意指定的大小。把寬度和高度相乘將會得到這個紋理總的單位紋理總數。你怎樣指定一個頂點的紋理座標?這個紋理座標不是單位紋理在整個紋理貼圖上的座標。這樣做的侷限性很大,因為如果換一個不同大小的貼圖之後,還需要更新所有頂點的紋理座標,以作匹配這種新的貼圖。理想的情況是,換貼圖但是不要換紋理座標。因此,紋理座標被定義在紋理空間中,它的值都是在規格化的[0,1]範圍內。這就意味著,紋理座標通常是一個係數,然後把乘以紋理貼圖的寬度或者高度以得到單位紋理在整個紋理中的座標。比如,如果紋理座標是[0.5,0.1],然後貼圖的大小是寬度=320,高度=200,那麼單位紋理的的位置在 (160,20) ,計算方式為:(0.5 * 320 = 160 以及0.1 * 200 = 20)。

最常用的變換是使用u和v作為紋理空間的軸,這裡u代表了2D座標系中的x軸,而v代表了2D座標系中的y軸。OpenGL對u軸是從做到右,而v則是從下到上。如下圖所示:
在這裡插入圖片描述

上圖展示了紋理空間,左下角是原點。u是向右增長,而v向上增長。我們現在來考慮三角形,它的紋理座標如下所示:

在這裡插入圖片描述

加入我們使用這個紋理,那麼將會得到上面的圖,而如果經過多種變換之後,我們會得到如下的圖:
在這裡插入圖片描述

正如你看到的,紋理座標和頂點黏在一起的,這個頂點的一個屬性,在變換的時候不會改變。在插值的時候,多個畫素會得到相同的紋理座標。我們對物體進行旋轉、拉伸、擠壓,紋理也會隨之變換,但是也有技術讓紋理運動起來,通過改變紋理座標實現,但是目前我們保持紋理座標不變。

另外一個很重要的概念是,就是過濾。我們已經討論過怎樣使用紋理座標取樣一個紋理單元。單位紋理的位置通常是整數,而當紋理座標是浮點數,比如0到1之間的某個浮點數,被對映成了 (152.34,745.14),這種情況下,向下取整變為(152,745)。所以,這個工作會得到差不多的效果,但是在某些情況下會變得很糟糕。另外一個很好的解決方式是,把此點的紋理用2x2個整數單位的單位紋理進行差值,比如上面的用(152,745),(153,745), (152,744) 和 (153,744)來做差值得到最終的顏色,這個線性差值,使用的是 (152.34,745.14)到四個點的距離進行差值。距離越近的,對顏色貢獻越大,這種效果比之前的那個方法要好。

哪個紋理單位最終被選擇,通常叫做過濾。最簡單的方式是最近距離過濾,更復雜的方式是我們上面使用的線性過濾。近鄰顧慮的領啊為一個名字是點過濾。OpenGL提供了幾種不同型別的過濾方式,你可以選擇其中任一個。通常,提供更好效果的過濾方法,其耗時也會大。所以在效能和效果之間要做一個權衡。

現在,我們理解了OpenGL是如何使用紋理座標來取樣一個紋理。紋理在OpenGL中,蘊含著四個概念:紋理物件,紋理單元,取樣物件和shader中的取樣統一。這個不懂。

紋理物件包含了紋理的圖片的資料,單位是紋理單元。紋理可以是不同的型別,1D,2D不同維度。資料型別可以是不同的格式,如RGB、RGBA等。OpenGL指定原資料在記憶體中的起始位置,然後載入資料到GPU。還有很多其他的引數你可以控制,比如過濾的型別等。紋理物件也會關聯一個頂點緩衝物件。當建立了這個控制代碼之後,載入紋理資料和引數之後,你可以再不同的控制代碼之間做繫結,以得到不同的OpenGL狀態。你不再需要載入資料。從現在開始,OpenGL驅動的任務是確保在GPU渲染之前把資料載入好。

紋理物件不是直接繫結到shader,shader是真正做取樣的地方。但是,紋理單元所對應的索引需要傳遞給shader。這樣shader可以通過紋理單元來訪問對紋理物件。有可能有多個紋理單元,最多有幾個和顯示卡有關係。為了繫結一個紋理物件A到紋理單元0,你首先要確保單元0是可用的,然後才能繫結紋理物件A。你可以把紋理單元1變為可用,然後繫結另外一個或者相同的紋理物件給它。

每個紋理單元有幾種不同的紋理物件,這會造成一定的複雜度。這個叫做紋理物件的目標。當你繫結紋理物件到紋理單元之後,你要指定紋理物件是1D,2D還是其他型別。所以你可以把紋理物件A繫結到1D物件,紋理物件B繫結到2D目標。

取樣操作通常發生在片段著色器中,而且有一個單獨的函式來處理。取樣函式需要知道紋理單元來訪問,你可以在片段著色器中取樣多個紋理單元。這裡有一組特殊的統一變數來做這個事情,根據紋理目標,有sampler2D、samper2D、sampler3D、samplerCube等等。你要建立你想要的取樣器統一變數。

最後一個概念是取樣物件,不要把取樣統一變數和他弄混了。他們是單獨的實體。一個事情是,貼圖物件包含了貼圖資料,而且還有一個取樣的引數配置。這些引數是取樣的狀態。但是,你可以建立取樣物件,配置它一個取樣狀態,然後繫結到紋理單元。當你對取樣物件做這些事情,將會改變他的狀態。不要擔心——現在我們不需要使用取樣物件,我們現在只需要知道它的存在即可。

下圖總結了紋理相關的概念:
在這裡插入圖片描述

程式碼註釋:
OpenGL知道從記憶體中使用不同的格式載入紋理資料,但是不提供任何方法載入PNG和JPG格式圖片到記憶體。我們需要使用額外的庫。在ImageMagick中有很多選項,它是一個免費的庫,支援很多圖片格式,而且是跨平臺的。檢視連結http://ogldev.atspace.co.uk/instructions.html 來安裝。

大多數的紋理處理函式都封裝在下面的類中:

(ogldev_texture.h:27)

class Texture
{
public:
   Texture(GLenum TextureTarget, const std::string& FileName);

   bool Load();

   void Bind(GLenum TextureUnit);
};

當建立一個Texture物件時,你需要指定目標,我們使用GL_TEXTURE_2D還有檔名。之後我們可以使用Load函式。這個可能會失敗,如果檔案不存在,或者ImageMagic遇到了其他的錯誤。當你使用指定的紋理例項時,你需要繫結它到紋理單元。

(ogldev_texture.cpp:31)

try {
   m_pImage = new Magick::Image(m_fileName);
   m_pImage->write(&m_blob, "RGBA");
}
catch (Magick::Error& Error) {
   std::cout << "Error loading texture '" << m_fileName << "': " << Error.what() << std::endl;
   return false;
}