歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenGL超級寶典學習筆記——紋理映射(一)

OpenGL超級寶典學習筆記——紋理映射(一)

日期:2017/3/1 9:32:16   编辑:Linux編程

紋理映射,是將紋理空間中的紋理像素映射到屏幕空間中的像素的過程。

紋理映射是真實感圖像制作的一個重要部分,運用它可以方便的制作出極具真實感的圖形而不必花過多時間來考慮物體的表面細節。然而紋理加載的過程可能會影響程序運行速度,當紋理圖像非常大時,這種情況尤為明顯。如何妥善的管理紋理,減少不必要的開銷,是系統優化時必須考慮的一個問題。其中OpenGL提供了紋理對象對象管理技術來解決上述問題。與顯示列表一樣,紋理對象通過一個單獨的數字來標識。這允許OpenGL硬件能夠在內存中保存多個紋理,而不是每次使用的時候再加載它們,從而減少了運算量,提高了速度。

加載紋理

要把紋理映射到幾何圖形中,首先我們需要加載紋理到內存中,加載之後這個紋理就成為OpenGL當前紋理狀態的一部分。OpenGL提供了下面三個方法從內存緩沖區中加載紋理:

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);

這三個方法告訴了OpenGL你加載的紋理數據的信息。OpenGL支持1維、2維、3維的紋理數據的映射,使用一致的根函數調用(glTexImage)加載紋理,使其成為當前紋理。使用上面的函數時,OpenGL會拷貝data參數所指向的位置的紋理信息。這種數據復制可能代價很高。OpenGL中可以使用紋理對象來緩解性能的問題。

第一個參數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來提取代理查詢的結果。

第二個參數level指定要Mipmap的等級。

第三個參數internalformat告訴OpenGL內部用什麼格式存儲和使用這個紋理數據(一個像素包含多少個顏色成分,是否壓縮)。常用的常量如下:

常量 描述 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;
}

效果圖如下:

OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm

OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm

OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm

Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm

OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm

Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm

如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm

更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581

Copyright © Linux教程網 All Rights Reserved