還有兩種紋理生成模式未介紹,GL_REFLECTION_MAP和GL_NORMAL_MAP,這兩種模式需要用到新的紋理目標:立方體貼圖。一個立方體貼圖被當做一個紋理來看待,它由六個正方形的2D圖像(必須是正方形)來組成立方體的六個面。下圖展示了cubemap示例的立方體的六個面:
這六個面分別是-X,+X,-Y,+Y,-Z,+Z.然後我們使用GL_REFLECTION_MAP的模式來生成紋理,能夠制造一個真實的表面的倒影。
立方體貼圖有六個新的值作為參數傳給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.
例如我們要加載正X方向的貼圖,如下所示:
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pImage);
在cubemap示例中,我們把立方體的六個圖像的路徑保存到一個數組中,然後循環加載這六個面的紋理貼圖:
//立方體貼圖的紋理坐標與一般的3D紋理坐標不同,S,T和R紋理坐標代表著一個從立方體貼圖中心出發的有符號向量,這個向量會與立方體的六個面中的一個相交,然後對相交的紋理單元進行采樣。
立方體貼圖常用於創建一個對周圍景象進行反射的物體。下面的代碼創建一個天空盒,一個有效的天空盒包含了六個面,從盒子的中心出發朝六個方向看去可以看到六張圖像。天空盒是有六個正方形構成的,每個面使用glTexCoord3f來手工設置紋理坐標。
glPushMatrix();
//手動指定紋理坐標
glBegin(GL_QUADS);
//-x
glTexCoord3f(-1.0f, 1.0f, -1.0f);
glVertex3f(-fExtent, fExtent, -fExtent);
glTexCoord3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-fExtent, fExtent, fExtent);
glTexCoord3f(-1.0f, -1.0f, 1.0f);
glVertex3f(-fExtent, -fExtent, fExtent);
glTexCoord3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-fExtent, -fExtent, -fExtent);
//+x
glTexCoord3f(1.0f, -1.0f, -1.0f);
glVertex3f(fExtent, -fExtent, -fExtent);
glTexCoord3f(1.0f, -1.0f, 1.0f);
glVertex3f(fExtent, -fExtent, fExtent);
glTexCoord3f(1.0f, 1.0f, 1.0f);
glVertex3f(fExtent, fExtent, fExtent);
glTexCoord3f(1.0f, 1.0f, -1.0f);
glVertex3f(fExtent, fExtent, -fExtent);
//+y
glTexCoord3f(-1.0f, 1.0f, -1.0f);
glVertex3f(-fExtent, fExtent, -fExtent);
glTexCoord3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-fExtent, fExtent, fExtent);
glTexCoord3f(1.0f, 1.0f, 1.0f);
glVertex3f(fExtent, fExtent, fExtent);
glTexCoord3f(1.0f, 1.0f, -1.0f);
glVertex3f(fExtent, fExtent, -fExtent);
//-y
glTexCoord3f(1.0f, -1.0f, -1.0f);
glVertex3f(fExtent, -fExtent, -fExtent);
glTexCoord3f(1.0f, -1.0f, 1.0f);
glVertex3f(fExtent, -fExtent, fExtent);
glTexCoord3f(-1.0f, -1.0f, 1.0f);
glVertex3f(-fExtent, -fExtent, fExtent);
glTexCoord3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-fExtent, -fExtent, -fExtent);
//-z
glTexCoord3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-fExtent, -fExtent, -fExtent);
glTexCoord3f(1.0f, -1.0f, -1.0f);
glVertex3f(fExtent, -fExtent, -fExtent);
glTexCoord3f(1.0f, 1.0f, -1.0f);
glVertex3f(fExtent, fExtent, -fExtent);
glTexCoord3f(-1.0f, 1.0f, -1.0f);
glVertex3f(-fExtent, fExtent, -fExtent);
//+z
glTexCoord3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-fExtent, fExtent, fExtent);
glTexCoord3f(1.0f, 1.0f, 1.0f);
glVertex3f(fExtent, fExtent, fExtent);
glTexCoord3f(1.0f, -1.0f, 1.0f);
glVertex3f(fExtent, -fExtent, fExtent);
glTexCoord3f(-1.0f, -1.0f, 1.0f);
glVertex3f(-fExtent, -fExtent, fExtent);
glEnd();
glPopMatrix();
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
DrawSkyBox();
然後畫一個反射周圍環境的球體,開啟紋理坐標自動生成:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
為了制造更加真實的反射效果,我們需要考慮照相機的方向。我們可以從照相機類中獲得照相機的旋轉矩陣,並進行反轉,然後在應用立方體紋理貼圖之前,先把它乘以紋理矩陣。沒有旋轉紋理坐標,立方體貼圖將無法正確地反射周圍的環境(反射的是固定的圖像不隨著照相機的旋轉而變化)。因為gltDrawSphere函數並不影響模型視圖矩陣,所以我們可以使矩陣模式為GL_TEXTURE(紋理模式)直到我們畫完球體後再恢復到原始狀態。代碼如下:
//繪制球體效果如下(移動照相機,並旋轉可以看到不同的反射面)(code):
現代的OpenGL硬件實現都支持把多個紋理應用到幾何圖形上。我們可以通過GL_MAX_TEXTURE_UNITS檢查有多少個紋理單元是可用的:
GLint iUnits;
glGetIntergv(GL_MAX_TETURE_UNITS, &iUnits);
可用的紋理為從基礎的紋理單元(GL_TEXTURE0)到最大的紋理單元(GL_TEXTUREn)(這裡的n代表紋理單元的下標)。每個紋理單元有自己的紋理環境狀態(即每個紋理單元都可通過glTexEnv設置自己的紋理環境),紋理坐標生成狀態(glTexGen),紋理矩陣狀態,紋理的啟用狀態和紋理過濾器等。
紋理結合的過程大致如下:
首先從圖形中得到了片段的顏色值作為輸入,然後和被應用到圖元上的第一個紋理上對應的顏色值進行結合作為輸出。把之前的輸出作為輸入,再與第二個紋理的顏色進行結合,如此循環到最後一個被啟用的紋理單元為止。
默認情況下,第一個紋理單元是激活的紋理單元。除了glTexCoord之外的所有紋理命令,都只影響當前激活的紋理單元。我們可以通過調用glActiveTexture參數為GL_TEXTUREn來激活相應的紋理單元(紋理的下標是從0開始)。例如:我們激活第二個紋理,並啟用2D紋理:
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
相反地禁用則如下:
glDisable(GL_TEXTURE_2D);
glActiveTexutre(GL_TEXTURE0);
所有的紋理函數調用glTexParameter, glTexEnv,glTexGen,glTexImage和glBindTexture都只對當前激活的紋理單元有效。當圖形被渲染時,被啟用的紋理單元將被應用。
當我們使用glTexCoord指定紋理坐標的時候,這個紋理坐標是針對GL_TEXTURE0設置的。如果我們想為其他的紋理單元設置紋理坐標可以通過glMultiTexCoord來設置:
glMultiTexCoord1f(GLenum texUnit, GLfloat s);
glMultiTexCoord2f(Glenum texUnit, GLfloat s, GLfloat t);
glMultiTexCoord3f(GLenum texUnit, GLfloat s, GLfloat t, GLfloat r);
其中texUnit為GL_TEXTUREn. 也有相應的不同類型的版本。當然我們也可以用自動生成紋理坐標的方式。
在之前的CUBEMAP例子的基礎上做一些更改,我們把cubemap的紋理作為第二個紋理即GL_TEXTURE1。第一個紋理是有污點的紋理。然後,讓立方體貼圖紋理和這個污點紋理相乘,就能得到如下的效果:
完整代碼示例如下:
#include "gltools.h"#include "math3d.h"#include "glframe.h"#define COLORMAP 0#define CUBEMAP 1#define TEXTURENUM 2//紋理對象GLuint textureObj[TEXTURENUM] = {0,0};//紋理路徑數組static const char *szCubeFile[] = {"..\\pos_x.tga", "..\\neg_x.tga","..\\pos_y.tga", "..\\neg_y.tga","..\\pos_z.tga", "..\\neg_z.tga"};//立方體貼圖static GLenum cube[] = {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};static GLfloat fExtent = 10.0f;static GLFrame camra;void DrawSkyBox() { glPushMatrix(); glBegin(GL_QUADS); //-x glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, -1.0f); glVertex3f(-fExtent, fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, 1.0f); glVertex3f(-fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, 1.0f); glVertex3f(-fExtent, -fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, -1.0f); glVertex3f(-fExtent, -fExtent, -fExtent); //+x glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, -1.0f); glVertex3f(fExtent, -fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, 1.0f); glVertex3f(fExtent, -fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, 1.0f); glVertex3f(fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, -1.0f); glVertex3f(fExtent, fExtent, -fExtent); //+y glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, -1.0f); glVertex3f(-fExtent, fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, 1.0f); glVertex3f(-fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, 1.0f); glVertex3f(fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, -1.0f); glVertex3f(fExtent, fExtent, -fExtent); //-y glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, -1.0f); glVertex3f(fExtent, -fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, 1.0f); glVertex3f(fExtent, -fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, 1.0f); glVertex3f(-fExtent, -fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, -1.0f); glVertex3f(-fExtent, -fExtent, -fExtent); //-z glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, -1.0f); glVertex3f(-fExtent, -fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, -1.0f); glVertex3f(fExtent, -fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, -1.0f); glVertex3f(fExtent, fExtent, -fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, -1.0f); glVertex3f(-fExtent, fExtent, -fExtent); //+z glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, 1.0f); glVertex3f(-fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, 1.0f, 1.0f); glVertex3f(fExtent, fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, 1.0f, -1.0f, 1.0f); glVertex3f(fExtent, -fExtent, fExtent); glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, 1.0f); glVertex3f(-fExtent, -fExtent, fExtent); glEnd(); glPopMatrix(); }void SetupRC() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLint iWidth, iHeight, iComponents; GLenum eFormat; glGenTextures(TEXTURENUM, textureObj); //設置立方體紋理對象狀態 glBindTexture(GL_TEXTURE_CUBE_MAP, textureObj[CUBEMAP]); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); for (int i = 0; i < 6; ++i) { glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);void *pImage = gltLoadTGA(szCubeFile[i], &iWidth, &iHeight, &iComponents, &eFormat);if (pImage) { glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pImage); free(pImage); } } //設置污點紋理對象狀態 glBindTexture(GL_TEXTURE_2D, textureObj[COLORMAP]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); void *pImage = gltLoadTGA("..\\tarnish.tga", &iWidth, &iHeight, &iComponents, &eFormat); if (pImage) { glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pImage); free(pImage); } //激活紋理單元0,並啟用2D紋理,設置它的紋理和紋理環境, glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, textureObj[COLORMAP]); glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV, GL_DECAL); //激活紋理單元1,啟用CUBEMAP,並設置它的紋理和紋理環境,紋理生成模式 glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_CUBE_MAP); glBindTexture(GL_TEXTURE_CUBE_MAP, textureObj[CUBEMAP]); glTexEnvi(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_ENV, GL_MODULATE); //設置自動生成紋理坐標的方式為投影 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); camra.MoveForward(-5.0f); }void RenderScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); camra.ApplyCameraTransform(); //先關閉紋理單元0的2D紋理 glActiveTexture(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); //選擇紋理單元1並啟用立方體貼圖 glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_CUBE_MAP); //天空的紋理坐標手工設置 glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glTexEnvi(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_ENV, GL_DECAL); DrawSkyBox(); //開啟紋理坐標自動生成 glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glTexEnvi(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_ENV, GL_MODULATE); //繪制球體,激活紋理0和紋理1進行結合 glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glPushMatrix(); //注意每個紋理單元都有自己的紋理矩陣,這裡我們操作紋理1,立方體貼圖 glActiveTexture(GL_TEXTURE1); glMatrixMode(GL_TEXTURE); glPushMatrix(); M3DMatrix44f m,invert; //獲取照相機的位置,並進行反轉,形成反射的紋理 camra.GetCameraOrientation(m); m3dInvertMatrix44(invert,m); glMultMatrixf(invert); gltDrawSphere(0.75f, 41, 41); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopMatrix(); glutSwapBuffers(); }void ShutdownRC() { glDeleteTextures(TEXTURENUM, textureObj); }void ChangeSize(GLsizei w, GLsizei h) { if (h == 0) h = 1; glViewport(0, 0, w, h); GLfloat aspect = (GLfloat)w/(GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.0, aspect, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glutPostRedisplay(); }void SpecialKey(int value, int x, int y) { if (value == GLUT_KEY_LEFT) { camra.RotateLocalY(-0.1f); } if (value == GLUT_KEY_RIGHT) { camra.RotateLocalY(0.1f); } if (value == GLUT_KEY_UP) { camra.MoveForward(0.1f); } if (value == GLUT_KEY_DOWN) { camra.MoveForward(-0.1f); } glutPostRedisplay(); }int main(int args, char *argv[]) { glutInit(&args, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("mutiltex"); glutDisplayFunc(RenderScene); glutReshapeFunc(ChangeSize); SetupRC(); glutSpecialFunc(SpecialKey); glutMainLoop(); ShutdownRC(); return 0; }