一文詳解 紋理取樣與Mipmap紋理——構建山地渲染效果

在開發一些相對較大的場景時,例如:一片鋪滿相同草地紋理的丘陵地形,如果不採用一些技術手段,就會出現遠處的丘陵較近處的丘陵相比更加的清晰的視覺效果,而這種效果與真實世界中近處的物體清晰遠處物體模糊的效果是相違背的。

這是因為採用「透視投影」進行三維場景的繪製過程中,會產生近大遠小的效果,而遠處的丘陵與近處丘陵在繪製過程中採用的卻是同一幅紋理圖。如下圖所示為未採用Mipmap紋理貼圖和採用Mipmap紋理貼圖後的運行效果。

未採用Mipmap紋理貼圖效果

採用Mipmap紋理貼圖效果

從兩幅運行效果圖可以看出:

  • 第一幅圖 近處山體與遠處山體在視覺效果上清新程度幾乎相同,
  • 第二幅圖 遠處的山體較近處相比較產生了模糊的效果。

觀察了採用生成Mipmap紋理的山體運行效果圖後,下面對對Mipmap紋理的生成進行介紹。
生成Mipmap紋理不但要經過,紋理id的生成、紋理id的綁定、紋理過濾、指定紋理影像幾個階段還要有一個生成Mipmap紋理的階段。
此處重點介紹生成Mipmap紋理過程中的,紋理過濾與生成Mipmap紋理兩個階段。

一、生成Mipmap紋理

生成 Mipmap 紋理時綁定紋理、紋理過濾階段經常使用的紋理載入方法程式碼舉例如下:

// 進行紋理取樣 並 生成Mipmap紋理
public int initTexture(Bitmap bitmap)
{
	// 
	// (1)、生成紋理ID
	int[] textures = new int[1];
	GLES30.glGenTextures
	(
			1,          //產生的紋理id的數量
			textures,   //紋理id的數組
			0           //偏移量
	);
	// 
	// (2)、綁定紋理ID    
	int textureId=textures[0];    
	GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
	// 
	// (3)、選擇Mipmap紋理取樣方式:最近點取樣、線性取樣、三線性取樣等
	// 大紋理圖綁定到小的三維圖元上時:採用三線性取樣方式
	GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);   
	// 小紋理圖綁定到大的三維圖元上時:選擇最鄰近的mipmap層,使用線性取樣演算法進行紋理取樣。
	GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_NEAREST);
	// 
	// (4)、紋理拉伸方式:截取或重複
	// S方向採用 重複紋理
	GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S,GLES30.GL_REPEAT);
	// T方向採用 重複紋理
	GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T,GLES30.GL_REPEAT);		
    // 
	// (5)、載入紋理圖   
    GLUtils.texImage2D
    (
    		GLES30.GL_TEXTURE_2D,   //紋理類型,在OpenGL ES中必須為GL30.GL_TEXTURE_2D
    		0, 					  //紋理的層次,0表示基本影像層,可以理解為直接貼圖
    		bitmap, 			  //紋理影像
    		0					  //紋理邊框尺寸
    );   
    // 
	// (6)、生成Mipmap紋理
    GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);
    //釋放紋理圖
    bitmap.recycle();
    //返回紋理ID
    return textureId;
}

initTexture為將Bitmap圖片轉化為一個紋理的全部程式碼實現,其包含了紋理生成、Mipmap紋理取樣的全過程:

  • (1)、生成紋理ID
  • (2)、綁定紋理ID
  • (3)、選擇Mipmap紋理取樣方式:最近點取樣、線性取樣、三線性取樣等
  • (4)、紋理拉伸方式:截取或重複
  • (5)、載入紋理圖
  • (6)、生成Mipmap紋理

生成Mipmap紋理時:
與通常的生成載入一個普通的2D紋理不同,生成Mipmap紋理是由大到小生成一組紋理
例如:對於一個8x8像素的紋理來說,若構建Mipmap紋理,OpenGL會為其構造 4x4、2x2、1x1 這三個紋理(這三個紋理就是一組紋理)。
在紋理使用階段:
比如前邊山地效果圖,OpenGL使用紋理時,會根據開發者選擇的紋理取樣演算法從 Mipmap 紋理組中,按演算法要求選擇合適的一個或相鄰的兩個紋理進行紋理貼圖和紋理取樣,從而構建遠處模糊、近處清晰的效果
這裡邊兒涉及到的可選擇Mipmap紋理取樣演算法有:

  • GL_LINEAR_MIPMAP_LINEAR 三線性取樣;
  • GL_NEAREST_MIPMAP_NEAREST:選擇最鄰近的 mipmap 層,紋理採用最近點取樣;
  • GL_NEAREST_MIPMAP_LINEAR:選擇相鄰的兩個 mipmap 層,分別使用最近點取樣後,結果進行進行加權平均;
  • GL_LINEAR_MIPMAP_NEAREST:選擇 最鄰近的 mipmap 層,使用線性取樣演算法進行紋理取樣。

要介紹以上這幾種Mipmap紋理取樣算反,我們先要認識兩個主要的OpenGL API。
生成 Mipmap 紋理時,涉及到兩個主要的OpenGL API函數方法:

  • 紋理取樣與指定紋理拉伸方式的方法:glTexParameteri
  • 生成Mipmap紋理的方法:glGenerateMipmap
    其中glGenerateMipmap方法在生成MipMap紋理時,不是生成一個紋理,而是由大到小生成一組紋理。例如:對於一個8x8像素的紋理來說,若構建Mipmap紋理,OpenGL會為其構造 4x4、2x2、1x1 這三個紋理(這三個紋理就是一組紋理)。

二、API介紹

這裡大家應該能注意到,我特意在 glTexParameter* 後邊兒帶了一個星,這不是書寫錯誤。glTexParameter 存在多個方法,開發者常用的為:glTexParameteri 與 glTexParameterf

  • glTexParameter*
  • glTexParameteri與glTexParameterf區別

2.1 glTexParameter*

glTexParameteri 與 glTexParameterf方法的作用:
正如以上載入程式碼舉例中所示,用來指定紋理的取樣方式:最近點取樣、線性取樣、Mipmap紋理取樣等;指定紋理的拉伸方式:紋理截取、重複等

  • 取樣方式:
    GL_NEAREST(最近點取樣)、GL_LINEAR(線性取樣)、Mipmap紋理取樣等;
  • 紋理S、T方向的拉伸方式:
    GL_REPEAT(紋理重複)、GL_CLAMP_TO_EDGE(紋理截取);

(1) 其函數原型為:

void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameterf (GLenum target, GLenum pname, GLfloat param);

(2) 方法參數

  • target:為處於激活狀態的紋理單元指定紋理類型,參數為GL_TEXTURE_2D。
  • pname:指定紋理參數,可以為GL_TEXTURE_MIN_FILTER、 GL_TEXTURE_MAG_FILTER、GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T
  • param:param的值根據pname參數的不同而取不同的值,具體見下表所示:

2.2 glTexParameteri與glTexParameterf區別

有些朋友會問 glTexParameteri與glTexParameterf有什麼區別?

其實兩個函數方法:功能完全相同,只是最後一個輸入參數存在差異

  • 兩個函數方法,大多數情況下我們可直接使用 glTexParameteri 方法;
  • 但當 pname(第二個參數) 輸入參數為 GL_TEXTURE_MIN_LODGL_TEXTURE_MAX_LOD時,需選擇glTexParameterf方法。

我們觀察兩個方法的原型函數:
可以看到其只是最後一個參數的類型不同,其他並無區別。

void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameterf (GLenum target, GLenum pname, GLfloat param);

glTexParameteri與glTexParameterf區別

關於 GL_TEXTURE_MIN_LOD 與 GL_TEXTURE_MAX_LOD 的官方說明:

GL_TEXTURE_MIN_LOD的官方說明

2.3 glGenerateMipmap

前邊說道過:
glGenerateMipmap方法在生成Mipmap紋理時,不是生成一個紋理,而是由大到小生成一組紋理。例如:對於一個8x8像素的紋理來說,若構建Mipmap紋理,OpenGL會為其構造 4x4、2x2、1x1 這三個紋理(這三個紋理就是一組紋理)。
英文API描述為:
generate a complete set of mipmaps for a texture object。

(1) 其函數原型為:

void glGenerateMipmap (GLenum target);

(2) 方法參數

  • target 為處於激活狀態的紋理單元指定紋理類型。參數為 GL_TEXTURE_2D。

三、紋理取樣 與 紋理拉伸

正如第一部分程式碼舉例中,我們可以看到glTexParameter* 這個OpenGL API可以幫助開發人員完成Mipmap紋理取樣指定紋理的拉伸方式

// (3)、選擇Mipmap紋理取樣方式:最近點取樣、線性取樣、三線性取樣等
// 大紋理圖綁定到小的三維圖元上時:採用三線性取樣方式
GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);   
// 小紋理圖綁定到大的三維圖元上時:選擇最鄰近的Mipmap層,使用線性取樣演算法進行紋理取樣。
GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_NEAREST);
// 
// (4)、紋理拉伸方式:截取或重複
// S方向採用 重複紋理
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S,GLES30.GL_REPEAT);
// T方向採用 重複紋理
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T,GLES30.GL_REPEAT);	
  • 取樣方式:
    GL_NEAREST(最近點取樣)、GL_LINEAR(線性取樣)、Mipmap紋理取樣等;
  • 紋理S、T方向的拉伸方式:
    GL_REPEAT(紋理重複)、GL_CLAMP_TO_EDGE(紋理截取);

3.1 紋理取樣

對於OpenGL API

void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameterf (GLenum target, GLenum pname, GLfloat param);

我們知道當 pname 的值為 GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER 時,這個時候指的是指定紋理的取樣方式。:
但在正式介紹紋理取樣之前,先要對GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER 這兩個枚舉進行簡單介紹。

3.1.1 GL_TEXTURE_MIN_FILTER

這兩個枚舉類型的含義是:

  • 當紋理圖中的一個像素對應到待映射圖元上的多個片元時(紋理圖被放大),採用 MAG取樣;
  • 當紋理圖中的多個像素對應到待映射圖元上的一個片元時(紋理圖被縮小),採用 MIN 取樣。
設置紋理過濾方式 pname param
GL_TEXTURE_MIN_FILTER或GL_TEXTURE_MAG_FILTER GL_NEAREST、GL_LINEAR、GL_LINEAR_MIPMAP_LINEAR、GL_LINEAR_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR、GL_NEAREST_MIPMAP_NEAREST

3.1.2 紋理取樣演算法

當 pname 的值為 GL_TEXTURE_MIN_FILTERGL_TEXTURE_MAG_FILTER 時,這個時候指的是指定紋理的取樣方式。:

  • GL_NEAREST:最近點取樣
    最近點取樣,針對三維圖元的像素點對最其最接近它的紋理單元進行取樣。紋理取樣的效率比較高,但是這種紋理取樣方法的效果較差,甚至在螢幕顯示的影像會出現模糊。
  • GL_LINEAR:線性取樣
    線性取樣,每個象素要對其最接近的 nxn 的紋理單元進行取樣,取加權平均值。線性取樣相比於最近點取樣,效率較低,但效果較好。
  • GL_LINEAR_MIPMAP_LINEAR:三線性取樣
    三線性紋理取樣相對比較複雜,經常適用於紋理被縮小的情況。構建Mipmap紋理圖時,mip的意思是 「在狹窄的地方里的許多東西」,Mipmap就是對最初的紋理影像構造的一系列解析度減少並且預先過濾的紋理圖。
    對於一個8x8像素的紋理來說:若構建Mipmap紋理,需要為其構造4×4、2×2、1×1這三個紋理。
    如果一個三維空間中的矩形圖片在螢幕上占 6x6 像素點,那麼紋理取樣過程就變成:
    首先是到 8×8 的紋理圖中進行線性取樣;
    其次是到 4×4 的紋理圖中進行線性取樣;
    然後把兩次取樣的結果進行加權平均,得到最後的取樣數據。
    因為整個過程一共進行了三次的線性取樣,所以這種方法叫做三線性取樣。
  • GL_NEAREST_MIPMAP_NEAREST
    選擇最鄰近的 Mipmap 層,紋理採用最近點取樣;
  • GL_NEAREST_MIPMAP_LINEAR
    選擇相鄰的兩個 Mipmap 層,分別使用最近點取樣後,結果進行進行加權平均;
  • GL_LINEAR_MIPMAP_NEAREST
    選擇 最鄰近的 Mipmap 層,使用線性取樣演算法進行紋理取樣。

3.2 紋理拉伸

對於OpenGL API

void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameterf (GLenum target, GLenum pname, GLfloat param);

當 pname 的值為 GL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_T 時,這個時候指的是指定紋理的拉伸方式,那麼 param 可選的值為:

  • GL_REPEAT 重複紋理
  • GL_CLAMP_TO_EDGE 截取紋理
設置紋理S、T方向拉伸方式 pname param
GL_TEXTURE_WRAP_S或GL_TEXTURE_WRAP_T GL_REPEAT、GL_CLAMP_TO_EDGE

3.2.1 GL_REPEAT

一般我們給定紋理的在S與T方向的紋理坐標時都是在 [0,1]之間,但紋理在S與T方向的坐標值也是可以大於1的。
當給定紋理的在S與T方向的坐標值分別為[0,4]時,在紋理坐標的S與T方向上,若設置紋理重複方式均為 GL_REPEAT 重複紋理,那麼運行效果圖如下圖所示:

紋理重複

3.2.2 GL_CLAMP_TO_EDGE

當給定紋理的在S與T方向的坐標值分別為[0,4]時,在紋理坐標的S與T方向上,若設置紋理重複方式均為 GL_CLAMP_TO_EDGE 截取紋理,那麼運行效果圖如下圖所示:

紋理截取

附案例程式碼

該案例程式碼為Android 平台OpenGL ES實現舉例,有兩個作用:

  • 1、在Android平台,使用OpenGL ES通過載入灰度圖,構建山地圖形渲染效果;
  • 2、在Android平台,使用 OpenGLES 生成與使用Mipmap紋理,構建遠處模糊 近處清晰的效果。

案例源碼下載地址:
//download.csdn.net/download/aiwusheng/58430870

= THE END =

文章首發於公眾號」CODING技術小館「,如果文章對您有幫助,歡迎關注我的公眾號。
歡迎關注我的公眾號