1. GLSL
GLSL,OpenGL Shader Language縮寫,即OpenGL着色器語言,是專門為圖形計算器量身定制的類C語言。GLSL包含一些針對向量和矩陣操作的有用特性,着色器程序就是使用該語言編寫。着色器程序的開頭總是要聲明版本,接着是輸入和輸出變量、uniform和main函數,每個着色器的入口點都是main函數,再這個函數中我們處理所有的輸入變量,并将結果賦值到輸出變量中。一個典型的着色器程序結構如下:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 處理輸入并進行一些圖形操作
...
// 輸出處理過的結果到輸出變量
out_variable_name = weird_stuff_we_processed;
}
變量及變量類型:
GLSL中沒有指針類型,對于變量的運算,GLSL要求隻有類型一緻時,變量才能夠完成賦值或其他對應的操作,而對于類型轉換可以通過對應的構造器實現。示例代碼如下:
【更多音視頻學習資料,點擊下方鍊接免費領取,先碼住不迷路~】
音視頻開發(資料文檔 視頻教程 面試題)(FFmpeg WebRTC RTMP RTSP HLS RTP)
//------------------------------------------
// 1. 标量
float a, b=1.5;
int c = 2;
bool success = false;
a = float(c); // int類型轉float類型
//------------------------------------------
// 2. 向量
// (1)通過向向量構造器傳入參數,來創建對應類型的向量;
// (2)使用.或[]操作符訪問向量的分量,約定(x,y,z,w)、(r,g,b,a)
// 和(s,t,p,q)分别表示與位置、顔色和紋理坐标相關的向量分量;
vec3 rgb = vec3(1.0); // 等價于rgb = vec3(1.0, 1.0, 1.0);
vec3 pos = vec3(1.0, 0.0, 0.5);
vec4 tmp = vec4(pos, 1.0); // 提供的參數是向量,該向量的數據類型需一緻
// 獲取位置向量x、y、z軸分量的值
flot x = pos.x; // 等價于x = pos[0]
flot y = pos.y; // 等價于y = pos[1]
flot z = pos.z; // 等價于z = pos[2]
vec3 posNew = pos.xxy; // 對向量中的元素重新組合,生成一個新的向量
//------------------------------------------
// 3. 矩陣
// (1) 矩陣的值以列的順序來存儲,構造矩陣時參數會按列的順序填充矩陣;
// (2) 矩陣是向量的組合,可以通過标量、向量或标量和向量的混合來構造,
// 使用[]操作符訪問矩陣某一列的值,即該列是一個向量;
// 矩陣案例:
// 1.0, 0.5, 0,0
// 0.0, 1.0, 1.0
// 0.5, 0.0, 1.0
mat3 tmp1 = mat3(1.0, 0.0, 0.5, // 第一列
0.5, 1.0, 0.0, // 第二列
0.0, 1.0, 1.0); // 第三列
mat4 tmp2 = mat4(1.0) // 标量參數,構造一個4x4的單位矩陣
// 向量參數,構造一個3x3的矩陣
vec3 col1 = vec3(1.0, 0.0, 0.5); // 第一列
vec3 col2 = vec3(0.0, 1.0, 0.0); // 第二列
vec3 col3 = vec3(1.0, 1.0, 1.5); // 第三列
mat3 tmp3 = mat3(col1, col2, col3);
// 4. 紋理采樣類型
// (1) sampler(采樣器)是GLSL提供的可供紋理對象使用的内建數據,其中,
// sampler1D,sampler2D,sampler3D 表示不同維度的紋理類型;
// (2) sampler通常在片元着色器中内定義,被uniform修飾符修飾,表示這個變量是不會被修改的;
// (3) sampler不能被外部賦值,隻能通過GLSL提供的内置函數賦值
// 比如TEXTURE2D函數來采樣紋理的顔色值(坐标素紋)。
#version 330 core
out vec4 fragColor; // 片段着色器輸出
in vec2 textureCoord; // 紋理坐标
uniform sampler2D ourTexture; // 紋理采樣器
void main(){
fragColor = texture(ourTexture, textureCoord); // 采集紋理的顔色
}
GLSL結構體定義:
struct 結構體名 {
成員變量;
成員變量;
...
} 結構體類型變量;
GLSL中的結構體定義和使用同C語言,示例代碼如下:
struct Light {
float intensity;
vec3 position;
} lightVar;
// 等價于
// struct Light {
// float intensity;
// vec3 position;
// };
// Light lightVar; // Light為新創建的結構體類型
// 訪問結構體成員變量
float intensity = lightVar.lightVar;
vec3 pos = lightVar.position
GLSL中創建數組與C語言類似,但需要注意以下兩點:
- 除了 uniform 變量之外,數組的索引隻允許使用常數整型表達式;
- GLSL隻支持一維數組,當數組作為函數的形參時必須指定其大小;
// 創建一個數組
// 注:Light為1.1.2定義的結構體類型
float frequencies[3];
uniform vec4 lightPosition[4];
const int numLights = 2;
Light lights[numLights];
float a[5];
float b[] = a;
float b[5] = a; // 等價
float a[5] = float[5](3.4, 4.2, 5.0, 5.2, 1.1);
float a[5] = float[](3.4, 4.2, 5.0, 5.2, 1.1); // 等價
// 數組賦值
float a[5];
a[0] = 1.0;
a[1] = 0.5;
...
a[4] = 0.5;
絕大多數的運算符與 C 語言中一緻。與 C 語言不同的是:GLSL 中對于參與運算的數據類型要求比較嚴格,即運算符兩側的變量必須有相同的數據類型。對于二目運算符(*,/, ,-),操作數必須為浮點型或整型,除此之外,乘法操作可以放在不同的數據類型之間,如浮點型、向量和矩陣等。
1.2.2 流程控制語句GLSL提供了if-else、switch-case-default(選擇),for、while或do..while(循環),discard、return、break和countiune(跳轉)控制語句,除了discard,其他功能與C一樣。
- 判斷語句
// if--else判斷
if(boolean_expression) {
/* 如果布爾表達式為真将執行的語句 */
} else {
/* 如果布爾表達式為假将執行的語句 */
}
// switch---case判斷
switch(expression){
case constant-expression :
statement(s);
break; /* 可選的 */
case constant-expression :
statement(s);
break; /* 可選的 */
/* 您可以有任意數量的 case 語句 */
default : /* 可選的 */
statement(s);
}
循環語句
// while循環
while(condition) {
statement(s);
}
// do..while循環
do {
statement(s);
} while(condition);
discard、break、return和continue
// break用于終止循環;
// continue用于結束本次循環;
// return用于終止循環,并終止當前函數執行,同時返回一個值給函數調用者;
// discard僅作用片段着色器中,用于抛棄片段着色器的當前所有操作
#version 330 core
out vec4 fragColor; // 片段着色器輸出
in vec2 textureCoord; // 紋理坐标
uniform sampler2D ourTexture; // 紋理采樣器
void main(){
// 采集紋理的顔色值textureColor
// 當紋理的顔色值等于vec3(1.0,0.0,0.0)時
// 抛棄當前片段着色器的所有操作
vec4 textureColor = texture(ourTexture, textureCoord);
if (textureColor.rgb == vec3(1.0,0.0,0.0))
discard;
fragColor = textureColor
}
GLSL中函數定義:
// 函數聲明
returnType functionName (type0 arg0, type1 arg1, ..., typen argn);
// 函數定義
returnType functionName (type0 arg0, type1 arg1, ..., typen argn)
{
// do some computation
return returnValue;
}
由上述定義可知,GLSL中的函數聲明、定義和使用都是與C一緻的,即先聲明再使用,并且如果函數沒有返回值,需将返回值類型設定為void,并且函數的參數需要指定具體類型,并指定限定符(in/out/inouts)、const修飾參數(可選)。GLSL中函數名可以被重栽,隻要相同函數名的參數列表(主要是指參數的類型和個數)不同就可以了。mian函數是着色器程序的入口。
【更多音視頻學習資料,點擊下方鍊接免費領取,先碼住不迷路~】
音視頻開發(資料文檔 視頻教程 面試題)(FFmpeg WebRTC RTMP RTSP HLS RTP)
1.4 限定符(Qualifiers)1.4.1 存儲限定符通過uniform設置三角形的顔色,示例代碼:
// 片段着色器
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 片段着色器輸出,在OpenGL程序代碼中設定這個變量
void main()
{
FragColor = ourColor;
}
// 調用glGetUniformLocation函數獲取着色器中uniform屬性的索引/位置值
// 再調用glUniform4f函數為uniform屬性設值
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
注:Uniform是一種從CPU中的應用向GPU中的着色器發送數據的方式,但uniform和頂點屬性有些不同。首先,uniform是全局的(Global)。全局意味着uniform變量必須在每個着色器程序對象中都是獨一無二的,而且它可以被着色器程序的任意着色器在任意階段訪問。第二,無論你把uniform值設置成什麼,uniform會一直保存它們的數據,直到它們被重置或更新。
注:varying、attribute已被棄用。
1.4.2 參數限定符GLSL 提供了一種特殊的限定符用來定義某個變量的值是否可以被函數修改。
示例代碼:
vec4 myFunc(inout float myFloat, // inout parameter out vec4 myVec4, // out parameter mat4 myMat4); // in parameter (default)
1.4.3 精度限定符
OpenGL ES 與 OpenGL 之間的一個區别就是在 GLSL 中引入了精度限定符。精度限定符可使着色器的編寫者明确定義着色器變量計算時使用的精度,變量可以選擇被聲明為低、中或高精度。精度限定符可告知編譯器使其在計算時縮小變量潛在的精度變化範圍,當使用低精度時,OpenGL ES 的實現可以更快速和低功耗地運行着色器,效率的提高來自于精度的舍棄,如果精度選擇不合理,着色器運行的結果會很失真。
示例代碼:
lowp float color; out mediump vec2 P; lowp ivec2 foo(lowp mat3); highp mat4 m;
除了精度限定符,還可以指定默認使用的精度。如果某個變量沒有使用精度限定符指定使用何種精度,則會使用該變量類型的默認精度。默認精度限定符放在着色器代碼起始位置,以下是一些用例:
precision highp float; precision mediump int;
當為 float 指定默認精度時,所有基于浮點型的變量都會以此作為默認精度,與此類似,為 int 指定默認精度時,所有的基于整型的變量都會以此作為默認精度。在頂點着色器中,如果沒有指定默認精度,則 int 和 float 都使用 highp,即頂點着色器中,未使用精度限定符指明精度的變量都默認使用最高精度。在片段着色器中,float 并沒有默認的精度設置,即片段着色器中必須為 float 默認精度或者為每一個 float 變量指明精度。
1.4.4 不可變限定符當着色器被編譯時,編譯器會對其進行優化,這種優化操作可能引起指令重排序(instruction reordering),指令重排序可能引起的結果是當兩個着色器進行相同的計算時無法保證得到相同的結果。通常情況下,不同着色器之間的這種值的差異是允許存在的。如果要避免這種差異,則可以将變量聲明為invariant,可以單獨指定某個變量或進行全局設置。
示例代碼:
1.5 内置變量與内置函數1.5.1 内置變量
// 使已經存在的變量值不可變 invariant gl_Position; // make existing gl_Position be invariant out vec3 Color; invariant Color; // make existing Color be invariant // 聲明一個變量時,使其不可變 invariant centroid out vec3 Color;
GLSL中還内置了其他的一些變量,我們在後續的學習中具體詳說。
1.5.2 内置函數GLSL中内置了很多函數實現對标量和向量的操作。
GLSL中還内置了很多其他函數,我們在後續的學習中具體詳說。
2. 紋理紋理(TEXTURE),即物體表面的樣子。在計算機的世界中,我們能夠繪制的僅僅是一些非常基礎的形狀,比如點、線、三角形,這些基礎顯然是無法将一個現實世界中的物體很好的描述在屏幕上的。通常我們通過紋理映射将物體表面圖片貼到物體的幾何圖形上面,完成貼圖的過程,将物體從現實世界中模拟到虛拟世界中。簡單來說,紋理就是一副隻讀圖像,而把一副圖像映射到圖形上的過程叫做紋理映射。從另一個角度來說它是一個隻讀數據容器,而通常它用于存儲圖像信息。
2.1 紋素紋素,即紋理元素(Texel),是紋理圖形的基本單元,用于定義三維對象的曲面。3D 對象曲面的基本單位是紋理,而 2D 對象由像素(Pixel)組成。在計算機圖形學當中,紋素代表了映射到三維表面的二維紋理的最小單元,紋素和像素很相似因為都代表了圖像的基礎單元,但是在貼圖中的紋素和圖片中的像素在顯示的時候是有區别的,在特殊的3D映射情況下,紋素和像素可以一一對應,但大多數情況下,他們并不能直接映射。 包含3D紋理的對象靠近觀看時紋素看起來相對較大,每個紋素可能存在好幾個像素,并且容易看出紋理樣式。當相同的紋理對象被移到更遠的距離時,紋理映射圖案看起來越來越小。最終,每個紋素可以變得小于像素。然後平均采用會被用來組合幾個紋素成為一個像素。如果對象足夠遠,或者其中一個小面與觀看視線夾角形成銳角,那麼紋素可能變得如此之小,使得圖案的本質在觀察圖像中丢失。
紋理圖像數據存在多種彩色格式(PixelFormat)和多種存儲格式(PixelType),比如GL_RGB、GL_RGBA、GL_LUMINANCE和GL_UNSIGNED_SHORT_4_4_4_4、GL_UNSIGNED_SHORT_5_6_5等,這兩個屬性共同決定了每一個紋理數據單元(紋素)的構成。
2.2 紋理坐标衆所周知,屏幕的坐标以屏幕左上角為原點,從原點出發沿右方向為x軸,沿下方向為y軸,坐标系中的每一個坐标頂點就是一個像素。同理,紋理也有自己的坐标,它是以紋理(位圖)的左下角為原點,從原點出發沿右方向為s軸,沿上方向為t軸,坐标系中的每一個坐标頂點就是一個紋素,每個紋素的坐标範圍位于(0,0)~(1,1)之間。屏幕坐标系和紋理坐标系示意圖如下:
在上文中我們了解到,OpenGL的頂點坐标系是在世界坐标系上,當頂點坐标經過頂點着色器處理後,将會被轉換(歸一化)為标準化設備坐标。假如我們需要将紋理圖片映射到OpenGL繪制的正方形上,就需要指定四邊形的每個頂點各自對應紋理的哪個部分,這樣每個頂點就會關聯着一個紋理坐标(Texture Coordinate),用來标明該從哪個部分采樣(Sampling)得到對應紋理顔色值,而這個映射的過程被稱之為紋理映射或貼圖,比如将紋理的ABCD坐标映射到正方形頂點abcd的結果如下圖(三)所示:
或許你會疑惑,為什麼圖(三)映射的結果圖片是倒置的?這是因為紋理的生成是由圖片像素來生成的,而圖片的存儲是從左上角開始的,由此可知,圖片左上角像素生成的紋理部分就在紋理左下角處,即圖片的左上角對應到了紋理的左下角,上下颠倒了。因此,如果要使圖片能夠按正常方向貼到正方形上,就需要對紋理坐标進行上下調換,即A與D調換,B與C調換。在OpenGL代碼中,頂點坐标和紋理坐标對應關系:
// 貼圖倒置 float mVertices[] = { // 頂點坐标 // 顔色 // 紋理坐标 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // B 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // C -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // D -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // A }; // 貼圖正常 float mVertices2[] = { // 頂點坐标 // 顔色 // 紋理坐标 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // B 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.1f, // C -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // D -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f // A };
【更多音視頻學習資料,點擊下方鍊接免費領取,先碼住不迷路~】
音視頻開發(資料文檔 視頻教程 面試題)(FFmpeg WebRTC RTMP RTSP HLS RTP)
2.3 紋理環繞紋理坐标的範圍通常是從[0, 0]到[1, 1],超過[0.0, 1.0]的範圍是允許的,而對與超出範圍的内容要如何顯示,這就取決于紋理的環繞方式(Wrapping mode)。OpenGL提供了多種紋理環繞方式:
各種環繞方式效果如下圖所示:
2.4 紋理過濾
紋理坐标不依賴于分辨率(Resolution),它可以是任意浮點值,所以OpenGL需要知道怎樣将 紋理像素(Texture Pixel)映射到紋理坐标。當你有一個很大的物體但是紋理的分辨率很低的時候這就變得很重要了,OpenGL對于上述情況會進行紋理過濾(Texture Filtering),主要提供了GL_NEAREST和GL_LINEAR兩種過濾方式,這兩種濾波方式依據不同的算法會得出不同的像素結果。
- GL_NEAREST
Nearest Neighbor Filtering,即鄰近濾波,是OpenGL默認的紋理過濾方式。當設置為GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理坐标的那個像素。下圖中你可以看到四個像素,加号代表紋理坐标。左上角那個紋理像素的中心距離紋理坐标最近,所以它會被選擇為樣本顔色:
- GL_LINEAR
(Bi)linear Filtering,即線性濾波,它會基于紋理坐标附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顔色。一個紋理像素的中心距離紋理坐标越近,那麼這個紋理像素的顔色對最終的樣本顔色的貢獻越大。下圖中你可以看到返回的顔色是鄰近像素的混合色。
兩種濾波效果如下圖:
由上圖可知,GL_NEAREST産生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素,而GL_LINEAR能夠産生更平滑的圖案,很難看出單個的紋理像素。相比于GL_NEAREST,GL_LINEAR可以産生更真實的輸出。
2.5 多級漸遠紋理(Mipmap)想象一下,假設我們有一個包含着上千物體的大房間,每個物體上都有紋理。有些物體會很遠,但其紋理會擁有與近處物體同樣高的分辨率。由于遠處的物體可能隻産生很少的片段,OpenGL從高分辨率紋理中為這些片段獲取正确的顔色值就很困難,因為它需要對一個跨過紋理很大部分的片段隻拾取一個紋理顔色。在小物體上這會産生不真實的感覺,更不用說對它們使用高分辨率紋理浪費内存的問題了。OpenGL使用一種叫做多級漸遠紋理(Mipmap)的概念來解決這個問題,它簡單來說就是一系列的紋理圖像,後一個紋理圖像是前一個的二分之一。多級漸遠紋理背後的理念很簡單:距觀察者的距離超過一定的阈值,OpenGL會使用不同的多級漸遠紋理,即最适合物體的距離的那個。由于距離遠,解析度不高也不會被用戶注意到。同時,多級漸遠紋理另一加分之處是它的性能非常好。讓我們看一下多級漸遠紋理是什麼樣子的:
OpenGL提供了glGenerateMipmaps函數為每個紋理圖像創建一系列多級漸遠紋理。特别的,在渲染中切換多級漸遠紋理級别(Level)時,OpenGL在兩個不同級别的多級漸遠紋理層之間會産生不真實的生硬邊界。就像普通的紋理過濾一樣,切換多級漸遠紋理級别時你也可以在兩個不同多級漸遠紋理級别之間使用NEAREST和LINEAR過濾。為了指定不同多級漸遠紋理級别之間的過濾方式,OpenGL提供了以下過濾方式來替換原有的GL_NEAREST和GL_LINEAR方式。
3. OpenGL實戰:繪制紋理
紋理圖像可能被儲存為各種各樣的格式,每種都有自己的數據結構和排列。OpenGL官方推薦使用stb_image.h來加載這些圖像,stb_image.h是SeanBarrett的一個非常流行的單頭文件圖像加載庫,它能夠加載大部分流行的文件格式,并且能夠很簡單得整合到我們的工程之中。 将一張圖像(紋理)映射到一個正方形上的步驟如下:
(1)創建紋理對象,并将其綁定到目标類型GL_TEXTURE_2D;
// 函數原型:void glGenTextures(GLsizei n, GLuint * textures); // // 函數作用:創建一個紋理對象,并返回唯一的ID // 參數說明: // n 表示要創建的紋理對象數量; // textures 表示要創建的所有紋理對象對應的ID GLuint mTextureId = NULL; glGenTextures(1, &mTextureId); // 函數原型:void glBindTexture(GLenum target,GLuint texture); // // 函數作用:綁定紋理對象到紋理目标GL_TEXTURE_2D // 參數說明: // target 指定紋理要綁定到的目标類型; // texture 表示紋理對象對應的ID; glBindTexture(GL_TEXTURE_2D, mTextureId);
注:調用glDeleteTextures()函數釋放這些紋理對象資源。
(2)設置紋理環繞和濾波方式;
// 函數原型:void glTexParameteri(GLenum target,GLenum pname,GLint param); // // 函數作用:設置紋理參數,比如環繞方式、濾波方式等 // 參數說明: // target 表示紋理對象ID; // pname 表示具體的紋理參數,比如GL_TEXTURE_WRAP_S為S軸的環繞方式; // param 指定紋理參數的值,詳細參考紋理主要的環繞方式和濾波方式; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // 設置s軸環繞方式為GL_REPEAT glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // 設置t軸環繞方式為GL_REPEAT glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 設置縮小過濾方式為線性過濾 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 設置放大過濾方式為線性過濾
(3)讀取本地圖片,生成紋理;
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" // 讀取本地圖片到内存 int imageWidth, imageHeight, imageChannelCount; unsigned char* data = stbi_load("D:/LearnOpenGL/src/lesson03/zhanlang.jpg", &imageWidth, &imageHeight, &imageChannelCount, 0); if (!data) { std::cout << "load image failed" << std::endl; return; } // 函數原型:void glTexImage2D(GLenum target,GLint level, // GLint internalformat,GLsizei width, // GLsizei height,GLint border,GLenum format, // GLenum type,const void * data) // // 函數作用:指定一個二維的紋理位圖(Texture Image) // 參數說明: // target 指定紋理目标; // level 指定圖片級别,0為基本圖像級别; // internalformat 指定紋理中顔色分量的數量,比如GL_R表示圖片像素包含R、G、B分量; // width 表示圖片的寬度; // height 表示圖片的高度; // border 具體含義不清楚,固定為0; // format 指定圖片的顔色格式,比如GL_RGB; // type 指定像素數據的數據類型,比如GL_UNSIGNED_BYTE; // data 指定圖片數據; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageWidth, imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, data); // 函數原型:void glGenerateMipmap(GLenum target); // // 函數作用:為指定的紋理對象生成Mipmap(多級漸遠紋理) // 參數說明: // target 表示紋理對象ID; glGenerateMipmap(GL_TEXTURE_2D); // 獲取紋理後,釋放圖片資源 stbi_image_free(data);
注:通過定義STB_IMAGE_IMPLEMENTATION,預處理器會修改頭文件,讓其隻包含相關的函數定義源碼,等于是将這個頭文件變為一個 .cpp 文件了。
(4)應用紋理。
當加載完紋理後,接下來就是如何将紋理映射到OpenGL繪制的正方形上,這個過程也稱“貼圖”。我将這部分分為三個步驟:首先,修改我們之前的頂點數據,即告訴OpenGL如何采樣紋理。每個頂點包含位置、顔色、紋理坐标屬性,且頂點位置屬性坐标要與紋理坐标映射一一對應。然後,再鍊接各個頂點屬性。OpenGL新的頂點格式如下:
相關代碼如下:
float vertices[] = { // 頂點坐标 // 顔色 // 紋理坐标 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.1f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // bottom left -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f // top left }; // 鍊接位置屬性,layout=0 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 鍊接顔色屬性,layout=1 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // 鍊接紋理坐标屬性,layout=2 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2);
接着我們需要調整頂點着色器使其能夠接受頂點坐标為一個頂點屬性,即頂點着色器的輸入的頂點數據由位置、顔色值、紋理坐标屬性構成,并将頂點的顔色值和紋理坐标作為頂點着色器的輸出,也就是片段着色器的輸入,片段着色器根據紋理坐标對紋理進行采樣,得到對應的紋理顔色值。相關代碼如下:
// 頂點着色器源碼 #version 330 core layout (location = 0) in vec3 aPos; // 着色器輸入 layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTextureCoord; out vec3 ourColor; // 着色器輸出 out vec2 ourTextureCoord; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; ourTextureCoord = aTextureCoord; }; // 片段着色器源碼 #version 330 core out vec4 rgbColor; // 片段着色器輸出 in vec3 ourColor; // 片段着色器輸入 in vec2 ourTextureCoord; uniform sampler2D ourTexture; // 采樣器 void main() { // 根據紋理坐标對紋理進行采樣 // 将采樣得到的紋理顔色顔色值賦值給rgbColor rgbColor = texture(ourTexture, ourTextureCoord); };
最後,将紋理對象綁定到紋理目标上,之後調用glDrawElements函數實現紋理顔色渲染,即該函數被調用後會自動把紋理賦值給片段着色器的采樣器。代碼如下:
glUseProgram(mShaderProgramId); glBindVertexArray(mVAOId); // 函數原型:void glBindTexture(GLenum target,GLuint texture) // // 函數作用:将紋理對象綁定到紋理目标上 // 參數說明: // target 用于指定要綁定到的紋理目标; // texture 用于指定被綁定的紋理對象ID; glBindTexture(GL_TEXTURE_2D, mTextureId); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
如果你對音視頻開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區讨論!
,