原文地址:http://www.videotutorialsrock.com/opengl_tutorial/textures/home.php
视频下载:http://www.videotutorialsrock.com/opengl_tutorial/textures/video.flv
文本模式:http://www.videotutorialsrock.com/opengl_tutorial/textures/text.php
代码下载:http://www.videotutorialsrock.com/opengl_tutorial/textures/textures.zip
译文:
什么是纹理,为什么我们需要使用纹理
很多时候我们想在我们的3D多边形中绘制图片,或者叫“纹理”,而不只仅仅是颜色。这样做有两个原因:第一,很明显,我们想给这个形状一些更加精细的描绘。比如:如果我们要绘制一个人的脸,我们获得一个脸的照片然后在这个脸部的一些多边形中加入一些纹理,这样比使用100万种细小的颜色绘制人脸容易的多。第二个原因是我们需要在不加入很多多边形的情况下模拟一些光照下的细小特征。比如,使用纹理可以是一个高尔夫球看起来像有很多的涟漪在上面,见下图:
其实看上去并不很直白,只是给你这个概念罢了。上面的图片的多边形较少,可以从边缘的轮廓看出来。通过在表面添加纹理出现涟漪效果。不使用纹理,我们也可以给图片添加大量的多边形,但这会影响绘制的速度。计算想要添加的面和额外的点也要消耗很多的工作。这项技术是计算机图形学必不可少的,在ps1和任天堂游戏中运用最多。
使用纹理模拟细小的特征的缺陷是其对光照的反应并不十分准确。比如,如果我们和高尔夫球中的某个面几乎平行的放置光源,那么这个小圈应该一边点亮一边为黑,但是使用纹理时并不会发生。但这比什么都没有的好,比起上面说的添加额外的多边形要好的多。
OpenGL的纹理
我们看些代码。首先看下完成的程序效果图:
为了使图片看起来像这样,首先需要载入我们想添加纹理的图片。我们想获取一个图片文件并且将其转为一个字符数组(R1,G1,B1,R2,G2,B2,...)来表示图片中每个像素的颜色。每个字符的取值为0到255。我们的数组从左低(坐上方)的像素开始,到这一行的右边结束,然后移动到下一行...这是OpenGL图片的格式。
我写了一个loadBMP的函数为我们载入一个bitmap(位图)图片。位图比起图片格式(.png)占更多的空间,但使用位图是因为他们相对比较容易载入成我们需要的格式。loadBMP函数并不都这么长和复杂(这里我处理了一些内存管理以确保程序不会泄露内存)。我是在维基百科上查到关于位图的信息的。不管怎样,你不必了解这是如何工作的,只要知道这是做什么的就行了。
看下下imageloader.h文件。这里给出loadBMP函数功能的基本思路。(真正的loadBMP函数代码在imageloader.cpp中)给出一个文件名,返回一个Image对象,其中包含了图像的长和宽,像素矩阵(按照我们的意图排列的像素的颜色)。
一旦我们获得图像,我们需要将其发送到OpenGL,我们在loadTexture函数中做这些。
//Makes the image into a texture, and returns the id of the texture
GLuint loadTexture(Image *image) {
loadTexture函数接受一个Image对象并返回一个GLuint(这个变量同一个unsigned int类似)表示OpenGL给这个物体纹理的id。
GLuint textureId;
glGenTextures(1, &textureId); //Make room for our texture
首先,我们调用glGenTextures告诉OpenGL为纹理留出空间。第一个参数是我们需要纹理的数量,第二个参数是一个OpenGL存储纹理的id的数组。我们这个例子中第二个参数是一个大小为1的数组。通过使用&textureId作为第二个参数为一个纹理用textureId保存其id。
glBindTexture(GL_TEXTURE_2D, textureId); //Tell OpenGL which texture to edit
//Map the image to the texture
glTexImage2D(GL_TEXTURE_2D,//Always GL_TEXTURE_2D
0, //0 for now
GL_RGB, //Format OpenGL uses for image
image->width, image->height, //Width and height
0, //The border of the image
GL_RGB, //GL_RGB, because pixels are stored in RGB format
GL_UNSIGNED_BYTE, //GL_UNSIGNED_BYTE, because pixels are stored
//as unsigned numbers
image->pixels); //The actual pixel data
现在需要将纹理id分配到我们的图像数据中。我们调用glBindTexture(GL_TEXTURE_2D,textureId)让OpenGL知道我们想要我们刚刚创建的纹理来工作。然后,我们调用glTexImage2D将图片载入到OpenGL。注释详细的说明了每个参数是干嘛的,但你不必认真的去了解所有的参数。OpenGL会拷贝像素数据,因此在这个函数之后,我们可以使用delete释放这个图片的内存。(我们不在这里释放,我们在main.cpp中释放。)
注意我们只能使用宽和高为64,128,256的图片(2的幂数)。其他尺寸的可能不能正常运行。
return textureId; //Returns the id of the texture
}
最后重要的一点是要返回纹理的id。
我们想载入文件名为vtr.bmp的图片并加入OpenGL纹理,因此在initRendering中加入下面代码
Image* image = loadBMP("vtr.bmp");
_textureId = loadTexture(image);
delete image;
上面的代码很直白。我们载入图片,然后将纹理载入OpenGL,最后删除image对象,因为已经不在需要了。
在drawScene函数中,调用glEnable(GL_TEXTURE_2D)激活纹理,glBindTexture(GL_TEXTURE_2D,_textureId)告诉OpenGL我们想使用id为_textureId的纹理。
现在已经基本建立了如何用OpenGL映射我们的纹理的步骤。要理解更深一点,我们需要多了解一些纹理贴图。
每个纹理多边形的像素点对应我们图像中的一个点。比如,可能对应上面图片中的绿点。OpenGL计算每个像素的颜色。最直观的办法是使用最近的texel(纹理像素),在这个例子中为淡蓝色。但是这使得我们的纹理看起来不均匀,像我们程序截图的下面。你可能在游戏中看到过不均匀的纹理当你靠近一个物体或者墙的时候,这种游戏就使用这种纹理贴图。一个更好的想法是取这个点周围的纹理点的平均值。这个例子中,我们采用蓝色纹理点的加权均值,上方的一个点,左边的一个点,左上方的一个点。总的来说,依我来看,极小会用到不均匀的绘图。
在底面我们使用这种不均匀的贴图方式(blocky mapping)。调用glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)和glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST)。第一个函数告诉OpenGL当纹理离我们太远的时候使用不均匀贴图(GL_NEAREST),第二个函数在纹理很近的时候使用不均匀贴图。如果你想看到程序使用模糊贴图的效果,将这两行注释掉,并将下面两行的注释号删去。
现在调用glColor3f(1.0f,0.2f,0.2f)设置纹理的颜色。这个函数让图片看上去偏红。这个函数告诉OpenGL将图片中绿色和蓝色的成分乘系数0.2。如果我们不想为图片加色,我们可以调用glColor3f(1.0f,1.0f,1.0f)。另外,一个纹理中你甚至还可以使用混合色。
glBegin(GL_QUADS);
glNormal3f(0.0, 1.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-2.5f, -2.5f, 2.5f);
glTexCoord2f(1.0f, 0.0f);
glVertex3f(2.5f, -2.5f, 2.5f);
glTexCoord2f(1.0f, 1.0f);
glVertex3f(2.5f, -2.5f, -2.5f);
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-2.5f, -2.5f, -2.5f);
glEnd();
现在除了法向量,每个顶点有一个纹理坐标。这个纹理坐标表示了每个顶点映射到图像中的那个点。这个纹理坐标(a+b,c+d),a和c为整型,表示点坐标在纹理的b个分数的上方,d个分数的右边。设置一个顶点的纹理坐标,调用glTexCoord2f和我们在调用顶点坐标glVertex3f之前希望的纹理的坐标。
//Back
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glColor3f(1.0f, 1.0f, 1.0f);
对于后面,我们希望使用模糊的纹理贴图,所以调用最后参数为GL_LINEAR的glTexParameteri函数。然后调用glColor3f(1.0f,1.0f,1.0f)使图片没有任何颜色。注意我们不必再调用glBindTexture来设置纹理,OpenGL会保持相同的纹理(状态机设计)。
glBegin(GL_TRIANGLES);
glNormal3f(0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f);
glVertex3f(-2.5f, -2.5f, -2.5f);
glTexCoord2f(5.0f, 5.0f);
glVertex3f(0.0f, 2.5f, -2.5f);
glTexCoord2f(10.0f, 0.0f);
glVertex3f(2.5f, -2.5f, -2.5f);
glEnd();
指定法向量,纹理坐标,和后面的三角形的顶点。注意到我们设置纹理坐标的方法,我们的图像会在三角形这个面中被挤压和重复,看程序的截图。
//Left
glDisable(GL_TEXTURE_2D);
glColor3f(1.0f, 0.7f, 0.3f);
glBegin(GL_QUADS);
glNormal3f(1.0f, 0.0f, 0.0f);
glVertex3f(-2.5f, -2.5f, 2.5f);
glVertex3f(-2.5f, -2.5f, -2.5f);
glVertex3f(-2.5f, 2.5f, -2.5f);
glVertex3f(-2.5f, 2.5f, 2.5f);
glEnd();
现在我想不用纹理而换回使用颜色了,因此调用glDisable(GL_TEXTURE_2D)禁用纹理,然后像前面一课一样使用颜色。
OK,这就是OpenGL纹理工作过程。
练习:
- 使用't'键触发纹理效果。
- 使用时钟,使纹理在三角形的表面移动。
- 使用自己的位图做一个纹理。并将其用在底部作为第二个纹理,而不用改变后面三角形的纹理。