[OpenGL]OpenGL紋理

1.紋理

紋理是一個2D圖片,甚至有可能是1D 和3D紋理,紋理是用來添加物體的細節,可以想象紋理是一張繪有磚塊的紙,貼在一面白色的牆上,這樣看起來牆就有磚塊外表了。
紋理除了圖像以外,紋理可以被用來存儲大量的數據,這些數據可以發送到着色器上

紋理座標在x和y軸上,範圍爲[0,1](用的2D紋理圖像),使用紋理座標獲取紋理顏色叫做採樣,紋理座標起始於(0,0),也就是紋理圖片的左下角,終於(1,1),即紋理圖片的右上角。

假如,我們現在要把一張磚塊的紙,貼到之前畫的三角形上。。。
爲了能夠把紋理映射到三角形上,需要指定三角形的每個頂點各自對應紋理的哪個部分,這樣每個頂點就會關聯着一個紋理座標(Texture Coordinate),用來標明從紋理圖像哪個部分採樣,之後再圖形的其他片段上進行插值。
在這裏插入圖片描述
如上圖,我們爲紅色的三角形指定3個紋理座標,

float texCoords[] = {
    0.0f, 0.0f, // 左下角
    1.0f, 0.0f, // 右下角
    0.5f, 1.0f // 上中
};

2.紋理環繞方式

紋理座標的範圍是從(0,0)到(0,1),那如果我們把紋理座標設置在範圍之外呢?OpenGL默認的行爲是重複這個紋理圖像
環繞類型:

(1)GL_REPEAT 對紋理的默認,重複紋理圖像
在這裏插入圖片描述
(2)GL_MIRRORED_REPEAT 和GL_REPEAT一樣,但是重複的圖片是鏡像放置的
在這裏插入圖片描述
(3)GL_CLAMP_TO_EDGE 紋理座標會被餘數在0和1之間,超出的部分會重複紋理座標邊緣,產生一種被拉伸效果
在這裏插入圖片描述
(4)GL_CLAMP_TO_BORDER 超出的座標爲用戶指定的邊緣顏色

使用glTexParameter 函數對單獨的一個座標軸設置(s,t,r)*

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

第一個參數指定了紋理目標,使用的是2D紋理,GL_TEXTURE_2D 第二個參數指定設置的選項與應用的紋理軸,並且指定s和t軸
第三個參數紋理環繞方式

如果我們選擇GL_CLAMP_TO_BORDER,還需要指定一個邊緣顏色,使用glTexParameterfv,
使用GL_TEXTURE_BORDER_COLOR

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

3.紋理過濾

紋理座標不依賴於分辨率,可以是任意浮點值,所以 OpenGL需要知道怎樣將紋理像素映射到紋理座標?

我們主要對OpenGL的GL_NEARESTGL_LINEAR兩種紋理過濾

紋理像素:當我們打開一張.jpg格式圖片,不斷放大我們就會發現圖片是由無數像素點組成的,這些點就是紋理像素
紋理座標是給模型頂點設置的數組,OpenGL以這個頂點的紋理座標數據去查找紋理圖像上的像素,然後進行採樣提取紋理像素的顏色

GL_NEAREST(鄰近過濾)是OpenGL默認的紋理過濾方式,當設置爲鄰近過濾的時候,OpenGL會選擇中心點最接近紋理座標的那個像素,如下圖:加號代表紋理座標,可以看到四個像素,左上角那個紋理像素的中心距離紋理座標最近,所以會被選擇爲樣本顏色
在這裏插入圖片描述

GL_LINEAR(線性過濾),線性過濾會基於紋理座標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理座標越近,那麼這個紋理像素的顏色對最終的顏色貢獻越大

在這裏插入圖片描述

當我們進行放大和縮小操作的時候可以設置紋理過濾選項,在縮小的時候使用鄰近過濾,被放大的時候使用線性過濾,我們需要使用glTexParameter*函數爲放大和縮小指定過濾方式。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

GL_LINEAR效果
在這裏插入圖片描述
GL_NEAREST效果
在這裏插入圖片描述

4.多級漸遠紋理

當我們處在一個房間中,這個房間包含着上千的物品,每個物品都有紋理,有些物體會很遠,但是紋理擁有與近處物品同樣高的分辨率,遠處的物體可能只產生很少的片段,OpenGL想從高分辨率紋理中爲這些片段獲取正確的顏色就很困難,會使小物體產生不真實的感覺。。。

另外,使用高分辨率的紋理浪費內存

OpenGL使用多級漸遠紋理來解決,簡單來說就是一系列的紋理圖像,後一個紋理圖像是前一個的二分之一
理念:距離觀察者的距離超過一定的閾值,OpenGL會使用不同的多級漸遠紋理,即最適合物體的距離的那個

OpenGL中用glGenerateMipmaps函數,在創建完一個紋理後調用,接下去的工作OpenGL會承擔

注意:在渲染中切換多級漸遠紋理的時候,OpenGL在兩個不同級別的多級漸遠紋理之間會產生不真實的便捷,可以在不同的多級漸遠紋理級別之間使用NEAREST和LINEAR過濾。

GL_NEAREST_MIPMAP_NEAREST:使用最鄰近的多級漸遠紋理來匹配像素大小,並使用鄰近插值進行紋理採樣
GL_LINEAR_MIPMAP_NEAREST:使用最鄰近的多級漸遠紋理級別,並使用線性插值進行採樣
GL_NEAREST_MIPMAP_LINEAR:在兩個最匹配像素大小的多級漸遠紋理之間進行線性插值,使用鄰近插值進行採樣
GL_LINEAR_MIPMAP_LINEAR:在兩個鄰近的多級漸遠紋理之間使用線性插值,並使用線性插值進行採樣

我們可以用glTexParameter*函數將過濾方式設置

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

5.加載與創建紋理

在使用紋理之前,我們必須把紋理加載進應用中,正常有兩種解決方式:

①選擇一個需要的文件格式,比如.png或者.jpeg,然後針對單個格式的圖像,寫一個加載器,把圖像轉化成字節序列,但是圖像文件格式這麼多,如果每個格式都需要寫一個加載器,那就太累了
②使用一個支持多種流行格式的圖像加載庫來解決,可以使用stb_image.h庫,可以從這下載:https://github.com/nothings/stb/blob/master/stb_image.h 當然,還有另一種方法,在我們的工程頭文件裏,添加一個新的C++的頭文件,命名爲,stb_image,將stb_image.h的代碼複製到其中, #include “stb_image.h”

通過定義STB_IMAGE_IMPLEMENTATION,預處理器會修改頭文件,會讓其只包含相關的函數定義源碼

#define STB_IMAGE_IMPLEMENTATION
#include"stb_image.h"
③用stbi_load函數加載圖像
GLint width, height, nrChannels;
	unsigned char *data = stbi_load("resource/6789.jpg", &width, &height, &nrChannels, STBI_rgb);

加載一張表情的紋理

第一個參數:圖像文件的位置
第二個參數:寬度
第三個參數:高度
第四個參數:顏色通道的個數
第五個參數:加載模式,STBI_rgb爲RGB,STBI_rgb_alpha爲RGBA,也可以爲0

④生成紋理,跟之前OpenGL創建對象一樣,使用ID引用
unsigned int texture;
glGenTextures(1, &texture);

glGenTextures函數首先需要輸入生成紋理的數量,然後把它們儲存在第二個參數的unsigned int數組中

⑤綁定紋理
glBindTexture(GL_TEXTURE_2D, texture);
⑥glTexImage2D用生成紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

第一個參數:紋理目標,設置GL_TEXTURE_2D意味着會生成與當前綁定的紋理對象在同一個目標上的紋理
第二個參數:紋理指定多級漸遠紋理的級別,我們填0,就是基本級別 第三個參數:告訴OpEnGLish,把紋理存儲爲哪種格式
第四,第五個參數:紋理的寬度和高度 第六個參數:一直都是0
第七個,第八個參數:源圖的格式和數據類型,我們使用RGB加載圖像,存儲爲char(byte)數組 第九個參數:真正的圖像數據

當調用glTexImage2D,當前綁定的紋理對象就會被附加上紋理圖像,如果我們要使用多級漸遠紋理,我們必須手動設置所有不同的圖像,就是不斷增加第二個參數,或者,我們直接調用glGenerateMipmap,會爲當前綁定的紋理自動生成所有需要的多級漸遠紋理

⑦釋放圖像
stbi_image_free(data);

注:我們剛開始加載圖像的時候,會出現紋理倒立的情況,這是因爲OpenGL要求y軸0.0座標是在圖片的底部的,但是圖片的y軸0.0座標通常在頂部
我們可以在加載圖像之前,加入語句:

stbi_set_flip_vertically_on_load(true);

效果如下:
片元着色器計算最終顏色:
FragColor = texture(ourTexture, TexCoord);

在這裏插入圖片描述

擴展:

我們在片元着色器中對最終顏色進行紋理和圖像進行混合:
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

在這裏插入圖片描述

代碼如下:

#include <GL/glew.h>
#include<gl\glut.h>
#include<iostream>

#define STB_IMAGE_IMPLEMENTATION
#include"stb_image.h"

void MyInit();
void reshape(int w, int h);
void display();

GLuint vboId;
GLuint vaoId;
unsigned int texture;

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(512, 512);
	glutCreateWindow("SHADER_RECTANGLE");

	glewInit();
	MyInit();
	glutReshapeFunc(&reshape);
	glutDisplayFunc(&display);
	glutMainLoop();
	return 0;
}

void MyInit()
{
	glClearColor(0.0,0.0, 0.0,0.0);
	const GLfloat vertices[] = {
		//     ---- 位置 ----       ---- 顏色 ----     - 紋理座標 -
		0.0f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   0.5f, 1.0f,   // 左上
		-0.5f,  0.0f, 0.0f,   0.0f, 1.0f, 0.0f,   0.0f, 0.0f,   // 左下
		0.5f,  0.0f, 0.0f,   0.0f, 0.0f, 1.0f,  1.0f, 0.0f,   // 右上
	};

	//創建VAO對象
	glGenVertexArrays(1, &vaoId);
	glBindVertexArray(vaoId);

	//創建VBO對象
	glGenBuffers(1, &vboId);
	glBindBuffer(GL_ARRAY_BUFFER, vboId);

	//創建紋理
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);

	//傳入VBO數據
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//解除VBO綁定
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}


void reshape(int w, int h)
{
	glViewport(0, 0,(GLsizei)w, (GLsizei)h);
}

void display()
{
	glClear(GL_COLOR_BUFFER_BIT);
	const char* vertex_shader =
		"#version 330\n"
		"layout (location = 0) in vec3 aPos;"// 位置變量的屬性位置值爲 0 
		"layout (location = 1) in vec3 aColor; "// 顏色變量的屬性位置值爲 1
		"layout (location = 2) in vec2 aTexCoord; "// 紋理變量的屬性位置值爲2
		"out vec3 OurColor;"//向片段着色器輸出一個顏色
		"out vec2 TexCoord;"//向片段着色器輸出
		"void main() {"
		"  gl_Position = vec4(aPos, 1.0);"
		"OurColor = aColor;"
		"TexCoord = aTexCoord;"
		"}";

	const char* fragment_shader =
		"#version 330\n"
		"out vec4 FragColor;"
		"in vec3 OurColor;"
		"in vec2  TexCoord;"
		"uniform sampler2D ourTexture;"
		"void main() {"
		"  FragColor = texture(ourTexture, TexCoord);"  //片元着色器最終顏色
		"}";

	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertex_shader, NULL);
	glCompileShader(vertexShader);
	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragment_shader, NULL);
	glCompileShader(fragmentShader);
	
	GLuint shader_programme = glCreateProgram();
	glAttachShader(shader_programme, fragmentShader);
	glAttachShader(shader_programme, vertexShader);
	glLinkProgram(shader_programme);

	glUseProgram(shader_programme);

	glDeleteShader(fragmentShader);
	glDeleteShader(vertexShader);



	//綁定VBO
	glBindBuffer(GL_ARRAY_BUFFER, vboId);

	//解釋頂點數據方式
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8* sizeof(float), 0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*) (3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);
        
        //倒立圖像處理
	stbi_set_flip_vertically_on_load(true);
        //加載圖像
	GLint width, height, nrChannels;
	unsigned char *data = stbi_load("resource/112233.jpg", &width, &height, &nrChannels, STBI_rgb);
        //生成紋理
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
	glGenerateMipmap(GL_TEXTURE_2D);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //釋放紋理
	stbi_image_free(data);
	//繪製模型
	glDrawArrays(GL_TRIANGLES, 0, 3);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(2);
    glutSwapBuffers();
}

6.紋理單元

在着色器中,sampler2D變量是一個uniform,我們可以用glUniform賦值,給紋理採樣器分配一個位置。一個紋理的位置值,稱爲紋理單元,一個紋理的默認紋理單元爲0,是默認的激活紋理單元,所以我們之前我們沒有分配一個位置值
紋理單元的主要目的是讓我們在着色器中可以使用多於一個的紋理。通過把紋理單元賦值給採樣器,我們可以一次綁定多個紋理,只要我們首先激活對應的紋理單元。就像glBindTexture一樣,我們可以使用glActiveTexture激活紋理單元,傳入我們需要使用的紋理單元。。
glActiveTexture(GL_TEXTURE0); // 在綁定紋理之前先激活紋理單元
glBindTexture(GL_TEXTURE_2D, texture);

注:
OpenGL至少保證有16個紋理單元供我們使用,也就是說我們可以激活從GL_TEXTURE0到GL_TEXTRUE15。它們都是按順序定義的,所以我們也可以通過GL_TEXTURE0 + 8的方式獲得GL_TEXTURE8,這在當我們需要循環一些紋理單元的時候會很有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章