渲染世界的OPENGL紋理進階-矩形及立方體貼圖
1.矩形紋理
對於二維紋理影象來說,另一個有用的選項是紋理目標:GL_TEXTURE_RECTANGLE.
紋理特點:不能進行MIP貼圖,意味著我們只能夠載入glTexImage2D的第0層。 紋理座標不是標準化的。這就意味著紋理座標實際上是對畫素定址,而不是從0到1的範圍覆蓋影象的。
注意紋理座標不能重複,並且不能支援壓縮。
例如:紋理座標(5,19)實際上是影象中從左起6個畫素以及從上面起第20個畫素。
載入矩形紋理
gltWriteTGA函式將螢幕影象儲存為一個Targa檔案。
我們必須使用GL_NEAREST或者GL_LINEAR過濾器模式。
//前後的對比,一個是載入2D紋理,一個是載入矩形紋理
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 讀入紋理位
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL )
return false;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1 );
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
//用於檢查MIP貼圖紋理過濾器
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
bool LoadTGATextureRect(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 讀入紋理位
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
//對紋理屬性進行設定
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, magFilter);
//設定畫素的儲存格式
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//載入2D影象到矩形紋理
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
return true;
}
注意上面載入紋理的過程,基本上也就這樣:
讀入紋理位
設定紋理引數
設定畫素儲存模式
載入紋理
檢驗mip貼圖紋理過濾器(根據紋理情況而定)
下面我們在主程式碼中看一看矩形紋理是怎麼設定的:
首先是在SetUpRC當中對矩形紋理的設定。
//對矩形紋理的設定
int x = 500;
int y = 155;
int width = 300;
int height = 155;
logoBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
//注意紋理座標和實際座標的初始點完全不一致
// 左上角的座標設定
logoBatch.MultiTexCoord2f(0, 0.0f, height);
logoBatch.Vertex3f(x, y, 0.0);
// 左下角的座標設定
logoBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
logoBatch.Vertex3f(x, y - height, 0.0f);
// 右下角的座標設定
logoBatch.MultiTexCoord2f(0, width, 0.0f);
logoBatch.Vertex3f(x + width, y - height, 0.0f);
// 右上角的座標設定
logoBatch.MultiTexCoord2f(0, width, height);
logoBatch.Vertex3f(x + width, y, 0.0f);
logoBatch.End();
// 生成紋理貼圖
glGenTextures(4, uiTextures);
// Load the Marble
glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
// Load Mars
glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// Load Moon
glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// 繫結紋理
glBindTexture(GL_TEXTURE_RECTANGLE, uiTextures[3]);
//並且使用寫好的載入紋理的函式
LoadTGATextureRect("OpenGL-Logo.tga", GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE);
//使用對應的渲染器進行渲染
rectReplaceShader = gltLoadShaderPairWithAttributes("RectReplace.vp", "RectReplace.fp",
2, GLT_ATTRIBUTE_VERTEX, "vVertex", GLT_ATTRIBUTE_TEXTURE0, "vTexCoord");
//得到統一值
locRectMVP = glGetUniformLocation(rectReplaceShader, "mvpMatrix");
locRectTexture = glGetUniformLocation(rectReplaceShader, "rectangleImage");
就像上面所示的,主要是設定好矩形紋理的座標、載入紋理以及對渲染器進行設定。最後得到對渲染器當中統一值的控制。
// 建立一個矩陣,不用每一幀都計算的。
// 這個程式的中心目的是,在螢幕右下角顯示OPENGL的標誌
//在螢幕空間進行2D繪製的時候,普遍的一個做法是建立一個和螢幕大小
//匹配的正投影矩陣。我們選擇讓座標系和螢幕上的紋理匹配。但是
//是將原點設定到左下角。(紋理座標的原點)
M3DMatrix44f mScreenSpace;
m3dMakeOrthographicMatrix(mScreenSpace, 0.0f, 800.0f, 0.0f, 600.0f, -1.0f, 1.0f);//建立一個正交矩陣
// 注意:開啟混合模式,關閉深度測試。
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
//對rectReplaceShader當中的統一值進行設定。
glUseProgram(rectReplaceShader);
glUniform1i(locRectTexture, 0);
glUniformMatrix4fv(locRectMVP, 1, GL_FALSE, mScreenSpace);
glBindTexture(GL_TEXTURE_RECTANGLE, uiTextures[3]);
logoBatch.Draw();
在實際渲染當中的程式碼。
#version 140
// 輸入每個頂點的座標以及紋理座標
in vec4 vVertex;
in vec2 vTexCoord;
//模型投影矩陣
uniform mat4 mvpMatrix;
// 傳遞給片段著色器程式
smooth out vec2 vVaryingTexCoord;
void main(void)
{
// 僅僅傳遞一下
vVaryingTexCoord = vTexCoord;
// 集合變換
gl_Position = mvpMatrix * vVertex;
}
頂點著色器程式碼如上。
#version 140
out vec4 vFragColor;
//統一值的矩形取樣紋理
uniform sampler2DRect rectangleImage;
//由頂點著色器程式傳遞的紋理座標
smooth in vec2 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(rectangleImage, vVaryingTexCoord);
}
片段渲染器程式碼。
2.立方體貼圖
立方體貼圖是作為一個單獨的紋理物件看待的,但是它由組成立方體的6個面的6個正方形(必須是正方形!!!)的2D影象組成的。立方體貼圖的應用範圍包括:3D光線貼圖、反射和高精度環境貼圖。
實質上一個立方體貼圖是投影到一個物件上的,就像這個立方體貼圖是包圍這個物件一樣。
(1)載入立方體貼圖
立方體貼圖有以下六個列舉值,這些值可以傳遞到glTexImage2D:
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
這些常量展示了包圍被貼圖物體立方體表面的場景座標方向。
在實際使用的時候,在全域性變數當中如下宣告:
// Six sides of a cube map
const char *szCubeFaces[6] = { "pos_x.tga", "neg_x.tga", "pos_y.tga", "neg_y.tga", "pos_z.tga", "neg_z.tga" };
GLenum cube[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };
立方體貼圖最普遍的用法就是建立一個反應它周圍映象的物件。一個立方體貼圖被應用到一個球體上,建立了鏡面表面的外觀,並且同樣的立方體貼圖也應用到天空盒上面,這個天空盒建立了反射後的背景。
天空盒使用GLTools當中的gltMakeCube進行繪製,這個函式所做的只是用組成一個指定半徑的立方體的三角形來填充GLBatch容器。
gltMakeCube(cubeBatch, 20);
//選擇一個在每個方向到原點距離都為20個單位長度的立方體。
這個函式將2D紋理座標分配給GLT_ATTRIBUTE_TEXTURE0屬性槽當中。這樣在立方體的每個表面上都會應用一個2D影象。不過這樣做不能滿足對於立方體貼圖的要求,我們需要的是代表一個向量的3D紋理,沿著這個向量在在這個立方體貼圖上進行紋理單元取樣。GLBatch只支援2D紋理,所以它在盒子的外面是無法使用的。
解決辦法就是:編寫一個自定義的頂點著色器,用它為我們計算紋理座標。
實際上,這樣只是簡單第指定立方體的每個角在頂點空間中也是一個從立方體的中心指向這個位置的向量。我們要做的就是對這個向量進行標準化。而我們已經有了一個現成的立方體貼圖座標。
頂點著色器
#version 130
// 輸入每個點的位置
in vec4 vVertex;
uniform mat4 mvpMatrix; // Transformation matrix
// 傳遞給片段程式的紋理座標
varying vec3 vVaryingTexCoord;
void main(void)
{
//規範化向量,傳遞紋理座標
vVaryingTexCoord = normalize(vVertex.xyz);
// Don't forget to transform the geometry!
gl_Position = mvpMatrix * vVertex;
}
片段著色器
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
varying vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord);
}
我們在立方體貼圖上使用mip貼圖的時候,沿著兩個面結合的邊緣常常會出現裂縫。OPENGL內部會調整自己的過濾規則,啟用GL_TEXTURE_CUBE_MAP_SEAMLESS時幫助消除這些縫隙。
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
(2)建立反射
首先必須使用表面法線和指向頂點的向量在著色器中建立一個視覺座標系中的反射向量。另外,為了獲得一個真實的反射,還要考慮相機的方向。從GLFrame類中提取照相機的旋轉矩陣並且進行轉置。然後將其作為統一值,與另外一個變換矩陣一起提供給著色器。(用來對前述的反射向量進行旋轉,這個反射香辣實際上就是立方體紋理座標)一起提供給著色器。
頂點著色器
#version 130
// Incoming per vertex... position and normal
in vec4 vVertex;
in vec3 vNormal;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
uniform mat4 mInverseCamera;
// 傳遞給片段程式的紋理座標
smooth out vec3 vVaryingTexCoord;
void main(void)
{
// 視覺空間中的法線
vec3 vEyeNormal = normalMatrix * vNormal;
// 視覺空間中的頂點位置
vec4 vVert4 = mvMatrix * vVertex;
vec3 vEyeVertex = normalize(vVert4.xyz / vVert4.w);
//反射向量獲取
vec4 vCoords = vec4(reflect(vEyeVertex, vEyeNormal), 1.0);
// 翻轉照相機進行旋轉
vCoords = mInverseCamera * vCoords;
vVaryingTexCoord.xyz = normalize(vCoords.xyz);
// 幾何圖形進行變換
gl_Position = mvpMatrix * vVertex;
}
片段程式使用插值立方體貼圖紋理座標對立方體貼圖進行取樣並且將其應用到片段上。
片段程式:
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
smooth in vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord.stp);
}