一文詳解 紋理采樣與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紋理,需要為其構造4x4、2x2、1x1這三個紋理。
    如果一個三維空間中的矩形圖片在屏幕上占 6x6 像素點,那么紋理采樣過程就變成:
    首先是到 8x8 的紋理圖中進行線性采樣;
    其次是到 4x4 的紋理圖中進行線性采樣;
    然后把兩次采樣的結果進行加權平均,得到最后的采樣數據。
    因為整個過程一共進行了三次的線性采樣,所以這種方法叫做三線性采樣。
  • 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紋理,構建遠處模糊 近處清晰的效果。

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

= THE END =

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM