OpenGL超级宝典笔记——纹理映射(一)

纹理映射,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程。数组

纹理映射是真实感图像制做的一个重要部分,运用它能够方便的制做出极具真实感的图形而没必要花过多时间来考虑物体的表面细节。然而纹理加载的过程可能会影响程序运行速度,当纹理图像很是大时,这种状况尤其明显。如何妥善的管理纹理,减小没必要要的开销,是系统优化时必须考虑的一个问题。其中OpenGL提供了纹理对象对象管理技术来解决上述问题。与显示列表同样,纹理对象经过一个单独的数字来标识。这容许OpenGL硬件可以在内存中保存多个纹理,而不是每次使用的时候再加载它们,从而减小了运算量,提升了速度。函数

加载纹理

要把纹理映射到几何图形中,首先咱们须要加载纹理到内存中,加载以后这个纹理就成为OpenGL当前纹理状态的一部分。OpenGL提供了下面三个方法从内存缓冲区中加载纹理:oop

void glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, void *data);性能

void glTexIamge2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, void *data);优化

void glTexIamge3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data);spa

这三个方法告诉了OpenGL你加载的纹理数据的信息。OpenGL支持1维、2维、3维的纹理数据的映射,使用一致的根函数调用(glTexImage)加载纹理,使其成为当前纹理。使用上面的函数时,OpenGL会拷贝data参数所指向的位置的纹理信息。这种数据复制可能代价很高。OpenGL中可使用纹理对象来缓解性能的问题。.net

第一个参数target是说明纹理对象是几维的,其中1D、2D、3D接受的参数分别为GL_TEXTURE_1D、GL_TEXTURE_2D和GL_TEXTURE_3D。你也能够用用相同的方式来指定代理纹理。参数为GL_PROXY_TEXTURE_1D、GL_PROXY_TEXTURE_2D、GL_PROXY_TEXTURE_3D,而后经过glGetTexParameter来提取代理查询的结果。3d

第二个参数level指定要Mipmap的等级。代理

第三个参数internalformat告诉OpenGL内部用什么格式存储和使用这个纹理数据(一个像素包含多少个颜色成分,是否压缩)。经常使用的常量以下:code

常量 描述
GL_APHPA 按照ALPHA值存储纹理单元
GL_LUMINANCE 按照亮度值存储纹理单元
GL_LUMINANCE_ALPHA 按照亮度和alpha值存储纹理单元
GL_RGB 按照RGB成分存储纹理单元
GL_RGBA 按照RGBA成分存储纹理单元

widht, height, depth分别指定了纹理的宽度、高度和深度。在OPENGL2.0以前,这三个值要求是2的n次幂(即1,2,4,8,16,32等),不然纹理将没法显示。虽然opengl2.0以后不要求纹理是2的n次幂了,但这不保证性能。考虑性能的话,通常仍是把纹理作成2的n次幂。

border参数是指定纹理的边界宽度。纹理边界容许咱们对边界外的纹理单元进行额外的设置,对它的宽度、高度、深度进行扩展。这在纹理过滤中颇有用。

后面三个参数format、type、data和glDrawPixles函数的参数相同。

加载完纹理数据以后,咱们还须要经过glEnable()接受GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D其中一个为参数开启相应维数的纹理映射。经过glDisable()来关闭。

PS:经过glTexImage加载的纹理数据同样会经过像素和图像管道进行变换。这意味着像素压缩,放大缩小,颜色表,和卷积都会被应用到被加载的纹理数据上。

颜色缓冲区中读取

跟从颜色缓冲区中读取像素同样,纹理数据同样能够从颜色缓冲区中读取。使用以下两个方法:

void glCopyTexImage1D(GLenum target, GLint level, GLenum internalformat,GLint x, GLint y, GLsizei width, GLint border);

void glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);

前面三个参数所表明的意义和glTexImage函数是同样的。x,y指定从颜色缓冲区的哪一个位置开始读取数据。width height指定宽度、高度,border指定边界宽度。注意:咱们没法从2维的颜色缓冲区中读取三维的纹理数据,因此没有glCopyTexImage3D这个方法。

更新纹理

若是咱们只须要修改纹理中的一部分数据,而不想从新加载纹理,咱们可使用glTexSubImage方法。这个方法比每次都去从新加载纹理数据要快得多。它的三个变型以下:

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 void *data);

void glTexSubImage3D(Glenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);

xOffset, yOffset, zOffset指定已存在的纹理数据中的偏移值,从这个位置开始替换更新纹理数据。width, height, depth指定要更新到如今的纹理中的纹理数据的规格宽、高、深度。

下面的函数容许咱们从颜色缓冲区中读取数据并插入或替换如今纹理的部分数据:

void glCopyTexSubImage1D(GLenum target, GLint level, GLint xOffset, GLint x, GLint y, GLsizei 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);

这些参数与上面解释过的是同样的意义。注意没有glCopyTexImage3D函数,可是咱们仍能够用glTexSubImage3D函数把2维的颜色缓冲区中的数据,应用到3维纹理的一个平面中。

映射纹理到几何图元

要把纹理映射到几何图元中,咱们须要告诉OpenGL如何映射这些纹理。纹理元素不是根据内存中的位置来放置的(与像素不一样)。纹理用更抽象的纹理坐标来肯定纹理元素放置的位置。纹理坐标包含s、t、r和q坐标轴相似于顶点坐标的x、y、z和w,以此来支持1维到3维的纹理。q用来缩放纹理坐标,即纹理坐标归一化后的坐标值为s/q、t/q、r/q,默认为1.0。纹理坐标值为浮点数,范围为[0.0,1.0]。下图解释纹理坐标如何放置纹理元素的:

image

咱们能够经过glTexCoord函数来设置纹理坐标,这相似于设置顶点坐标。下面是3个经常使用的glTexCoord变型:

void glTexCoord1f(GLfloat s);

void glTexCoord2f(GLfloat s, GLfloat t);

void glTexCoord3f(GLfloat s, GLfloat t, GLfloat r);

注意纹理坐标像表面法线,和颜色值同样要在设置顶点以前进行设置。用上面的这些函数为每一个顶点设置纹理坐标。而后OpenGL会根据须要对纹理进行缩放后映射到几何图元中(其中应用到纹理过滤,后面再解释)。下图是把2维的纹理映射到一个四方形GL_QUADE图元中,注意纹理的四个角与四方形的四个角是一一对应的。

image

固然咱们还能够把一个四方形的纹理图映射到一个三角形的几何图元中:

image

纹理矩阵

纹理坐标也能够经过纹理矩阵来进行变换。纹理矩阵的工做方式与投影矩阵,模型视图矩阵相似。经过glMatrixMode(GL_TEXTURE):来开启纹理矩阵模式,在此函数调用后面的矩阵变换将被应用于纹理坐标。纹理坐标能够进行移动、缩放、旋转。纹理矩阵的栈最多只能容纳两个纹理矩阵。经过glPushMatrix 和glPopMatrix来进行栈操做。

一个简单的例子

下面的例子是一个金字塔,我把每一个面设置成不一样的颜色,而后再进行纹理坐标映射,并使用定时器让金字塔旋转。

纹理图以下:

image

pyramid代码以下:

#include "gltools.h"
#include "math3d.h"

static GLint iWidth, iHeight, iComponents;
static GLenum eFormat;
static GLfloat xRot, yRot;

static GLfloat noLight[4] = {0.0f, 0.0f, 0.0f, 1.0f};
static GLfloat ambientLight[4] = {0.3f, 0.3f, 0.3f, 1.0f};
static GLfloat diffuseLight[4] = {0.7f, 0.7f, 0.7f, 1.0f};
static GLfloat brightLight[4] = {1.0f, 1.0f, 1.0f, 1.0f};

//光的位置在右上角
static GLfloat lightPos[] = { 5.0f, 5.0f, 5.0f, 1.0f};

void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
//设置光照环境
glEnable(GL_LIGHTING);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, noLight);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, brightLight);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
glEnable(GL_LIGHT0);

//开启颜色追踪
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glMaterialfv(GL_FRONT, GL_SPECULAR, brightLight);

//镜面光加亮的范围设置大一点
glMateriali(GL_FRONT, GL_SHININESS, 30);

//读取图像文件
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
void *pImage = NULL;
pImage = gltLoadTGA("..\\stone.tga", &iWidth, &iHeight, &iComponents, &eFormat);

if (pImage)
{
//加载纹理,而后释放临时的内存
glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pImage);
free(pImage);
pImage = NULL;
}

//设置纹理过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_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);

//设置纹理环境
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glEnable(GL_TEXTURE_2D);
}

void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/*
金字塔顶点下标为0,底部坐标以下
1______2
| |
|______|
3 4
*/
//金字塔顶点数组
M3DVector3f vertices[5] = {{0.0f, 0.8f, 0.0f},
{-.50f, 0.0f, -.50f},
{.50f, 0.0f, -.50f},
{-.50f, 0.0f, .50f},
{.50f, 0.0f, .50f}};

//表面法线向量
M3DVector3f normal;

glPushMatrix();

//先往里和往下平移一点
glTranslatef(0.0f, -0.3f, -4.0f);
if (xRot > 360.5f)
{
xRot = 0.0f;
}

if (yRot > 360.5f)
{
yRot = 0.0f;
}

//进行旋转
glRotatef(xRot, 1.0f, 0.0f, 0.0f);
glRotatef(yRot, 0.0f, 1.0f, 0.0f);
xRot += 0.5f;
yRot += 0.5f;

glBegin(GL_TRIANGLES);

//底面的四方形由两个三角形组成
glColor3f(1.0f, 0.0f, 0.0f);

//注意法线和纹理都要在顶点以前设置
glNormal3f(0.0f, -1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex3fv(vertices[1]);
glTexCoord2f(1.0f, 1.0f);
glVertex3fv(vertices[2]);
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[4]);

glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[4]);
glTexCoord2f(0.0f, 0.0f);
glVertex3fv(vertices[3]);
glTexCoord2f(0.0f, 1.0f);
glVertex3fv(vertices[1]);

//前面
glColor3f(0.0f, 1.0f, 0.0f);
m3dFindNormal(normal, vertices[0], vertices[3], vertices[4]);
glNormal3fv(normal);
glTexCoord2f(0.5f, 0.5f);
glVertex3fv(vertices[0]);
glTexCoord2f(0.0f, 0.0f);
glVertex3fv(vertices[3]);
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[4]);

//左侧面
glColor3f(0.0f, 0.0f, 1.0f);
m3dFindNormal(normal, vertices[1], vertices[3], vertices[0]);
glNormal3fv(normal);
glTexCoord2f(0.0f, 0.0f);
glVertex3fv(vertices[1]);
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[3]);
glTexCoord2f(0.5f, 0.5f);
glVertex3fv(vertices[0]);

//右侧面
glColor3f(0.0f, 1.0f, 1.0f);
m3dFindNormal(normal, vertices[0], vertices[4], vertices[2]);
glNormal3fv(normal);
glTexCoord2f(0.5f, 0.5f);
glVertex3fv(vertices[0]);
glTexCoord2f(0.0f, 0.0f);
glVertex3fv(vertices[4]);
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[2]);

//后面
glColor3f(1.0f, 0.0f, 1.0f);
m3dFindNormal(normal, vertices[0], vertices[2], vertices[1]);
glNormal3fv(normal);
glTexCoord2f(0.5f, 0.5f);
glVertex3fv(vertices[0]);
glTexCoord2f(0.0f, 0.0f);
glVertex3fv(vertices[2]);
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(vertices[1]);

glEnd();

glPopMatrix();

glutSwapBuffers();
}

void ChangeSize(GLsizei w, GLsizei h)
{
if (h == 0)
h = 1;

glViewport(0, 0, w, h);

float fAspect = (GLfloat)w/(GLfloat)h;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(35.0, fAspect, 1.0, 100.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glutPostRedisplay();
}

void TimerFunc(int value)
{
glutPostRedisplay();
glutTimerFunc(60, TimerFunc, 1);
}


int main(int args, char *arv[])
{
glutInit(&args, arv);
glutInitDisplayMode(GL_RGB | GL_DOUBLE | GL_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("pyramid");

glutDisplayFunc(RenderScene);
glutReshapeFunc(ChangeSize);
SetupRC();

glutTimerFunc(50, TimerFunc, 1);

glutMainLoop();

return 0;
}

效果图以下:

image

image

相关文章
相关标签/搜索