總目錄
LearnOpenGL從入門到入魔(1):什么是OpenGL?
LearnOpenGL從入門到入魔(2):如何使用OpenGL?
LearnOpenGL從入門到入魔(3):著色器語言和坐標系統
LearnOpenGL從入門到入魔(4):光照(光斬訓礎,材質)
LearnOpenGL從入門到入魔(5):光照(光照貼圖,投光物)
1. OpenGL基本概念
1.1 影像渲染管線
?在OpenGL中,任何事物都在3D空間中,而螢屏和視窗卻是2D像素陣列,這導致OpenGL的大部分作業都是關于把3D坐標轉變為適應你螢屏的2D像素,3D坐標轉為2D坐標的處理程序是由OpenGL的圖形渲染管線(GraphicsPipeline,或稱管線)管理的,該程序可表述為將一堆原始圖形資料途經一個輸送管道,期間經過各種變化處理最終出現在螢屏的程序,圖形渲染管線可以被劃分為兩個主要部分:第一部分將輸入的3D坐標轉換為2D坐標,第二部分是把2D坐標轉變為實際的有顏色的像素,下圖表示一個圖形渲染管線的每個階段的抽線表示:

?藍色部分著色器允許我們自定義,即可編程著色器,包括
頂點著色器、幾何著色器和片段著色器,而幾何著色器通常使用默認的就可以了,其它部分也是用默認的即可,在現代OpenGL中,我們必須定義至少一個頂點著色器和一個片段著色器,因為GPU中沒有默認的頂點/片段著色器,
?圖形渲染管線各階段功能如下:
- 頂點著色器(Vertex Shader)
?頂點著色器接收一組頂點陣列作為輸入,這些頂點資料使用頂點屬性表示,它會將每個頂點的3D坐標轉換為另一種3D坐標,同時對該頂點的頂點屬性進行一些基本處理,然后再將經過處理后的頂點陣列,
- 頂點屬性
?頂點屬性指定了每個頂點的各種屬性,包括坐標(postion)、顏色(color)、法線(normal)以及紋理(Texture)等,下面演示了如何定義一組頂點資料,其中每個頂點包含了坐標、顏色和紋理屬性:
GLfloat vertex[ 4*(3+4+2)] = { //x,y,z, r,g,b,a s,t(紋理) -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 1.0f, // 第1個頂點 0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, // 第2個頂點 -0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, // 第3個頂點 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, // 第4個頂點 };?在OpenGL中,頂點屬性資料存盤在一段連續的記憶體空間,下圖展示了3個頂點的存盤情況,其中每個頂點的坐標和顏色屬性分別占3個float,即12位元組(BYTE):
- 形狀(圖元)裝配(SHAPE/Primitive Assembly)
?圖元(Primitive),或稱形狀,是指OpenGL的渲染型別,當我們將輸入的坐標和顏色渲染成具體的某種表示時,需要在呼叫OpenGL的指令時指定圖元,比如GL_POINTS表示點,GL_TRIANGLES表示三角形以及GL_LINE_STRIP表示一系列的連續直線等,因此,圖元裝配的作用時將頂點著色器輸出的所有頂點作為輸入,輸出制定的基本圖元,
- 幾何著色器(Gemometry Shader)
?幾何著色器把基本圖元形式的頂點的集合作為輸入,可以通過產生新頂點構造出新的(或是其他的)基本圖元來生成其他形狀,
- 光柵化(Rasterization Stage)
?光柵化階段將幾何著色器輸出圖元映射為最終螢屏上相應的像素,生成供片段著色器使用的片段(Fragment),在片段著色器運行之前會執行裁切,而裁切的目的即為丟棄超出我們視圖以外的所有像素,以便提升執行效率,
片段(Fragment):指OpenGL渲染一個像素所需的所有資料,
- 片段著色器(Fragment Shader)
?片段著色器以光柵化階段生成的片段作為輸入,它的作用是計算一個像素的最終顏色,這也是所有OpenGL高級效果產生的地方,通常,片段著色器包含3D場景的資料(比如光照、陰影、光的顏色等等),這些資料可以被用來計算最終像素的顏色值,
- 測驗與混合(Test & Blend)
?該階段的目的為檢測片段對應的深度值,用它們來判斷這個像素是其它物體的前面還是后面,決定是否應該丟棄,同時,也會檢查用于定義物體透明度的alpha值,并對物體進行混合,所以,即使在片段著色器中計算出來了一個像素輸出的顏色,在渲染多個有重疊的物體的時候最后的像素顏色也可能完全不同,
1.2 著色器
?圖形渲染管線接受一組3D坐標,然后把它們轉變為螢屏上的有色2D像素輸出,圖形渲染管線可以被劃分為幾個階段,每個階段將會把前一個階段的輸出作為輸入,所有這些階段都是高度專門化的(它們都有一個特定的函式),并且很容易并行執行,正是由于它們具有并行執行的特性,當今大多數顯卡都有成千上萬的小處理核心,它們在GPU上為每一個(渲染管線)階段運行各自的小程式,從而在圖形渲染管線中快速處理你的資料,這些小程式叫做著色器(Shader),OpenGL的著色器使用著色器語言GLSL(OpenGL Shading Language)撰寫,通過編譯頂點著色器的原始碼,就可以在程式中使用它,
1.2.1 頂點著色器
?頂點著色器接收一組頂點資料(VertextData)作為輸入,這些頂點資料使用頂點屬性表示,它會將每個頂點的3D坐標轉換為另一種3D坐標(標準化設備坐標),同時對該頂點的頂點屬性進行一些基本處理,需要注意的是,頂點著色器依次處理頂點集合中的頂點資料,比如,我們要繪制一個三角形,那么就需要向頂點著色器輸入3個頂點資料,且每個頂點的坐標(postion)是3D的,下列代碼描述一個非常簡單的頂點著色器原始碼:
// 注釋(1)
#version 330 core
// 注釋(2)
layout (location = 0) in vec3 aPos;
void main()
{
// 注釋(3)
// GLSL中一個向量最多有四個分量,使用vec4表示
// 每個分量值表示空間中的坐標,即
// vec.x 表示x軸坐標;
// vec.y 表示y軸坐標;
// vec.z 表示z軸坐標;
// vec.w 不用做表達空間的位置,主要應用再透視除法(后續解釋)
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
?注釋(1) 用于指明OpenGL的使用版本為3.3,采用核心模式;注釋(2) 有兩個作用,一是使用in關鍵字在頂點著色器中宣告所有的輸入頂點屬性(注:本例中只關心頂點的位置position資料),二是通過layout (location = 0)設定輸入變數的位置值Location;注釋(3) 用于設定頂點著色器的輸出,即將位置資料賦值給預定義變數gl_Position,該變數的型別為vec4,這是一個包含4個分量的向量,
- 標準化設備坐標
?當頂點坐標經過頂點著色器處理后,將會被轉換為
標準化設備坐標,所謂標準化設備坐標,是一個x、y和z值在-1.0~1.0的一小段空間,任何落在范圍外的坐標都會被丟棄或者裁剪,不會顯示在設備螢屏上,下圖展示了在標準化坐標中定義一個2D三角形,即忽略z軸,
1.2.2 片段著色器
?片段著色器用于計算一個像素的最終顏色值,該階段是OpenGL高級效果產生的地方,通常,片段著色器包含3D場景的資料,比如光照、陰影、光的顏色等等,這些資料將被用來計算最終像素的顏色值,在計算機圖形中,一個像素的顏色值由4個分量組成,即R(紅色)、G(綠色)、B(藍色)和A(透明度),就是我們熟知的RGBA,在OpenGL中,顏色的每個分量的取值范圍為0.0f~1.0f,顏色程度由淺色到深色,下列代碼演示了如何創建一個簡單的片段著色器:
// 指明OpenGL的使用版本為3.3,并采用核心模式
#version 330 core
// 定義片段著色器的輸出
// 輸出一個vec4型別的變數,該變數表示像素的顏色值
out vec4 FragColor;
void main()
{
// 對變數進行賦值
// 即每個像素的顏色為紅色
FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
1.3 VAO、VBO和EBO
1.3.1 VBO
?VBO,Vertex Buffer Object,即頂點緩沖物件,該物件用于管理GPU的一段記憶體(在GPU中又稱顯存),這段記憶體存盤了大量的頂點資料以供頂點著色器和片段著色器使用,使用這些VBO的好處就是我們可以一次性由CPU向GPU發送一大批資料,以提高著色器訪問頂點資料的效率,而不是每個頂點發送一次,畢竟CPU把資料發送到顯卡相對較慢,VBO創建程序:
- 首先,呼叫glGenBuffers函式創建一個VBO物件,并生成一個對應的唯一ID;
unsigned int mVBOId;
// 函式原型:glGenBuffers(GLsizei n, GLuint *buffers);
//
// 函式作用:創建一組緩沖物件,并指定各自對應的唯一ID,這里創建的是VAO物件
// 引數說明:
// n 表示VBO物件的數目
// buffers 指定這些VBO物件對應的唯一ID;
glGenBuffers(1, &mVBOId);
- 其次,呼叫glBindBuffer函式將創建的VBO物件(快取)系結到
GL_ARRAY_BUFFER目標上,OpenGL有很多緩沖物件型別,頂點緩沖物件的緩沖型別是GL_ARRAY_BUFFER,OpenGL允許我們同時系結多個緩沖,只要它們是不同的緩沖型別,
// 函式原型:glBindBuffer(GLenum target, GLuint buffer)
//
// 函式作用:系結VBO物件到指定目標快取型別
// 引數說明:
// target 指定該VBO物件要系結到的目標緩沖型別,比如GL_ARRAY_BUFFER;
// buffer 指定該VBO物件對應的ID;
glBindBuffer(GL_ARRAY_BUFFER, mVBOId);
- 第三,呼叫glBufferData函式將定義的頂點資料復制到當前系結的緩沖的記憶體中,
// 函式原型:
// glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
//
// 函式作用:拷貝頂點資料到GPU顯存
// 引數說明:
// target 指定當前快取記憶體(VBO)系結的目標,這里為GL_ARRAY_BUFFER
// size 指定發送頂點資料的大小(位元組)
// data 指定發送的頂點資料
// usage 指定GPU如何管理這些資料,有三種形式:
// GL_STATIC_DRAW :資料不會或幾乎不會改變;
// GL_DYNAMIC_DRAW:資料會被改變很多;
// GL_STREAM_DRAW :資料每次繪制時都會改變;
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
注釋:三角形的位置資料不會改變,每次渲染呼叫時都保持原樣,所以它的使用型別最好是GL_STATIC_DRAW,如果,比如說一個緩沖中的資料將頻繁被改變,那么使用的型別就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把資料放在能夠高速寫入的記憶體部分,
- 最后,使用完VBO后需要釋放相關資源,
// 函式原型:glDeleteBuffers(GLsizei n, GLuint *buffers);
//
// 函式作用:釋放一組VBO對所占資源
// 引數說明:
// n 表示VBO物件的數目
// buffers 指定這些VBO物件對應的唯一ID;
glDeleteBuffers(1, &mVBOId);
?下圖演示了VBO在記憶體中的存盤形式:

?其中,VBO1的每個頂點只包含位置(pos)屬性;VBO2的每個頂點包含了位置(pos)和顏色(color)屬性,然而,雖然我們已經把輸入頂點資料發送給了GPU,并指示了GPU如何在頂點和片段著色器中處理它,但是,OpenGL還不知道它該如何解釋記憶體中的頂點資料,以及它該如何將頂點資料鏈接到頂點著色器的屬性上,因此,在渲染之前,我們必須指定OpenGL該如何解釋頂點資料,而這個程序被稱之為鏈接頂點屬性,下圖展示了經過鏈接頂點屬性操作后,顯存中的頂點資料被決議后的樣子:

?從上圖可知,這段GPU顯存(快取)中連續存盤了三個頂點資料,即VEXTEX1、VEXTEX2、VEXTEX3,每個頂點由位置(pos)、顏色(color)和紋理三個屬性組成,其中,位置屬性包含X、Y、Z分量,每個分量占1float(=4位元組),因此位置屬性共占12個位元組;顏色屬性包含R、G、B分量,每個分量占1float(=4位元組),因此顏色屬性共占12個位元組;紋理屬性包含S、T分量,每個分量占1float(=4位元組),因此顏色屬性共占8個位元組,此外,兩個相鄰頂點之間相同的屬性之間的距離稱為為步長(STRIDE),即第一個頂點某個屬性起始分量到第二個頂點相同屬性的起始分量所占位元組數,在OpenGL中,我們使用glVertexAttribPointer函式告訴OpenGL該如何決議頂點資料,即將這些資料標識具體的頂點屬性,代碼如下:
// 函式原型:glVertexAttribPointer(GLuint index, GLint size, GLenum type,
// GLboolean normalized, GLsizei stride, const void *pointer);
//
// 函式作用:定義一個頂點屬性資料
// 引數說明:
// index 指定要配置的某個頂點屬性索引index,如著色器原始碼中layout(location=0)
// 表示將位置屬性索引設定為0,當我們希望將資料傳遞到這個位置屬性中,
// 直接傳入這個位置屬性的索引0即可;
// size 指定某個頂點屬性的大小,比如位置屬性,是一個vec3,因此大小是3;
// type 指定頂點屬性的型別,比如位置屬性,每個分量資料型別為浮點型;
// normalized 設定資料是否被標準化(歸一化),即資料被映射到-1~1之間;
// stride 指定步長(Stride)大小;
// pointer 指定頂點屬性資料在快取中起始位置的偏移量(offset);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 函式原型:glEnableVertexAttribArray(GLuint index);
//
// 函式作用:啟用頂點屬性
// 引數說明:
// index 頂點屬性索引
glEnableVertexAttribArray(0);
1.3.2 VAO
?VAO,Vertex Array Object,即頂點陣列物件,該物件存盤的是一系列指向頂點屬性存盤地址的指標,如上上圖所示,以VAO2為例,它的atttribute pointer 0指向VBO2中第一個頂點的位置屬性(pos[0])的存盤地址,atttribute pointer 1指向VBO2中第一個頂點的顏色屬性(col[0])的存盤地址,atttribute pointer 1指向VBO2中第二個頂點的位置屬性(pos[0])的存盤地址等等,使用頂點陣列物件的好處時,當我們配置鏈接好頂點屬性后,只需要初始化的時候執行一次,再后續的繪制物體的時候就無需再執行一遍拷貝資料到顯存,鏈接頂點屬性,而是直接系結相應的VAO即可,VAO創建程序如下:
- 首先,創建一組VAO物件,并為每個VAO物件生成一個對應的唯一ID;
unsigned int mVAOId;
// 函式原型:glGenVertexArrays(GLsizei n, GLuint *arrays);
//
// 函式作用:創建一組VAO物件
// 引數說明:
// n 表示VAO物件的數目
// arrays 指定這些VAO物件對應的唯一ID,是一個陣列;
glGenVertexArrays(1, &mVAOId);
- 然后,呼叫glBindVertexArray系結VAO,
// 函式原型: glBindVertexArray(GLuint array);
//
// 函式作用:系結一個VAO物件
// 引數說明:
// array 表示一個VAO物件對應的ID
glBindVertexArray(mVAOId);
- 最后,釋放VAO物件所占資源,
// 函式原型:glDeleteVertexArrays(GLsizei n, GLuint *arrays);
//
// 函式作用:創建一組VAO物件
// 引數說明:
// n 表示VAO物件的數目
// arrays 指定這些VAO物件對應的唯一ID,是一個陣列
glDeleteVertexArrays(1, &mVAOId);
1.3.3 EBO
?EBO,Element Buffer Object,即索引快取物件,顧名思義,索引緩沖物件是用來存盤索引資料的,所謂索引資料,是指一組索引,而這個索引就是頂點資料在陣列中的下標,索引緩沖物件的作用,就是用于解決繪制一個物體時同一個頂點被重復指定的問題,這對于擁有上千個三角形來說,作業量是巨大的,比如,我們要繪制一個矩形,在OpenGL中通常通過繪制兩個三角形實作,因為OpenGL主要處理三角形,此時輸入的頂點資料可為:
float vertices[] = {
// 第一個三角形
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, 0.5f, 0.0f, // 左上角
// 第二個三角形
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
?"聰明絕頂"的你應該知道,兩個三角形共用斜邊,這就意味著矩形的左下角和右上角的頂點被指定兩次,如上述陣列可知,那么有沒有一種方法,頂點陣列中只包含不同的頂點,當需要繪制有重復的頂點時,我們只需要通過陣列的下標將頂點資料取出來就好,有!這個方法就是索引緩沖物件,OpenGL呼叫這些頂點的索引來決定該繪制哪個頂點,
// 頂點陣列
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
// 索引陣列
unsigned int indices[] = { // 注意索引從0開始!
0, 1, 3, // 第一個三角形
1, 2, 3 // 第二個三角形
};
EBO創建、使用程序:
- 首先,創建一組EBO物件,并分別指定對應的唯一ID;
unsigned int mEBOId;
// 函式原型:glGenBuffers(GLsizei n, GLuint *buffers);
//
// 函式作用:創建一組緩沖物件,并指定各自對應的唯一ID,
// 引數說明:這里創建的是EBO物件
// n 表示EBO物件的數目
// buffers 指定這些EBO物件對應的唯一ID;
glGenBuffers(1, &mEBOId);
- 其次,系結EBO,再呼叫glBufferData將索引資料拷貝到索引緩沖區中,其中緩沖目標設定為
GL_ELEMENT_ARRAY_BUFFER;
// 函式原型:glBindBuffer(GLenum target, GLuint buffer);
//
// 函式作用:系結一個命名的緩沖區物件
// 引數說明:
// target 指定緩沖物件被系結到的目標型別,EBO的目標型別為GL_ELEMENT_ARRAY_BUFFER
// buffer 指定要系結緩沖物件的ID
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBOId);
// 函式原型:glBufferData(GLenum target,GLsizeiptr size,const void * data, GLenum usage);
//
// 函式作用:把資料拷貝到緩沖區,這里是把索引資料拷貝到EBO
// 引數說明:
// target 指定緩沖物件被系結到的目標型別;
// size 指定索引資料的大小,以位元組為單位;
// data 指定索引資料
// usage 指定GPU如何管理這些資料,有三種形式:
// GL_STATIC_DRAW :資料不會或幾乎不會改變;
// GL_DYNAMIC_DRAW:資料會被改變很多;
// GL_STREAM_DRAW :資料每次繪制時都會改變;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
- 第三,使用索引緩沖物件的索引資料,繪制圖形,使用前需要系結EBO,
glDrawElements函式從當前系結到GL_ELEMENT_ARRAY_BUFFER目標的EBO中獲取索引,由于VAO系結時正在系結的索引緩沖物件會被保存為VAO的元素緩沖物件,系結VAO的同時也會自動系結EBO,因此,我們再使用索引渲染一個物體時只需要再初始化時系結相應的EBO即可,
// 系結一個EBO物件
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBOId);
// 函式原型:void glDrawElements(GLenum mode,GLsizei count,GLenum type,const void * indices);
//
// 函式作用:從陣列獲取資料繪制圖元
// 引數說明:
// mode 指定渲染的圖元型別,比如三角形為GL_TRIANGLES;
// count 指定渲染元素的數量,本例正方形為6個頂點;
// type 指定索引資料元素型別,本例為無符號整型(unsigned int);
// indices 指定一個指向索引存盤位置的指標,本例起始位置為0;
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
- 最后,釋放緩沖物件所占資源,
// 函式原型:glDeleteBuffers(GLsizei n, GLuint *buffers);
//
// 函式作用:釋放緩沖物件所占資源
// 引數說明:
// n 表示緩沖物件的數目,這里表示EBO物件的數目;
// buffers 指定這些緩沖對應唯一ID,這里表示一組EBO物件對應的ID;
glDeleteBuffers(1, &mEBOId);
?下圖演示了EBO在記憶體中的存盤:

?從上圖可知,EBO物件會被保持為VAO的元素緩沖物件,
2. OpenGL實戰
?CPU與GPU通信程序:

?根據上圖可知,OpenGL的作業程序:
- 首先,我們從CPU將要繪制的形狀頂點資料和紋理資料傳遞到GPU顯存中,在GPU顯存中主要是通過VBO、VAO和EBO來管理這些資料;
- 其次,分別創建頂點著色器和片段著色器,并將頂點資料輸入給頂點著色器處理;
- 第三,創建一個程式物件,并將編譯好的著色器附加到這個程式物件,再將這些著色器鏈接起來;
- 最后,繪制圖形,
2.1 案例1:繪制三角形
1. 創建著色器
(1)創建著色器Shader物件,并回傳該物件對應的唯一ID;
GLuint mShaderId = NULL;
// 函式原型:GLuint glCreateShader( GLenum shaderType)
//
// 函式作用:創建一個Shader物件
// 引數說明:
// shaderType 指定要創建的Shader物件型別,
// 比如 GL_VERTEX_SHADER 表示頂點著色器
// GL_FRAGMENT_SHADER 表示片段著色器
// mShaderId = glCreateShader(GL_VERTEX_SHADER);
mShaderId = glCreateShader(GL_FRAGMENT_SHADER);
(2)將著色器原始碼附著到著色器,由于該案例是創建一個漸變色的三角形,因此,我們將頂點著色器的輸出作為片段著色器的輸入,以實作每個像素的顏色值盡可能不一致;
// 頂點著色器原始碼
const char* mVertexShaderStr = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n" // 頂點著色器輸入
"out vec3 outPos;\n" // 頂點著色器輸出
"void main()\n"
"{\n"
" outPos = aPos;"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
// 片段著色器原始碼
const char* mFragmentShaderStr = "#version 330 core\n"
"out vec4 rgbColor;\n" // 片段著色器輸出
"in vec3 outPos;\n" // 以頂點著色器的輸出變數作為輸入
"void main()\n"
"{\n"
" rgbColor = vec4(outPos, 1.0f);\n"
"}\n\0";
// 函式原型:glShaderSource(GLuint shader,GLsizei count,const GLchar **string,const GLint *length);
//
// 函式作用:替換著色器物件原始碼(將著色器原始碼附著到著色器物件中)
// 引數說明:
// shader 著色器物件ID;
// count 指定字串數量或者陣列中字串中的數量,本例字串數為1;
// string 著色器原始碼
// length 著色器原始碼string長度(以位元組為單位),通常傳入NULL會自動計算;
glShaderSource(mShaderId, 1, &shaderStr, NULL);
(3)編譯著色器物件,實質是編譯著色器物件中存盤的原始碼,以便于著色器程式能夠鏈接它,
// 函式原型:void glCompileShader(GLuint shader);
//
// 函式作用:編譯一個著色器物件
// 引數說明:
// shader 指定要編譯的著色器物件ID;
glCompileShader(mShaderId);
2. 創建程式Program
(1)創建一個程式物件,glCreateProgram()函式將創建一個空的程式物件,之前創建的著色器物件將都附著在這個程式物件上;
GLuint mShaderProgramId = NULL;
mShaderProgramId = glCreateProgram();
(2)將編譯好的著色器物件附著到程式物件;
// 函式原型:void glAttachShader(GLuint program, GLuint shader);
//
// 函式作用:附著著色器物件到程式物件
// 引數說明:
// program 創建的程式物件ID;
// shader 要附著到程式物件的著色器物件ID;
glAttachShader(mShaderProgramId, vertexShaderId);
glAttachShader(mShaderProgramId, fragmentShaderId);
(3)鏈接程式物件,只有在鏈接程式物件成功后,才能在后續呼叫glUseProgram函式使用該程式物件,從而讓著色器生效,當然,鏈接程式物件成功后,我們可以進行釋放著色器物件操作,以釋放相關資源,
// 函式原型:void glLinkProgram(GLuint program);
//
// 函式作用:鏈接程式物件
// 引數說明:
// program 創建的程式物件ID;
glLinkProgram(mShaderProgramId);
// 洗掉著色器物件
glDeleteShader(vertexShaderId);
glDeleteShader(fragmentShaderId);
3. 拷貝資料到顯存,并繪制三角形
(1)創建VBO、VAO物件,拷貝頂點資料到GPU顯存并鏈接頂點屬性;
// 1. 創建VAO物件
glGenVertexArrays(1, &mVAOId);
// 2. 創建VBO,拷貝資料到GPU顯存,再配置頂點屬性
// (1) 分別創建VAO物件
GLuint vboId = 0;
glGenBuffers(1, &vboId);
// (2) 系結VAO物件
glBindVertexArray(mVAOId);
// (3) 將新創建的緩沖系結到頂點緩沖型別GL_ARRAY_BUFFER上
glBindBuffer(GL_ARRAY_BUFFER, vboId);
// (4) 將頂點資料復制到緩沖的顯存供OpenGL使用
// 并指定顯卡管理資料模式為GL_STATIC_DRAW,即資料不會或幾乎不會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
// (5) 鏈接頂點屬性
glVertexAttribPointer(layout, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(layout);
// (6) 解綁VAO, VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
(2)使用程式物件,并系結VAO物件,然后呼叫glDrawArray函式繪制三角形,
// 函式原型:void glUseProgram(GLuint program);
//
// 函式作用:使用程式物件
// 引數說明:
// program 創建的程式物件ID
glUseProgram(mShaderProgramId);
// 函式原型:void glBindVertexArray(GLuint id);
//
// 函式作用:系結VAO物件
// 引數說明:
// id 創建的VAO物件對應的ID;
glBindVertexArray(mVAOId);
// 函式原型:void glDrawArrays(GLenum mode,GLint first,GLsizei count);
//
// 函式作用:繪制渲染形狀
// 引數說明:
// mode 指定渲染的圖元,比如三角形為GL_TRIANGLES;
// first 第一個元素的陣列下標,本例第一個頂點從0開始;
// count 繪制元素的數量,本例元素為3個頂點;
glDrawArrays(GL_TRIANGLES, 0, 3);
4. 釋放資源
// 釋放著色器物件資源
if (mShaderId != NULL) {
glDeleteShader(mShaderId);
mShaderId = NULL;
}
// 釋放VAO資源
if (mVAOId != NULL) {
glDeleteVertexArrays(1, &mVAOId);
mVAOId = NULL;
}
// 釋放VBO資源
if (vboId != NULL) {
glDeleteBuffers(1, &vboId);
vboId = NULL;
}
// 釋放程式物件資源
if (mShaderProgramId != NULL) {
glDeleteProgram(mShaderProgramId);
mShaderProgramId = NULL;
}
.
2.2 案例2:繪制正方形
?繪制正方形案例演示了如何使用EBO物件,現在我們在案例1代碼的基礎上作如下處理,
(1)創建EBO物件,在鏈接頂點屬性之前將索引資料拷貝到EBO快取;
// 頂點陣列
float data[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
// 索引陣列
unsigned int indices[] = { // 注意索引從0開始!
0, 1, 3, // 第一個三角形
1, 2, 3 // 第二個三角形
};
// 1. 創建VAO物件
glGenVertexArrays(1, &mVAOId);
// 2. 創建VBO,拷貝資料到GPU顯存,再配置頂點屬性
// (1) 分別創建VAO物件
GLuint vboId = 0;
glGenBuffers(1, &vboId);
// (2) 系結VAO物件
glBindVertexArray(mVAOId);
// (3) 將新創建的緩沖系結到頂點緩沖型別GL_ARRAY_BUFFER上
glBindBuffer(GL_ARRAY_BUFFER, vboId);
// (4) 將頂點資料復制到緩沖的顯存供OpenGL使用
// 并指定顯卡管理資料模式為GL_STATIC_DRAW,即資料不會或幾乎不會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
// 創建EBO物件
glGenBuffers(1, &mEBOId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBOId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// (5) 鏈接頂點屬性
glVertexAttribPointer(layout, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(layout);
// (6) 解綁VAO, VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
(2)呼叫glDrawElements函式使用索引資料渲染圖元;
glUseProgram(mShaderProgramId);
glBindVertexArray(mVAOId);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
?程式執行結果:
3. 參考文獻
1. LearnOpenGL中文檔案
2. OpenGL3 Reference Pages
Github原始碼:LearnOpenGL(如果覺得有用,記得給個小star哈~)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/239155.html
標籤:AI


