OpenGL(十二) 紋理映射(貼圖)


OpenGL繪製紋理的步驟:函數


  • 1. 開啓紋理功能

使用glEnable(GL_TEXTURE_2D)開啓2D紋理功能,使用glDisable(GL_TEXTURE_2D)關閉紋理,默認狀況下紋理是關閉的。oop


  • 2. 讀取紋理圖片到內存

讀取的時候注意如下3點:測試

  •  1) BMP文件數據前54位是文件頭和信息頭數據,偏移54位以後纔是要讀取的圖像數據。
  •  2) 要檢測圖像寬度的位數是不是4的整數倍,若是不是,須要補齊,而且以補齊後的內存帶下分配內存。
  •  3) 出於兼容較低版本OpenGL的目的,通常須要檢測圖像的長寬是不是2的整數次冪,以及長寬是否超過了當前版本所支持的最大長寬數值,若條件不符合,須要進行相應的調整,可使用gluScaleImage函數對圖像進行縮放。


  • 3. 分配紋理編號並設置相關屬性

使用函數glGenTextures(N,&textureID)來分配N個紋理編號,使用glTexParameteri來設置經常使用的4個紋理參數,這些參數包括了當前紋理圖像大小/小於模型目標時擴展紋理的處理方式。使用gltextImage2D函數根據指定的紋理參數生成一個2D紋理,函數原型是:ui

glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);

  • 第一個參數爲指定的目標,可使用GL_TEXTURE_2D。
  • 第二個參數爲「多重細節層次」,不考慮多重紋理的話設置爲零。
  • 第三個參數表示RGB數據存儲的格式,這裏使用GL_RGB。
  • 第4、五個參數是二維紋理像素的寬度和高度。
  • 第六個參數是紋理邊框的大小,不使用紋理邊框設置爲零。
  • 第七個核第八個參數是數據格式和數據保存形式。
  • 第九個參數是保存了紋理圖像的內參塊地址。

在分配紋理以前最好先獲取一下程序當前所使用的紋理編號,而且在生成本次紋理以後恢復以前保存的紋理編號,從而保證不對當前程序產生影響。spa


  • 4) 設置視景體和觀察點

經過gluPerspective和gluLookAt設置「視角」和「觀察點」的相關參數。操作系統


  • 5. 紋理映射,繪製紋理圖像

紋理映射跟顏色的繪製同樣,須要指定每個頂點在紋理圖像中所對應的位置,OpenGL會自動計算出頂點之間的其餘點在紋理圖像中應該對應的位置。這裏注意紋理圖像的座標範圍是從(0,0)到(1,1),左下角爲(0,0),右上角爲(1,1),鑑於以前已經在第3步中設置了座標點大於1或者座標點小雨0的狀況的處理方法,因此不考慮顯示效果的話,座標能夠任意指定。code


下邊代碼是紋理映射的應用:orm

#define WindowWidth  400
#define WindowHeight 400
#define WindowTitle  "OpenGL紋理測試"

#include <glut.h>
#include <stdio.h>
#include <stdlib.h>

//定義兩個紋理對象編號
GLuint texGround;
GLuint texWall;

#define BMP_Header_Length 54  //圖像數據在內存塊中的偏移量
static GLfloat angle = 0.0f;   //旋轉角度

// 函數power_of_two用於判斷一個整數是否是2的整數次冪
int power_of_two(int n)
{
	if( n <= 0 )
		return 0;
	return (n & (n-1)) == 0;
}

/* 函數load_texture
* 讀取一個BMP文件做爲紋理
* 若是失敗,返回0,若是成功,返回紋理編號
*/
GLuint load_texture(const char* file_name)
{
	GLint width, height, total_bytes;
	GLubyte* pixels = 0;
	GLuint last_texture_ID=0, texture_ID = 0;

	// 打開文件,若是失敗,返回
	FILE* pFile = fopen(file_name, "rb");
	if( pFile == 0 )
		return 0;

	// 讀取文件中圖象的寬度和高度
	fseek(pFile, 0x0012, SEEK_SET);
	fread(&width, 4, 1, pFile);
	fread(&height, 4, 1, pFile);
	fseek(pFile, BMP_Header_Length, SEEK_SET);

	// 計算每行像素所佔字節數,並根據此數據計算總像素字節數
	{
		GLint line_bytes = width * 3;
		while( line_bytes % 4 != 0 )
			++line_bytes;
		total_bytes = line_bytes * height;
	}

	// 根據總像素字節數分配內存
	pixels = (GLubyte*)malloc(total_bytes);
	if( pixels == 0 )
	{
		fclose(pFile);
		return 0;
	}

	// 讀取像素數據
	if( fread(pixels, total_bytes, 1, pFile) <= 0 )
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 對就舊版本的兼容,若是圖象的寬度和高度不是的整數次方,則須要進行縮放
	// 若圖像寬高超過了OpenGL規定的最大值,也縮放
	{
		GLint max;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
		if( !power_of_two(width)
			|| !power_of_two(height)
			|| width > max
			|| height > max )
		{
			const GLint new_width = 256;
			const GLint new_height = 256; // 規定縮放後新的大小爲邊長的正方形
			GLint new_line_bytes, new_total_bytes;
			GLubyte* new_pixels = 0;

			// 計算每行須要的字節數和總字節數
			new_line_bytes = new_width * 3;
			while( new_line_bytes % 4 != 0 )
				++new_line_bytes;
			new_total_bytes = new_line_bytes * new_height;

			// 分配內存
			new_pixels = (GLubyte*)malloc(new_total_bytes);
			if( new_pixels == 0 )
			{
				free(pixels);
				fclose(pFile);
				return 0;
			}

			// 進行像素縮放
			gluScaleImage(GL_RGB,
				width, height, GL_UNSIGNED_BYTE, pixels,
				new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);

			// 釋放原來的像素數據,把pixels指向新的像素數據,並從新設置width和height
			free(pixels);
			pixels = new_pixels;
			width = new_width;
			height = new_height;
		}
	}

	// 分配一個新的紋理編號
	glGenTextures(1, &texture_ID);
	if( texture_ID == 0 )
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 綁定新的紋理,載入紋理並設置紋理參數
	// 在綁定前,先得到原來綁定的紋理編號,以便在最後進行恢復
	GLint lastTextureID=last_texture_ID;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);
	glBindTexture(GL_TEXTURE_2D, texture_ID);
	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_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
		GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
	glBindTexture(GL_TEXTURE_2D, lastTextureID);  //恢復以前的紋理綁定
	free(pixels);
	return texture_ID;
}


void display(void)
{
	// 清除屏幕
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// 設置視角
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(75, 1, 1, 21);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(-4, 7,7, 0, 0, 0, 0, 0, 1);

	glRotatef(angle, 0.0f, 0.0f, 1.0f); //旋轉

	// 繪製底面以及紋理
	glBindTexture(GL_TEXTURE_2D, texGround);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f);
	glTexCoord2f(0.0f, 3.0f); glVertex3f(-8.0f, 8.0f, 0.0f);
	glTexCoord2f(3.0f, 3.0f); glVertex3f(8.0f, 8.0f, 0.0f);
	glTexCoord2f(3.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f);
	glEnd();
	// 繪製立面
	glBindTexture(GL_TEXTURE_2D, texWall);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 5.0f);
	glTexCoord2f(2.0f, 1.0f); glVertex3f(6.0f, -3.0f, 5.0f);
	glTexCoord2f(2.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
	glEnd();

	//繪製另一個立面
	glBegin(GL_QUADS);
	glTexCoord2f(2.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);     
	glTexCoord2f(0.0f, 0.0f); glVertex3f(6.0f, 9.0f, 0.0f);		 
	glTexCoord2f(0.0f, 1.0f); glVertex3f(6.0f, 9.0f, 5.0f);		 
	glTexCoord2f(2.0f, 1.0f); glVertex3f(6.0f, -3.0f, 5.0f);	 
	glEnd();	

	glutSwapBuffers();  
}

void myIdle(void)  
{     
	angle += 1.8f;    
	if( angle >= 360.0f )      
		angle = 0.0f;   
	display();  
}   

int main(int argc, char* argv[])
{
	// GLUT初始化
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(WindowWidth, WindowHeight);
	glutCreateWindow(WindowTitle);	
	glEnable(GL_DEPTH_TEST);    
	glEnable(GL_TEXTURE_2D);    // 啓用紋理
	texGround = load_texture("ground2.bmp");  //加載紋理
	texWall = load_texture("wall2.bmp");
	glutDisplayFunc(&display);   //註冊函數 
	glutIdleFunc(&myIdle);  
	glutMainLoop(); //循環調用
	return 0;
}

運行效果:



動圖效果:對象




  • 程序中使用到的關鍵函數的簡單說明:

gluDisplayFunc: 該函數用於註冊一個繪圖函數,只是註冊而不是調用,這時候不會當即執行註冊函數,操做系統會在必要時刻對窗體進行從新繪製操做,這時候就會調用執行註冊函數。blog


glutMainLoop:調用該函數以後,程序進入GLTU事件處理循環,讓全部跟「事件」有關的、任何已經註冊的函數無限循環執行,該函數不會返回。


glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max):獲取當前版本OpenGL支持的最大紋理尺寸。

判斷一個整數是否是2的整數次冪的方法:使用n&(n-1)判斷,若是n是2的整次冪,則n的二進制形式表示裏應該只有一個1,其餘位所有爲0。例如對於8bit二進制00100000來講,n-1=00011111,則n&(n-1)結果應該是0,若是不爲0,則n不是2的整次冪。


gluScaleImage:這個函數調用適當的像素存儲模式改變一個像素圖像的大小來讀取源圖像的像素數據,而後把像素數據寫入新的目標圖像。簡單來講就是調整圖像大小。


glGenTextures:用於分配一個紋理對象的編號,能夠批量分配一系列紋理對象。


glGenIntegerv(GL_TEXTURE_BINGDING_2D,@last_texs):用於獲取當前綁定的紋理編號,把紋理編號放置如last——texs內。


glBindTexture:用於指定接下來操做的紋理對象的編號,操做包括指定紋理像素、指定紋理參數、指定紋理座標等。這些操做都會應用在glBindTexture裏綁定的紋理對象上。若是不指定,默認會在編號爲0的紋理對象上操做。


glTexParameteri:用來設置紋理參數,一般須要設置的參數有4個:

  • 第一個是GL_TEXTURE_MAG_FILTER,指當目標大於當前紋理圖像時如何處理;
  • 第二個是GL_TEXTURE_MIN_FILTER,指當目標小於當前紋理圖像時如何處理;
  • 第三個是GL_TEXTURE_WRAP_S,指當紋理座標的第一維座標值大於1.0或者小於0.0時如何處理。紋理圖像的座標範圍是爲(0,0)~(1,1),左下角是(0,0),右上角是(1,1)。

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE):設置紋理跟材質的融合方式,不一樣的設置能夠產生不一樣的效果,默認爲GL_REPLACE,即只是用紋理,覆蓋模型的材質。


glTexImage2D:用於載入一個二維的紋理。


glTexCoord2f:用於繪製圖形時指定紋理的座標。

相關文章
相關標籤/搜索