还有两种纹理生成模式未介绍,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示例中,我们把立方体的六个图像的路径保存到一个数组中,然后循环加载这六个面的纹理贴图:
// 纹理文件目录 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}; ..... ..... GLint iWidth, iHeight, iComponents; GLenum eFormat; // 设置纹理环境 glTexEnvi(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_ENV, GL_REPLACE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_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) { 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); } }
然后我们需要启用立方体贴图:glEnable(GL_TEXTURE_CUBE_MAP);如果GL_TEXTURE_CUBE_MAP和GL_TEXTURE_2D同时开启的话,GL_TEXTURE_CUBE_MAP优先起作用。PS:glTexParameter同时对这立方体的六张图像起作用,这六张图像在一个立方体纹理中。
立方体贴图的纹理坐标与一般的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(纹理模式)直到我们画完球体后再恢复到原始状态。代码如下:
//绘制球体 glPushMatrix(); //切换到纹理矩阵 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();
效果如下(移动照相机,并旋转可以看到不同的反射面)(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; }