系列推薦文章:
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定儲存著色器
OpenGL/OpenGL ES入門:影象渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 常用API解析
OpenGL/OpenGL ES入門: 紋理應用 - 紋理座標及案例解析(金字塔)
紋理應用
在上一篇文章OpenGL/OpenGL ES 紋理初探 - 常用API解析中,我們講述了紋理相關常用的API。載入紋理只是在幾何圖形上應用紋理的第一步。最低限度我們必須同時提供紋理座標,並設定紋理座標環繞模式和紋理過濾。最後,我們可以選擇對紋理進行Mip
貼圖,以提高紋理貼圖效能和/或視覺質量。
紋理座標
總體上說,通過為每個頂點指定一個紋理座標而直接在幾何圖形上進行紋理貼圖的。紋理座標要麼是指定為著色器的一個屬性,要麼通過演算法計算出來。
紋理貼圖中的紋理單元是作為一個更加抽象(經常是浮點值)的紋理座標,而不是作為記憶體位置(在畫素圖中則是這樣)進行定址的。典型情況下,紋理座標是0.0~1.0之間的浮點值指定的。
紋理座標命名為s、t、r和q(與頂點座標x、y、z和w類似)
,支援從一維到三維的紋理座標,並且可以選擇一種對座標進行縮放的方法。
q
座標對於幾何圖形座標w
。這是一個縮放因子,作用於其他紋理座標。也就是說,實際上所使用的紋理座標是s/q、t/q和r/q
。在預設情況下,q
設定為1.0。
著重瞭解2D紋理座標,在x
和y
軸上,範圍為0~1之間。使用紋理座標獲取紋理顏色叫做採用(Sampling
)。
紋理座標起始於(0,0),也就是紋理圖片的左下角,終止於(1,1)。即紋理圖片的右上角
通過下圖,體會一下紋理座標的對映:
金字塔案例
下面將通過金字塔案例講解一下紋理的使用,效果圖如下:
SetupRC函式
設定背景色,初始化等
void SetupRC()
{
// 設定背景色
glClearColor(0.7, 0.7, 0.7, 1.0);
// 初始化shaderManager
shaderManager.InitializeStockShaders();
// 開啟深度測試
glEnabel(GL_DEPTH_TEST);
// 分配紋理物件
/*
引數1: 紋理物件個數
引數2: 紋理物件指標
*/
glGenTextures(1, &textureID);
// 繫結紋理
/*
引數1: 紋理狀態2D
引數2: 紋理物件
*/
glBindTexture(GL_TEXTURE_2D, textureID);
// 將TGA檔案載入為2D紋理
/*
引數1: 紋理檔名稱
引數2和引數3: 需要的縮小和放大過濾器
引數4: 紋理座標環繞模式
*/
LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
// 創造金字塔pyramidBatch
MakePyramid(pyramidBatch);
// 相機frame MoveForward(平移)
/*
引數1:Z,深度(螢幕到圖形的Z軸距離)
*/
cameraFrame.MoveForward(-10);
}
複製程式碼
載入紋理檔案LoadTGATexture
// 將TGA檔案載入為2D紋理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
//1、讀紋理位,讀取畫素
/*
引數1:紋理檔名稱
引數2:檔案寬度地址
引數3:檔案高度地址
引數4:檔案元件地址
引數5:檔案格式地址
返回值:pBits,指向影象資料的指標
*/
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(pBits == NULL)
return false;
//2、設定紋理引數
/*
引數1:紋理維度
引數2:為S/T座標設定模式
引數3:wrapMode,環繞模式
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
/*
引數1:紋理維度
引數2:線性過濾
引數3:wrapMode,過濾模式
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
//3.載入紋理
/*
引數1:紋理維度
引數2:mip貼圖層次
引數3:紋理單元儲存的顏色成分(從讀取畫素圖是獲得)
引數4:載入紋理寬
引數5:載入紋理高
引數6:載入紋理的深度
引數7:畫素資料的資料型別(GL_UNSIGNED_BYTE,每個顏色分量都是一個8位無符號整數)
引數8:指向紋理影象資料的指標
*/
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
//使用完畢釋放pBits
free(pBits);
//4.載入Mip,紋理生成所有的Mip層
//引數:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
複製程式碼
繪製金字塔(獲取定點、紋理座標)
-------- 前情匯入 ---------
1、 設定法線
void Normal3f(GLfloat x, GLfloat y, GLfloat z);
Normal3f: 新增一個表面發現(法線座標與Vertex頂點座標中的Y軸一致)
表面法線是有方向的向量,代表表面或頂點面對的方向(相反的方向)。在多數的光照模式下必須使用
2、 設定紋理座標
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
引數1:texture,紋理層次,對於使用儲存著色器來進行渲染,設定為0
引數2:s: 對應頂點座標中的x座標
引數3:t: 對應頂點座標中的y
(s,t,r,q對應頂點座標的x,y,z,w)
pyramidBatch.MultiTexCoord2f(0,s,t);
3、 設定頂點座標
void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
void Vertex3fv(M3DVector3f vVertex);
向三角形批次類新增頂點資料(x,y,z);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
4、獲取從三點找到一個座標(三點確定一個面)
void m3dFindNormal(result, point1, point2, point3);
引數1:結果
引數2~4:3個頂點資料
複製程式碼
金字塔座標解析:
在座標系中繪製一個金字塔,座標原點位於金字塔中心
頂點座標
塔頂座標(0.0, 1.0, 0.0)
vBackLeft(-1.0, -1.0, -1.0)
vBackRight(1.0. -1.0, -1.0)
vFrontRight(1.0, -1.0, 1.0)
vFrontLeft(-1.0, -1.0, 1.0)
看下面圖片描述了金字塔底部兩個三角形的紋理座標
紋理座標
三角形A紋理座標
vBackLeft(0.0, 0.0, 0.0)
vBackRight(0.0, 1.0, 0.0)
vFrontRight(0.0, 1.0, 1.0)三角形B紋理座標
vFrontLeft(0.0, 0.0, 1.0)
vBackLeft(0.0, 0.0, 0.0)
vFrontRight(0.0, 1.0, 1.0)
//繪製金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
// 通過pyramidBatch組建三角形批次
/*
引數1:型別
引數2:頂點數
引數3:這個批次中將會應用1個紋理
注意:如果不寫這個引數,預設為0,表示應用1個紋理
*/
pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
//塔頂
M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f };
M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f };
M3DVector3f n;
//金字塔底部
//底部的四邊形 = 三角形A + 三角形B
//三角形A = (vBackLeft, vBackRight, vFrontRight)
//1.找到三角形A 法線
m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
//vBackLeft
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vBackRight
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//vFrontRight
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//三角形B =(vFrontLeft,vBackLeft,vFrontRight)
//1.找到三角形B 法線
m3dFindNormal(n, vFrontLeft, vBackLeft, vFrontRight);
//vFrontLeft
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//vBackLeft
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//vFrontRight
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontRight);
// 金字塔前面
//三角形:(Apex,vFrontLeft,vFrontRight)
m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
//金字塔左邊
//三角形:(vApex, vBackLeft, vFrontLeft)
m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
//金字塔右邊
//三角形:(vApex, vFrontRight, vBackRight)
m3dFindNormal(n, vApex, vFrontRight, vBackRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//金字塔後邊
//三角形:(vApex, vBackRight, vBackLeft)
m3dFindNormal(n, vApex, vBackRight, vBackLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackLeft);
//結束批次設定
pyramidBatch.End();
}
複製程式碼
RenderScene函式
void RenderScene(void)
{
//1.顏色值&光源位置
static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f };
static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f };
//2.清理快取區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//3.當前模型視訊壓棧
modelViewMatrix.PushMatrix();
//新增照相機矩陣
M3DMatrix44f mCamera;
//從camraFrame中獲取一個4*4的矩陣
cameraFrame.GetCameraMatrix(mCamera);
//矩陣乘以矩陣堆疊頂部矩陣,相乘結果儲存到堆疊的頂部 將照相機矩陣 與 當前模型矩陣相乘 壓入棧頂
modelViewMatrix.MultMatrix(mCamera);
//建立mObjectFrame矩陣
M3DMatrix44f mObjectFrame;
//從objectFrame中獲取矩陣,objectFrame儲存的是特殊鍵位的變換矩陣
objectFrame.GetMatrix(mObjectFrame);
//矩陣乘以矩陣堆疊頂部矩陣,相乘結果儲存到堆疊的頂部 將世界變換矩陣 與 當前模型矩陣相乘 壓入棧頂
modelViewMatrix.MultMatrix(mObjectFrame);
//4.繫結紋理,因為我們的專案中只有一個紋理。如果有多個紋理。繫結紋理很重要
glBindTexture(GL_TEXTURE_2D, textureID);
/*5.點光源著色器
引數1:GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF(著色器標籤)
引數2:模型檢視矩陣
引數3:投影矩陣
引數4:視點座標系中的光源位置
引數5:基本漫反射顏色
引數6:圖形顏色(用紋理就不需要設定顏色。設定為0)
*/
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightPos, vWhite, 0);
//pyramidBatch 繪製
pyramidBatch.Draw();
//模型檢視出棧,恢復矩陣(push一次就要pop一次)
modelViewMatrix.PopMatrix();
//6.交換快取區
glutSwapBuffers();
}
複製程式碼
ShutdownRC函式
void ShutdownRC(void)
{
// 清理…例如刪除紋理物件
glDeleteTextures(1, &textureID);
}
複製程式碼
SpecialKeys函式
void SpecialKeys(int key, int x, int y)
{
if(key == GLUT_KEY_UP)
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_DOWN)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_LEFT)
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
if(key == GLUT_KEY_RIGHT)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
glutPostRedisplay();
}
複製程式碼
ChangeSize函式
void ChangeSize(int w, int h)
{
//1.設定視口
glViewport(0, 0, w, h);
//2.建立投影矩陣
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
//viewFrustum.GetProjectionMatrix() 獲取viewFrustum投影矩陣
//並將其載入到投影矩陣堆疊上
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//3.設定變換管道以使用兩個矩陣堆疊(變換矩陣modelViewMatrix ,投影矩陣projectionMatrix)
//初始化GLGeometryTransform 的例項transformPipeline.通過將它的內部指標設定為模型檢視矩陣堆疊 和 投影矩陣堆疊例項,來完成初始化
//當然這個操作也可以在SetupRC 函式中完成,但是在視窗大小改變時或者視窗建立時設定它們並沒有壞處。而且這樣可以一次性完成矩陣和管線的設定。
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
複製程式碼
本篇主要描述了關於紋理方面的程式碼,後面的幾個函式RenderScene、SpecialKeys、ChangeSize等
在前幾篇文章中都詳細介紹過,所以這裡就一筆帶過。
下面整理了一張流程圖,僅供大家參考: