OpenGL 七 - OpenGL 紋理基礎與案例演示
紋理基初知識
一、紋理
1)無論是 tga 檔案還是 png/jpg 檔案,最終圖片檔案都是要歸結到點陣圖檔案去處理的。
紋理檔案 --> TGA檔案 --> OpenGL--> 點陣圖
iOS開發中 --> OpenGL ES --> png/jpg --> 點陣圖
2)原始影象資料:
影象儲存空間 = 影象高度 * 影象寬度 * 每個畫素的位元組數
二、相關函式
1)
// 改變畫素儲存⽅式
void glPixelStorei(GLenum pname,GLint param);
// 恢復畫素儲存⽅式
void glPixelStoref(GLenum pname,GLfloat param);
// 舉例 :
// 引數1: GL_UNPACK_ALIGNMENT 指定 OpenGL 如何從資料快取區中解包影象資料
// 引數2: 表示引數 GL_UNPACK_ALIGNMENT 設定的值
// GL_UNPACK_ALIGNMENT 指記憶體中每個畫素⾏起點的排列請求,
// 允許設定為1:byte排列列、2:排列為偶數byte的行、4:字word排列、8:行從雙位元組邊界開始
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
2)從顏色快取區 內容作為畫素圖 直接讀取
// 引數1:x, 矩形左下角的窗⼝座標
// 引數2:y, 矩形左下角的視窗座標
// 引數3:width, 矩形的寬,以畫素為單位
// 引數4:height, 矩形的高,以畫素為單位
// 引數5:format, OpenGL 的畫素格式,參考下面表格: OpenGL畫素表格
// 引數6:type, 解釋引數 pixels 指向的資料,告訴OpenGL 使用快取區中的什麼資料型別來儲存顏⾊分量,畫素資料的資料型別,參考表格:畫素資料的畫素型別
// 引數7:pixels, 指向圖形資料的指標
void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height, GLenum format, GLenum type,const void * pixels);
glReadBuffer(mode);// 指定讀取的快取
glWriteBuffer(mode);// 指定寫入的快取
3)載入紋理
void glTexImage1D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLint border,GLenum format,GLenum type,void *data);
void glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,void * data);
void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);
/*引數們:
* target:`GL_TEXTURE_1D`、`GL_TEXTURE_2D`、`GL_TEXTURE_3D`
* Level:指定所載入的 mip 貼圖層次。一般我們都把這個引數設定為0
* internalformat:每個紋理單元中儲存多少顏色成分
* width、height、depth 引數:指載入紋理的寬度、⾼度、深度。注意: 這些值必須是 2的整數次⽅ --> 這是因為 OpenGL 舊版本上的遺留下的⼀個要求,當然現在已經可以支援不是 2 的整數次方,但是開發者們還是習慣使⽤以2的整數次⽅去設定這些引數。
* border 引數:允許為紋理貼圖指定一個邊界寬度
* format、type、data 引數:與我們在講 glDrawPixels 函式對應的引數相同*/
4)更新紋理
void glTexSubImage1D(GLenum target, GLint level, GLint xOffset, GLsizei width, GLenum format, GLenum type, const GLvoid *data);
void glTexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *data);
void glTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, Glenum type, const GLvoid * data);
5)插⼊替換紋理
void glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizewidth);
void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yOffset, GLint x, GLint y,GLsizei width,GLsizei height);
void glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yOffset, GLint zOffset, GLint x,GLint y, GLsizei width, GLsizei height);
6)使⽤顏⾊快取區載入資料,形成新的紋理使⽤
void glCopyTexImage1D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLint border);
void glCopyTexImage2D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLsizei height,GLint border);
x,y 在顏⾊快取區中指定了開始讀取紋理資料的位置; 快取區裡的資料,是源快取區通過 glReadBuffer 設定的。
注意:不存在 glCopyTextImage3D ,因為我們⽆法從 2D 顏⾊快取區中獲取體積資料。
7)紋理物件
// 使⽤函式分配紋理物件
// 指定紋理物件的數量 和 指標 (指標指向⼀個⽆符號整形陣列,由紋理物件識別符號填充)
void glGenTextures(GLsizei n,GLuint * textTures);
// 繫結紋理狀態
// 引數target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
// 引數texture: 需要繫結的紋理物件
void glBindTexture(GLenum target,GLunit texture);
// 刪除繫結紋理物件
// 紋理物件 以及 紋理物件指標 —> 指標指向⼀個⽆符號整形陣列,由紋理物件識別符號填充
void glDeleteTextures(GLsizei n,GLuint *textures);
// 測試紋理物件是否有效
// 如果 texture 是⼀個已經分配空間的紋理物件,那麼這個函式會返回 GL_TRUE, 否則會返回 GL_FALSE。
GLboolean glIsTexture(GLuint texture);
8)設定紋理引數
glTexParameterf(GLenum target,GLenum pname,GLFloat param);
glTexParameteri(GLenum target,GLenum pname,GLint param);
glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);
glTexParameteriv(GLenum target,GLenum pname,GLint *param);
/* 引數1:target, 指定這些引數將要應⽤在哪個紋理模式上,⽐如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
引數2:pname,指定需要設定哪個紋理引數
引數3:param,設定特定的紋理引數的值*/
8.1)設定過濾方式 下圖,臨近過濾取值:+ 號所在位置的色值; 線性過濾取值:+ 號所在位置的 附近3個挨著的顏色的混合值。2種紋理過濾方式的比較:紋理放大縮小時,設定是使⽤線性過濾還是臨近過濾
臨近過濾-影象鋸齒;線性過濾-平滑柔焦 --> 此效果是指影象放大 or 縮小到一定程度的效果,正常圖片的顯示不會有問題的。
紋理引數設定函式
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 紋理放大時,使⽤線性過濾
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 紋理縮小時,使⽤線性過濾
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// 紋理放大時,使⽤臨近過濾
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);// 紋理縮小時,使⽤臨近過濾
8.2)設定環繞方式
設定環繞方式 API
/*
引數1:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
引數2:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,針對 s、t、r 座標 -- s -> x
引數3:GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE、GL_CLAMP_TO_BORDER
GL_REPEAT:OpenGL 在紋理理座標超過1.0的⽅方向上對紋理理進⾏行行重複;
GL_CLAMP:所需的紋理單元取⾃紋理邊界或 TEXTURE_BORDER_COLOR;
GL_CLAMP_TO_EDGE 環繞模式強制對範圍之外的紋理座標沿著合法的紋理單元的最後一⾏或者最後一列來進⾏取樣。
GL_CLAMP_TO_BORDER:在紋理座標在 0.0到1.0範圍之外的 只使⽤邊界紋理單元。邊界紋理單元是作為圍繞基本影象的 額外的行和列,並與基本紋理影象⼀起載入的。
*/
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
二、相關表格
1)OpenGL畫素表格:
2)畫素資料的畫素型別:
UNSIGNED_BYTE_3_3_2
UNSIGNED_BYTE_2_3_3_REV
指定分量 RGBA 的排列順序根據 format 引數確定。分量按照分量高位到低位排列。
三、紋理座標
頂點座標:x、y、z
紋理座標:s、t、r --> 其實就是對應的 x、y、z
紋理座標範圍是 0~1 之間,更多地是描述一種對映關係。紋理座標預設左下角為原點 (0,0)。圖示:
立方體:
四、案例
效果:
三角錐底部(2個三角形)的座標紋理對應關係:
其他剩餘三角形面同理。
主要程式碼:
1 /// 繪製金字塔 2 void MakePyramid(GLBatch& pyramidBatch) 3 { 4 /*1、通過pyramidBatch組建三角形批次 5 引數1:型別 6 引數2:頂點數 7 引數3:這個批次中將會應用1個紋理 8 注意:如果不寫這個引數,預設為0。 9 */ 10 pyramidBatch.Begin(GL_TRIANGLES, 18, 1); 11 12 /***前情匯入 13 14 2)設定紋理座標 15 void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t); 16 引數1:texture,紋理層次,對於使用儲存著色器來進行渲染,設定為0 17 引數2:s:對應頂點座標中的x座標 18 引數3:t:對應頂點座標中的y 19 (s,t,r,q對應頂點座標的x,y,z,w) 20 21 pyramidBatch.MultiTexCoord2f(0,s,t); 22 23 3)void Vertex3f(GLfloat x, GLfloat y, GLfloat z); 24 void Vertex3fv(M3DVector3f vVertex); 25 向三角形批次類新增頂點資料(x,y,z); 26 pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f); 27 28 */ 29 30 //塔頂 31 M3DVector3f vApex = { 0.0f, 1.0f, 0.0f }; 32 M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f }; 33 M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f }; 34 M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f }; 35 M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f }; 36 37 //金字塔底部 38 // 底部的四邊形 = 三角形X + 三角形Y 39 // 三角形X = (vBackLeft,vBackRight,vFrontRight) 40 // vBackLeft 41 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 42 pyramidBatch.Vertex3fv(vBackLeft); 43 44 // vBackRight 45 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 46 pyramidBatch.Vertex3fv(vBackRight); 47 48 // vFrontRight 49 pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f); 50 pyramidBatch.Vertex3fv(vFrontRight); 51 52 53 // 三角形Y =(vFrontLeft,vBackLeft,vFrontRight) 54 // vFrontLeft 55 pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f); 56 pyramidBatch.Vertex3fv(vFrontLeft); 57 58 //vBackLeft 59 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 60 pyramidBatch.Vertex3fv(vBackLeft); 61 62 //vFrontRight 63 pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f); 64 pyramidBatch.Vertex3fv(vFrontRight); 65 66 67 // 金字塔前面 68 //三角形:(Apex,vFrontLeft,vFrontRight) 69 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 70 pyramidBatch.Vertex3fv(vApex); 71 72 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 73 pyramidBatch.Vertex3fv(vFrontLeft); 74 75 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 76 pyramidBatch.Vertex3fv(vFrontRight); 77 78 //金字塔左邊 79 //三角形:(vApex, vBackLeft, vFrontLeft) 80 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 81 pyramidBatch.Vertex3fv(vApex); 82 83 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 84 pyramidBatch.Vertex3fv(vBackLeft); 85 86 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 87 pyramidBatch.Vertex3fv(vFrontLeft); 88 89 //金字塔右邊 90 //三角形:(vApex, vFrontRight, vBackRight) 91 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 92 pyramidBatch.Vertex3fv(vApex); 93 94 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 95 pyramidBatch.Vertex3fv(vFrontRight); 96 97 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 98 pyramidBatch.Vertex3fv(vBackRight); 99 100 //金字塔後邊 101 //三角形:(vApex, vBackRight, vBackLeft) 102 pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f); 103 pyramidBatch.Vertex3fv(vApex); 104 105 pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f); 106 pyramidBatch.Vertex3fv(vBackRight); 107 108 pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f); 109 pyramidBatch.Vertex3fv(vBackLeft); 110 111 //結束批次設定 112 pyramidBatch.End(); 113 } 114 115 /// 將TGA檔案載入為2D紋理。 116 //引數1:紋理檔名稱 117 //引數2&引數3:需要縮小&放大的過濾器模式設定 118 //引數4:紋理座標 環繞模式 119 bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) 120 { 121 GLbyte *pBits; 122 int nWidth, nHeight, nComponents; 123 GLenum eFormat; 124 125 //1、讀紋理位,讀取畫素 --> 將圖片讀取為 點陣圖 126 //引數1:紋理檔名稱 127 //引數2:檔案寬度地址 128 //引數3:檔案高度地址 129 //引數4:檔案元件地址 130 //引數5:檔案格式地址 131 //返回值:pBits,指向影象資料的指標 132 pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat); 133 134 if(pBits == NULL)// 讀取圖片資料失敗直接返回 false 135 return false; 136 137 //2、設定紋理引數 138 //引數1:紋理維度 139 //引數2:為 S/T 座標設定模式 140 //引數3:wrapMode,環繞模式 141 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode); 142 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode); 143 144 // z設定過濾方式:紋理放大縮小時怎麼顯示 145 //引數1:紋理維度 146 //引數2:過濾的放大縮小 147 //引數3: 縮小/放大過濾方式. 148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); 149 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); 150 151 152 //3.載入紋理 153 //引數1:紋理維度 154 //引數2:mip貼圖層次 155 //引數3:紋理單元儲存的顏色成分(從讀取畫素圖是獲得) 156 //引數4:載入紋理寬 157 //引數5:載入紋理高 158 //引數6:載入紋理的深度 159 //引數7:畫素資料的資料型別(GL_UNSIGNED_BYTE,每個顏色分量都是一個8位無符號整數) 160 //引數8:指向紋理影象資料的指標 161 glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, 162 eFormat, GL_UNSIGNED_BYTE, pBits); 163 164 //使用完畢釋放pBits 165 free(pBits); 166 167 //只有minFilter 等於以下四種模式,才可以生成Mip貼圖 168 //GL_NEAREST_MIPMAP_NEAREST具有非常好的效能,並且閃爍現象非常弱 169 //GL_LINEAR_MIPMAP_NEAREST常常用於對遊戲進行加速,它使用了高質量的線性過濾器 170 //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 過濾器在Mip層之間執行了一些額外的插值,以消除他們之間的過濾痕跡。 171 //GL_LINEAR_MIPMAP_LINEAR 三線性Mip貼圖。紋理過濾的黃金準則,具有最高的精度。 172 if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 173 minFilter == GL_LINEAR_MIPMAP_NEAREST || 174 minFilter == GL_NEAREST_MIPMAP_LINEAR || 175 minFilter == GL_NEAREST_MIPMAP_NEAREST) 176 //4.紋理生成所有的Mip層 177 //引數:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 178 glGenerateMipmap(GL_TEXTURE_2D); 179 180 return true; 181 } 182 183 // 初始化 184 void SetupRC() 185 { 186 //1. 187 glClearColor(0.7f, 0.7f, 0.7f, 1.0f ); 188 shaderManager.InitializeStockShaders(); 189 190 //2. 191 glEnable(GL_DEPTH_TEST); 192 193 //3. 194 //分配紋理物件 引數1:紋理物件個數,引數2:紋理物件指標 195 glGenTextures(1, &textureID); 196 197 //繫結紋理狀態 引數1:紋理狀態2D 引數2:紋理物件 198 glBindTexture(GL_TEXTURE_2D, textureID); 199 200 //將TGA檔案載入為2D紋理 --> LoadTGATexture:自定義方法 201 //引數1:紋理檔名稱 202 //引數2&引數3:需要縮小&放大的過濾器 203 //引數4:紋理座標環繞模式 204 LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE); 205 206 //4.創造金字塔 pyramidBatch --> MakePyramid:自定義方法 207 MakePyramid(pyramidBatch); 208 209 //5. 210 /**相機frame MoveForward(平移) 211 引數1:Z,深度(螢幕到圖形的Z軸距離) 212 */ 213 cameraFrame.MoveForward(-10); 214 } 215 216 // 渲染繪製 217 void RenderScene(void) 218 { 219 //1.顏色值&光源位置 220 static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f }; 221 static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f }; 222 223 //2.清理快取區 224 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 225 226 //3.當前模型視訊壓棧 227 modelViewMatrix.PushMatrix(); 228 229 //新增照相機矩陣 230 M3DMatrix44f mCamera; 231 //從camraFrame中獲取一個4*4的矩陣 232 cameraFrame.GetCameraMatrix(mCamera); 233 //矩陣乘以矩陣堆疊頂部矩陣,相乘結果儲存到堆疊的頂部 將照相機矩陣 與 當前模型矩陣相乘 壓入棧頂 234 modelViewMatrix.MultMatrix(mCamera); 235 236 //建立mObjectFrame矩陣 237 M3DMatrix44f mObjectFrame; 238 //從objectFrame中獲取矩陣,objectFrame儲存的是特殊鍵位的變換矩陣 239 objectFrame.GetMatrix(mObjectFrame); 240 //矩陣乘以矩陣堆疊頂部矩陣,相乘結果儲存到堆疊的頂部 將世界變換矩陣 與 當前模型矩陣相乘 壓入棧頂 241 modelViewMatrix.MultMatrix(mObjectFrame); 242 243 //4.繫結紋理,因為我們的專案中只有一個紋理。如果有多個紋理。繫結紋理很重要 244 glBindTexture(GL_TEXTURE_2D, textureID); 245 246 //5.紋理替換矩陣著色器 247 /* 248 引數1:GLT_SHADER_TEXTURE_REPLACE(著色器標籤) 249 引數2:模型檢視投影矩陣 250 引數3:紋理層 251 */ shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0); 252 253 //pyramidBatch 繪製 254 pyramidBatch.Draw(); 255 256 //模型檢視出棧,恢復矩陣(push一次就要pop一次) 257 modelViewMatrix.PopMatrix(); 258 259 //6.交換快取區 260 glutSwapBuffers(); 261 }