紋理基初知識
一、紋理
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, GLsize width);
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,設定特定的紋理參數的值*/
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)。圖示:
立方體:
四、案例
效果:
1)三角錐底部(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:border 159 //參數7、8:像素數據的數據類型(GL_UNSIGNED_BYTE,每個顏色分量都是一個8位無符號整數) 160 //參數9:指向紋理圖像數據的指針 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 }
2)Mip 貼圖
當不同大小圖的繪制時,縮放規則:每次縮放 1/2 直至最小的圖為 1:1 的紋理單元為止,會過濾(比例縮放)成多少份取決於圖片的大小 --> 提高渲染性能和顯示質量
2.1)設置 Mip 貼圖
// 設置Mip貼圖最基層
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
// 設置Mip貼圖最大層
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
--> 參數0: 第一層是0,第二層 1 ... ... 以此類推。
2.2)mip 貼圖的過濾方式
上面代碼 204 行:LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);// GL_LINEAR_MIPMAP_NEAREST --> 選擇最鄰近mip層,並進行線性過濾。
表格中后 4 種方式方式才可生成 mip 貼圖。代碼示例:上面的‘主要代碼’167行處。