紋理映射,是將紋理空間中的紋理像素映射到屏幕空間中的像素的過程。數組
紋理映射是真實感圖像製做的一個重要部分,運用它能夠方便的製做出極具真實感的圖形而沒必要花過多時間來考慮物體的表面細節。然而紋理加載的過程可能會影響程序運行速度,當紋理圖像很是大時,這種狀況尤其明顯。如何妥善的管理紋理,減小沒必要要的開銷,是系統優化時必須考慮的一個問題。其中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]。下圖解釋紋理座標如何放置紋理元素的:
咱們能夠經過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圖元中,注意紋理的四個角與四方形的四個角是一一對應的。
固然咱們還能夠把一個四方形的紋理圖映射到一個三角形的幾何圖元中:
紋理座標也能夠經過紋理矩陣來進行變換。紋理矩陣的工做方式與投影矩陣,模型視圖矩陣相似。經過glMatrixMode(GL_TEXTURE):來開啓紋理矩陣模式,在此函數調用後面的矩陣變換將被應用於紋理座標。紋理座標能夠進行移動、縮放、旋轉。紋理矩陣的棧最多隻能容納兩個紋理矩陣。經過glPushMatrix 和glPopMatrix來進行棧操做。
下面的例子是一個金字塔,我把每一個面設置成不一樣的顏色,而後再進行紋理座標映射,並使用定時器讓金字塔旋轉。
紋理圖以下:
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;
}
效果圖以下: