OpenGL入門1.2:渲染管線簡介,畫三角形


每一個小步驟的源碼都放在了Github

的內容為插入注釋,可以先跳過

圖形渲染管線簡介

在OpenGL的世界里,任何事物是處於3D空間中的,而屏幕和窗口顯示的卻是2D,所以OpenGL干的事情基本就是把3D坐標轉變為適應屏幕的2D像素

3D坐標轉為2D坐標的處理過程是由OpenGL的圖形渲染管線管理的,圖形渲染管線可以被划分為兩個主要部分:

圖形渲染管線(Graphics Pipeline)大多譯為管線,實際上指的是一堆原始圖形數據途經一個輸送管道,期間經過各種變化處理最終出現在屏幕的過程

  1. 第一部分把你的3D坐標轉換為2D坐標
  2. 第二部分是把2D坐標轉變為實際的有顏色的像素

另外,2D坐標像素也是不同的概念,2D坐標精確表示一個點在2D空間中的位置,而2D像素是這個點的近似值,2D像素受到你的屏幕/窗口分辨率的限制

現在我們就簡單地講講圖形渲染管線內,數據處理的過程:

  • 管線接受一組3D坐標,然后把它們轉變為你屏幕上的有色2D像素輸出
  • 管線可以被划分為幾個階段,每個階段將會把前一個階段的輸出作為輸入
  • 所有這些階段都是高度專門化的(它們都有一個特定的函數),並且很容易並行執行
  • 由於它們具有並行執行的特性,當今大多數顯卡都有成千上萬的小處理核心,它們在GPU上為每一個(渲染管線)階段運行各自的小程序,從而在圖形渲染管線中快速處理你的數據,這些小程序叫做着色器(Shader)
  • 着色器有好幾種,其中有些着色器允許開發者自己配置,以更細致地控制管線中的特定部分
  • 着色器運行在GPU上

OpenGL着色器是用OpenGL着色器語言(OpenGL Shading Language, GLSL)寫成的,我們之后再討論

下面是一個圖形渲染管線的每個階段的抽象展示,藍色的是我們可以注入自定義的着色器的部分

(如你所見,圖形渲染管線包含很多部分,每個部分都將在轉換頂點數據到最終像素這一過程中處理各自特定的階段,我們下面會概括性地解釋一下渲染管線的每個部分,從而對圖形渲染管線的工作方式有個大概了解)

圖元

我們需要先簡單了解下圖元

為了讓OpenGL知道我們的坐標和顏色值構成的到底是什么,你需要去指定這些數據所表示的渲染類型,比如說:傳入坐標等數據后,你想讓OpenGL把這些數據渲染成一系列的點?一系列的三角形?還是線?

以上要給OpenGL的這些信息就叫圖元(Primitive),任何一個繪制指令的調用都將是把圖元傳遞給OpenGL
這是其中的幾個:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP(點,三角形,線)

接下來正式進入渲染管線的介紹

渲染管線流程

假設我們的目的就是畫出一個三角形

首先,我們要以數組的形式傳遞3個3D坐標作為圖形渲染管線的輸入,用來表示一個三角形,一個3D坐標的數據的集合就是一個頂點(Vertex);這個數組就是一系列頂點的集合,我們叫他頂點數據(Vertex Data)

簡單起見,我們先假定每個頂點只由一個3D位置和一些顏色值組成


頂點數據就此進入圖形渲染管線的第一個部分,頂點着色器(Vertex Shader),它把一個單獨的頂點作為輸入,頂點着色器主要的目的是把輸入的3D坐標轉為另一種3D坐標(之后會解釋),同時對頂點屬性進行一些基本處理


之后進入圖元裝配(Primitive Assembly)階段,將頂點着色器輸出的所有頂點作為輸入,並所有的點裝配成指定圖元的形狀(這里的例子中是一個三角形,如果是GL_POINTS,那么就是一個個頂點)


圖元裝配階段的輸出會傳遞給幾何着色器(Geometry Shader),幾何着色器把圖元形式的一系列頂點的集合作為輸入,它可以通過產生新頂點構造出新的(或是其它的)圖元來生成其他形狀,在我們這里,它生成了另一個三角形


幾何着色器的輸出會被傳入光柵化階段(Rasterization Stage),這里它會把圖元映射為最終屏幕上相應的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)OpenGL中的一個片段是OpenGL渲染一個像素所需的所有數據
在片段着色器運行之前會執行裁切(Clipping),裁切會丟棄超出你的視圖以外的所有像素,用來提升執行效率


片段着色器(Fragment Shader)的主要目的是計算一個像素的最終顏色,這也是所有OpenGL高級效果產生的地方,通常,片段着色器包含3D場景的數據(比如光照、陰影、光的顏色等等),這些數據可被用來計算最終像素的顏色


在所有對應顏色值確定以后,最終的對象將會被傳到最后一個階段,我們叫做Alpha測試和混合(Blending)階段
這個階段檢測片段的對應的深度(和模板(Stencil))值,用以判斷這個像素是在前面還是后面,決定是否丟棄
這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)並對物體進行混合(Blend)
所以,即使在片段着色器中計算出來了一個像素輸出的顏色,在渲染多個三角形的時候最后的像素顏色也可能完全不同


可以看到,圖形渲染管線非常復雜,它包含很多可配置的部分

然而,對於大多數場合,我們只需要配置頂點片段着色器就行了(幾何着色器是可選的,通常使用它默認的着色器就行了)

在現代OpenGL中,我們也必須定義至少一個頂點着色器和一個片段着色器(GPU中沒有默認的頂點/片段着色器),因此剛開始學習的時候可能會非常困難,在你能夠渲染自己的第一個三角形之前,已經需要了解一大堆知識了

管線小結

  1. 首先,我們以數組的形式傳遞3個3D坐標作為圖形渲染管線的輸入,這個數組叫做頂點數據(Vertex Data),頂點數據是一系列頂點的集合,一個3D坐標的數據的集合就是一個頂點(Vertex)頂點數據是用頂點屬性(Vertex Attribute)表示的
  2. 頂點着色器(Vertex Shader),把一個單獨的頂點作為輸入,頂點着色器主要的目的是把3D坐標轉為另一種3D坐標(后面會解釋),同時頂點着色器允許我們對頂點屬性進行一些基本處理
  3. 圖元裝配(Primitive Assembly)階段將頂點着色器輸出的所有頂點作為輸入(如果是GL_POINTS,那么就是一個頂點),並所有的點裝配成指定圖元的形狀(這里的例子中是一個三角形)
  4. 幾何着色器(Geometry Shader)把圖元形式的一系列頂點的集合作為輸入,它可以通過產生新頂點構造出新的(或是其它的)圖元來生成其他形狀,在我們這里,它生成了另一個三角形
  5. 光柵化階段(Rasterization Stage)會把圖元映射為最終屏幕上相應的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment),在片段着色器運行之前會執行裁切(Clipping),裁切會丟棄超出你的視圖以外的所有像素,用來提升執行效率
  6. 片段着色器(Fragment Shader)的主要目的是計算一個像素的最終顏色,這也是所有OpenGL高級效果產生的地方,通常,片段着色器包含3D場景的數據(比如光照、陰影、光的顏色等等),這些數據可被用來計算最終像素的顏色
  7. Alpha測試和混合(Blending)階段檢測片段的對應的深度(和模板(Stencil))值,用以判斷這個像素是在前面還是后面,決定是否丟棄;這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)並對物體進行混合(Blend)

接下來我們將嘗試渲染一個三角形

頂點輸入

先記住以下三個單詞:

  • 頂點數組對象:Vertex Array Object,VAO
  • 頂點緩沖對象:Vertex Buffer Object,VBO
  • 索引緩沖對象:Element Buffer Object,EBO或Index Buffer Object,IBO

想要讓OpenGL繪制圖形,我們必須先給OpenGL喂一些頂點數據,頂點輸入在上面介紹的流程圖中很簡單,但實際上步驟並不少,過程並不簡單,希望各位耐心閱讀

首先OpenGL是一個3D圖形庫,所以我們在OpenGL中指定的所有坐標都是3D坐標(x,y,z)
OpenGL不是簡單地把所有的3D坐標變換為屏幕上的2D像素:僅當3D坐標在3個軸(x、y和z)上都為-1.0到1.0的范圍內時才處理它,而所有在所謂的標准化設備坐標(Normalized Device Coordinates)范圍內的坐標才會最終呈現在屏幕上

由於我們希望渲染一個三角形,我們一共要指定三個頂點,每個頂點都有一個3D位置,我們要將它們以標准化設備坐標的形式(OpenGL的可見區域)輸入,所以我們定義為一個float數組為頂點數據(Vertex Data)

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

由於OpenGL是在3D空間中工作的,而我們渲染的是一個2D三角形,我們將它頂點的 z 坐標設置為0.0,這樣子的話三角形每一點的深度(Depth)都是一樣的,從而使它看上去像是2D的

(通常深度可以理解為z坐標,它代表一個像素在空間中和你的距離,如果離你遠就可能被別的像素遮擋,你就看不到它了,它會被丟棄,以節省資源)

標准化設備坐標(Normalized Device Coordinates, NDC)

一旦你的頂點坐標已經在頂點着色器中處理過,它們就應該是標准化設備坐標了,標准化設備坐標是一個x、y和z值在-1.0到1.0的一小段空間,任何落在范圍外的坐標都會被丟棄/裁剪,不會顯示在你的屏幕上
下面你會看到我們定義的在標准化設備坐標中的三角形(忽略z軸):

你的標准化設備坐標接着會變換為屏幕空間坐標(Screen-space Coordinates),這是使用你通過glViewport函數提供的數據,進行視口變換(Viewport Transform)完成的,所得的屏幕空間坐標又會被變換為片段輸入到片段着色器中

頂點數據是要從CPU發往GPU上參與運算的,頂點數據通過CPU輸入到GPU的頂點着色器之前,我們先要在GPU上創建內存(顯存)空間,用於儲存我們的頂點數據,還要配置OpenGL如何讀懂這些數據,並且指定其如何發送給顯卡,然后才輪到頂點着色器處理我們在內存中指定的頂點

但是,從CPU把數據發送到GPU是一個相對較慢的過程,每個頂點發送一次耗費的時間將會非常大,所以我們要一次性發送盡可能多的數據,因此我們需要一個中介:頂點緩沖對象(Vertex Buffer Objects, VBO),來管理這內存,它會在GPU內存(顯存)中儲存大量頂點,因此我們就能一批一批發送大量頂點數據到GPU內存(顯存)了

而當數據儲存到GPU的內存(顯存)中后,頂點着色器幾乎能立即訪問頂點,這是個非常快的過程

頂點緩沖對象 VBO

VBO將輸入的頂點數據原封不動的存起來

和OpenGL中的其它對象一樣,這個緩沖必須要有一個獨一無二的ID,所以我們需要先生成一個整形ID,
再使用glGenBuffers函數,通過生成好的整形ID,生成一個VBO對象:

unsigned int VBO;		//生成一個ID
glGenBuffers(1, &VBO);	//glGenBuffers(緩沖區綁定對象目標數量,緩沖區對象名稱(ID))
						//glGenBuffers的就告訴你了它可以產生多個VBO,但是我們現在只要一個

你可能會問:“單獨生成id再用glGenBuffers返回這個id綁定的對象,這不是脫褲子放屁么?”

要注意,我們這里只生成一個VBO,看起來的確有點做作,但這個glGenBuffers是可以生成不止一個VBO的,如果你一次生成10個,第一個參數要改成10,你又要如何獲取這10個對象呢?這時候你就需要生成一個整形ID數組而不是一個整形ID

unsigned int VBO[10];	//生成一組ID
glGenBuffers(10, VBO);	//傳入VBO的ID數組的首地址

這才是這兩句最常用的用法

OpenGL有很多緩沖對象類型,頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER
OpenGL允許我們同時綁定多個緩沖,只要它們是不同的緩沖類型
我們可以使用glBindBuffer函數把新創建的緩沖綁定到GL_ARRAY_BUFFER目標上:

glBindBuffer(GL_ARRAY_BUFFER, VBO); //glBindBuffer(目標緩沖類型, 對象名稱(ID))

現在你才真正創建了一個VBO,接下來我們才是要把頂點數據存到VBO上,調用glBufferData函數:

glBufferData(
    GL_ARRAY_BUFFER,  //目標緩沖類型
    sizeof(vertices), //傳輸數據的大小
    vertices, 		  //發送的實際數據
    GL_STATIC_DRAW    //管理給定的數據的方式
);
//別忘了vertices數組就是我們的頂點數據

glBufferData是一個專門用來把用戶定義的數據復制到當前綁定緩沖的函數

  1. 第一個參數是目標緩沖的類型:頂點緩沖對象當前綁定到GL_ARRAY_BUFFER目標上
  2. 第二個參數指定傳輸數據的大小(以字節為單位);用一個簡單的sizeof計算出頂點數據大小就行
  3. 第三個參數是我們希望發送的實際數據
  4. 第四個參數指定了我們希望顯卡如何管理給定的數據,它有三種形式:
    • GL_STATIC_DRAW :數據不會或幾乎不會改變
    • GL_DYNAMIC_DRAW:數據會被改變很多
    • GL_STREAM_DRAW :數據每次繪制時都會改變

三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW
如果一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAWGL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分

最后總結一下我們干了什么:

  1. 建立了一批頂點數據存在vertices數組里
  2. 在顯存上創建了一個VBO
  3. 將頂點數據存在了VBO中,GPU可以通過VBO讀取頂點數據

下面我們會創建一個頂點着色器和一個片段着色器來讓GPU真正處理這些數據

着色器

如果我們打算做渲染的話,現代OpenGL需要我們至少設置一個頂點和一個片段着色器,接下來我們先簡單介紹一下着色器,然后配置兩個非常簡單的着色器:頂點着色器(Vertex Shader)片段着色器(Fragment Shader),用來來繪制我們第一個三角形

首先你要了解OpenGL中的向量

向量(Vector)

在圖形編程中我們經常會使用向量這個數學概念,因為它簡明地表達了任意空間中的位置和方向,並且它有非常有用的數學屬性。在GLSL中一個向量有最多4個分量,每個分量值都代表空間中的一個坐標,它們可以通過vec.xvec.yvec.zvec.w來獲取,注意vec.w分量不是用作表達空間中的位置的(我們處理的是3D不是4D),而是用在所謂透視除法(Perspective Division)上,我們之后回更詳細地討論向量

頂點着色器

還記得上面說的嗎?頂點着色器是我們圖形渲染管線的第一個部分,頂點着色器(Vertex Shader),它把一個單獨的頂點作為輸入,頂點着色器主要的目的是把3D坐標轉為另一種3D坐標,同時頂點着色器允許我們對頂點屬性進行一些基本處理

我們需要做的第一件事是學習使用着色器語言 GLSL(OpenGL Shading Language)編寫頂點着色器,然后編譯這個着色器,這樣我們就可以在程序中使用它了

和學初級語言時寫的HelloWorld一樣,下面我們先看一段非常簡單的頂點着色器的源代碼:

//頂點着色器
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

不難看出GLSL看起來很像C語言,接下來我們一句句看

每個着色器都起始於一個版本聲明,OpenGL 3.3以及和更高版本中,GLSL版本號和OpenGL的版本是匹配的(比如說GLSL 420版本對應於OpenGL 4.2),我們這里聲明使用3.30版本,並且使用核心模式

#version 330 core

下一步,使用in關鍵字,在頂點着色器中聲明所有的輸入頂點屬性(Input Vertex Attribute)
現在我們只關心位置(Position)數據,所以我們只需要一個頂點屬性
由於每個頂點都有一個3D坐標,我們就創建一個vec3輸入變量aPos

in vec3 aPos;

同時通過layout (location = 0)設定了輸入變量的位置值(Location)
(后面會看到為什么我們會需要這個位置值)

layout (location = 0) in vec3 aPos; //聲明輸入頂點屬性

為了設置頂點着色器的輸出,我們必須把位置數據賦值給預定義的gl_Position變量vec4類型)
在main函數里,gl_Position最后的值就是該頂點着色器的輸出

gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
//gl_Position的值即為輸出

由於我們的輸入是一個3分量的向量,我們必須把它轉換為gl_Position所對應的4分量的向量
我們可以把vec3的數據作為vec4構造器的參數,同時把w分量設置為1.0f(后面解釋為什么)

很簡單,就這樣寫完了,再看一次我們這個頂點着色器的代碼:

//頂點着色器
#version 330 core
layout (location = 0) in vec3 aPos; //聲明輸入頂點屬性

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    //gl_Position的值即為輸出
}

當前這個頂點着色器可能是我們能想到的最簡單的頂點着色器了,因為我們對輸入數據什么都沒有處理就把它傳到着色器的輸出了(在真實的程序里輸入數據通常都不是標准化設備坐標,所以我們首先必須先把它們轉換至OpenGL的可視區域內,但是現在我們可以先不考慮)

編譯頂點着色器

我們已經寫了一個頂點着色器源碼,但是為了能夠讓OpenGL使用它,我們必須在運行時動態編譯它的源碼,這和我之前在unity寫lua有點類似,我們寫的頂點着色器源碼將儲存在一個C的字符串中,所以上面寫的代碼你要這樣寫到main.cpp里:

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

(很惡心,但我們之后會通過文件讀寫解決這個問題的,不用着急)

我們首先要做的是創建一個着色器對象,注意還是用ID來引用的,所以我們儲存這個頂點着色器的ID為unsigned int,然后用glCreateShader創建這個着色器,我們把需要創建的着色器類型以參數形式提供給glCreateShader,由於我們正在創建一個頂點着色器,傳遞的參數是GL_VERTEX_SHADER

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

下一步我們把這個着色器源碼附加到着色器對象上,然后編譯它:

glShaderSource(
    vertexShader,		 //要編譯的着色器對象
    1, 					 //傳遞的源碼字符串數量
    &vertexShaderSource, //頂點着色器真正的源碼
    NULL
);
glCompileShader(vertexShader);

glShaderSource函數的參數:

  1. 第一個參數是要編譯的着色器對象
  2. 第二參數指定了傳遞的源碼字符串數量,這里只有一個
  3. 第三個參數是頂點着色器真正的源碼
  4. 第四個參數我們先設置為NULL

錯誤輸出(可忽略)

同時,我們希望檢測在調用glCompileShader后編譯是否成功了,如果沒成功的話,也希望知道錯誤是什么,這樣才能方便修復它們,檢測編譯時錯誤輸出可以通過以下代碼來實現:

首先我們定義一個整型變量success來表示是否成功編譯,還定義了一個儲存錯誤消息(出錯了才會有)的容器infoLog[],這是個char類型的數組,然后我們用glGetShaderiv函數檢查是否編譯成功,果編譯失敗,我們會用glGetShaderInfoLog獲取錯誤消息,然后打印它

以下函數都不難理解而且不太重要,不一一解釋了

int success;//是否成功編譯
char infoLog[512];//儲存錯誤消息
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//檢查是否編譯成功
if(!success)
{
	glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
	std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
else {
	std::cout << "vertexShader complie SUCCESS" << std::endl;
}

如果編譯的時候沒有檢測到任何錯誤,頂點着色器就被編譯成功了

片段着色器

片段着色器(Fragment Shader)是第二個我們打算創建用於渲染三角形的着色器

別忘了我們剛才通過頂點着色器輸出了gl_Position的值,現在我們要再片段着色器里處理他們

gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);

片段着色器(Fragment Shader)的主要目的是計算一個像素的最終顏色,這也是所有OpenGL高級效果產生的地方,通常,片段着色器包含3D場景的數據(比如光照、陰影、光的顏色等等),這些數據可被用來計算最終像素的顏色

在計算機圖形中顏色被表示為有4個元素的數組:紅色、綠色、藍色和alpha(透明度)分量,通常縮寫為RGBA
當在OpenGL或GLSL中定義一個顏色的時候,我們把顏色每個分量的強度設置在0.0到1.0之間
比如說我們設置紅為1.0f,綠為1.0f,我們會得到兩個顏色的混合色,即黃色
這三種顏色分量的不同調配可以生成超過1600萬種不同的顏色

#version 330 core
out vec4 FragColor;//只需要一個輸出變量

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

也是一句句看:

片段着色器只需要一個輸出變量,這個變量是一個4分量向量,它表示的是最終的輸出顏色,我們可以用out關鍵字聲明輸出變量,這里我們命名為FragColor

out vec4 FragColor;//只需要一個輸出變量

我們將一個alpha值為1.0(1.0代表完全不透明)的橘黃色的vec4賦值給顏色輸出FragColor

FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);

編譯片段着色器

編譯片段着色器的過程與頂點着色器類似,不過我們使用GL_FRAGMENT_SHADER常量作為着色器類型:

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

我們同樣用剛才的方法檢測編譯是否出錯:

int success;//是否成功編譯
char infoLog[512];//儲存錯誤消息
glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
if (!success) {
	glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
	std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
else {
	std::cout << "fragmentShader complie SUCCESS" << std::endl;
}

沒有檢測到任何錯誤,片段着色器也被編譯成功了


好了,現在兩個着色器現在都編譯了,總的代碼如下:

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
    ...
//build and compile 着色器程序(main內)
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
		//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
		//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	...
}

最后我們要把兩個着色器對象鏈接到一個用來渲染的着色器程序(Shader Program)

着色器程序

着色器程序對象(Shader Program Object)是多個着色器合並之后並最終鏈接完成的版本,如果要使用剛才編譯的着色器我們必須把它們鏈接(Link)為一個着色器程序對象,然后在渲染對象的時候激活這個着色器程序
已激活着色器程序的着色器將在我們發送渲染調用的時候被使用

當鏈接着色器至一個程序的時候,它會把每個着色器的輸出鏈接到下個着色器的輸入,如果輸出和輸入不匹配,就會得到一個連接錯誤

創建一個程序對象很簡單,像剛才一樣:

unsigned int shaderProgram;
shaderProgram = glCreateProgram();

glCreateProgram函數創建一個程序,並返回新創建程序對象的ID引用

現在我們需要把之前編譯的着色器附加到程序對象上,然后用glLinkProgram鏈接它們:

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

代碼應該很清楚,我們把着色器附加到了程序上,然后用glLinkProgram鏈接

檢測着色器程序

就像着色器的編譯一樣,我們也可以檢測鏈接着色器程序是否失敗,並獲取相應的日志

與上面不同,我們嘗試不調用glGetShaderivglGetShaderInfoLog,而是使用glGetProgramivglGetProgramInfoLog

glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
	glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
	...
}
else {
	std::cout << "shaderProgram complie SUCCESS" << std::endl;
}

如果着色器程序沒有報錯,我們通過glLinkProgram得到的就是一個程序對象,我們可以調用glUseProgram函數,用剛創建的程序對象作為它的參數,以激活這個程序對象:

glUseProgram(shaderProgram);//寫進渲染循環

在glUseProgram函數調用之后,每個着色器調用和渲染調用都會使用這個程序對象(也就是之前寫的着色器)了

對了,在把着色器對象鏈接到程序對象以后,記得刪除着色器對象,我們不再需要它們了:

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

現在,我們已經把輸入頂點數據發送給了GPU,並指示了GPU如何在頂點和片段着色器中處理它

但還沒結束,我們需要告訴OpenGL它該如何解釋內存中的頂點數據,以及它該如何將頂點數據鏈接到頂點着色器的屬性上

鏈接頂點屬性

頂點着色器允許我們指定任何以頂點屬性為形式的輸入,這使其具有很強的靈活性的同時,它還意味着我們還得告訴着色器:輸入數據的哪到哪是一個部分,每個部分對應什么

我們的頂點緩沖數據會被解析為下面這樣子:

  • 位置數據被儲存為32位(4字節)浮點值
  • 每個位置包含3個這樣的值
  • 在這3個值之間沒有空隙(或其他值),這幾個值在數組中緊密排列(Tightly Packed)
  • 數據中第一個值在緩沖開始的位置

有了這些信息我們就可以使用glVertexAttribPointer函數告訴OpenGL該如何解析頂點數據(應用到逐個頂點屬性上)了:

glVertexAttribPointer(
    0, 					//指定要配置的頂點屬性
    3, 					//指定頂點屬性的大小
    GL_FLOAT, 			//指定數據的類型
    GL_FALSE, 			//是否希望數據被標准化
    3 * sizeof(float), 	//連續的頂點屬性組之間的間隔
    (void*)0 			//偏移量
);
glEnableVertexAttribArray(0);

glVertexAttribPointer函數的參數非常多,這里逐一介紹它們:

  1. 第一個參數指定我們要配置的頂點屬性,還記得我們在頂點着色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0,因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0
  2. 第二個參數指定頂點屬性的大小,頂點屬性是一個vec3,它由3個值組成,所以大小是3
  3. 第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)
  4. 第四個參數定義我們是否希望數據被標准化(Normalize),如果我們設置為GL_TRUE,所有數據都會被映射到0(對於有符號型signed數據是-1)到1之間,我們把它設置為GL_FALSE
  5. 第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔,由於下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float),要注意的是由於我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用),一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子
  6. 最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換,它表示位置數據在緩沖中起始位置的偏移量(Offset),由於位置數據在數組的開頭,所以這里是0,我們會在后面詳細解釋這個參數

每個頂點屬性從一個VBO管理的內存中獲得它的數據,而具體是從哪個VBO(程序中可以有多個VBO)獲取則是通過在調用glVertexAttribPointer時綁定到GL_ARRAY_BUFFER的VBO決定的,由於在調用glVertexAttribPointer之前綁定的是先前定義的VBO對象,頂點屬性0現在會鏈接到它的頂點數據

現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性(頂點屬性默認是禁用的)

自此,所有東西都已經設置好了,再來總結一下我們干了什么:

  1. 建立了一批頂點數據存在vertices數組里
  2. 在顯存上創建了一個VBO
  3. 將頂點數據存在了VBO中,GPU可以通過VBO讀取頂點數據
  4. 建立了一個頂點和一個片段着色器
  5. 告訴了OpenGL如何把頂點數據鏈接到頂點着色器的頂點屬性上

在OpenGL中繪制一個物體,代碼會像是這樣:

// 0. 復制頂點數組到VBO緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 設置頂點屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//...
// 繪制代碼(渲染循環中)
while (!glfwWindowShouldClose(window))
{
    // 2. 渲染一個物體時要使用着色器程序
    glUseProgram(shaderProgram);
    // 3. 繪制物體
	someOpenGLFunctionThatDrawsOurTriangle();
}

每當我們繪制一個物體的時候都必須重復這一過程,看起來可能不多,但是如果有超過5個頂點屬性,上百個不同物體時(比如我們之后需要畫的10個正方體),綁定正確的緩沖對象,為每個物體配置所有頂點屬性很快就變成一件麻煩事

有沒有一些方法可以使我們把所有這些狀態配置儲存在另一個對象中,並且可以通過綁定這個對象來恢復狀態呢?

頂點數組對象

頂點數組對象(Vertex Array Object, VAO)可以像頂點緩沖對象那樣被綁定,任何隨后的頂點屬性調用都會儲存在這個VAO中,這樣的好處就是,當配置頂點屬性指針時,你只需要將那些調用執行一次,之后再繪制物體的時候只需要綁定相應的VAO就行了,這使在不同頂點數據和屬性配置之間切換變得非常簡單,只需要綁定不同的VAO就行了,剛剛設置的所有狀態都將存儲在VAO中

OpenGL的核心模式要求我們使用VAO,所以它知道該如何處理我們的頂點輸入,如果我們綁定VAO失敗,OpenGL會拒絕繪制任何東西

一個頂點數組對象會儲存以下這些內容:

  • glEnableVertexAttribArrayglDisableVertexAttribArray的調用
  • 通過glVertexAttribPointer設置的頂點屬性配置
  • 通過glVertexAttribPointer調用與頂點屬性關聯的頂點緩沖對象

所以通常我們是一個物體一個VAO

創建一個VAO和創建一個VBO很類似:

unsigned int VAO;
glGenVertexArrays(1, &VAO);

要想使用VAO,要做的只是使用glBindVertexArray綁定VAO

從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用,當我們打算繪制一個物體的時候,我們只要在繪制物體前簡單地把VAO綁定到希望使用的設定上就行了

這段代碼應該看起來像這樣:

// 初始化代碼,只運行一次 (除非你的物體頻繁改變)
// 1. 綁定VAO
glBindVertexArray(VAO);
// 2. 把頂點數組復制到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 設置頂點屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//...
// 繪制代碼(渲染循環中)
while (!glfwWindowShouldClose(window))
{
	// 4. 繪制物體
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    someOpenGLFunctionThatDrawsOurTriangle();
}

一般來說,我們打算繪制多個物體,就先要生成/配置每種物體的VAO(和必須的VBO及屬性指針),儲存它們供后面使用,繪制物體的時候就拿出相應的VAO,綁定它,繪制完后再解綁VAO

比如我們需要兩種不同的三角形時,首先需要兩個三角形的頂點數據

float firstTriangle[] = {
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
};
float secondTriangle[] = {
    0.0f, -0.5f, 0.0f,  // left
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};

然后創建兩個VAO,兩個VBO

unsigned int VBOs[2], VAOs[2];
glGenVertexArrays(2, VAOs);
glGenBuffers(2, VBOs);

分別設置

glBindVertexArray(VAOs[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glBindVertexArray(VAOs[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

然后是渲染指令(下面就會介紹)

glUseProgram(shaderProgram);
glBindVertexArray(VAOs[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(VAOs[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);

和釋放資源

glfwTerminate();
glDeleteVertexArrays(2, VAOs);
glDeleteBuffers(2, VBOs);

畫出三角形

終於到了這一刻了!終於能有點實效性的成果了朋友們,要想繪制我們想要的物體,OpenGL給我們提供了glDrawArrays函數,它使用當前激活的着色器,之前定義的頂點屬性配置,和VBO的頂點數據(通過VAO間接綁定)來繪制圖元

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(
    GL_TRIANGLES, //圖元的類型
    0, 			  //頂點數組的起始索引
    3 			  //繪制多少個頂點
);

glDrawArrays函數:

  1. 第一個參數是我們打算繪制的OpenGL圖元的類型,由於我們在一開始時說過,我們希望繪制的是一個三角形,這里傳遞GL_TRIANGLES給它
  2. 第二個參數指定了頂點數組的起始索引,我們這里填0
  3. 最后一個參數指定我們打算繪制多少個頂點,這里是3(我們只從我們的數據中渲染一個三角形,它只有3個頂點長)

現在嘗試編譯代碼,如果編譯通過了,你應該看到下面的結果:

這時候我們的代碼是這樣的:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	//連接着色器
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//檢查片段着色器是否編譯錯誤
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgram complie SUCCESS" << std::endl;
	}
	//連接后刪除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//頂點數據
	float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f
	};

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);

	// 初始化代碼
	// 1. 綁定VAO
	glBindVertexArray(VAO);
	// 2. 把頂點數組復制到緩沖中供OpenGL使用
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 3. 設置頂點屬性指針
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);



	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
	
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}

索引緩沖對象

在渲染頂點這一話題上我們還有最后一個需要討論的東西——索引緩沖對象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)

假設我們不再繪制一個三角形而是繪制一個矩形
我們可以繪制兩個三角形來組成一個矩形(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   // 左上角
};

可以看到,有幾個頂點疊加了:我們指定了右下角左上角兩次,一個矩形只有4個而不是6個頂點,這樣就產生50%的額外開銷

更好的解決方案是只儲存不同的頂點,並設定繪制這些頂點的順序,這樣子我們只要儲存4個頂點就能繪制矩形了,之后只要指定繪制的順序就行了

索引緩沖對象EBO就是干這個的,和頂點緩沖對象一樣,EBO也是一個緩沖,它專門儲存索引,OpenGL調用這些頂點的索引來決定該繪制哪個頂點

首先,我們先要定義(不重復的)頂點,和繪制出矩形所需的索引:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 0號點
    0.5f, -0.5f, 0.0f,  // 1號點
    -0.5f, -0.5f, 0.0f, // 2號點
    -0.5f, 0.5f, 0.0f   // 3號點
};
unsigned int indices[] = { // 注意索引從0開始!
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

你可以看到,當時用索引的時候,我們只定義了4個頂點,下一步我們需要創建索引緩沖對象:

與VBO類似,我們先綁定EBO然后用glBufferData把索引復制到緩沖里

unsigned int EBO;
glGenBuffers(1, &EBO);

同樣,和VBO類似,我們會把這些函數調用放在綁定和解綁函數調用之間,只不過這次我們把緩沖的類型定義為GL_ELEMENT_ARRAY_BUFFER

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

要注意的是,我們傳遞了GL_ELEMENT_ARRAY_BUFFER當作緩沖目標

最后一件要做的事是用glDrawElements來替換glDrawArrays函數,來指明我們從索引緩沖渲染

使用glDrawElements時,我們會使用當前綁定的索引緩沖對象中的索引進行繪制:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(
    GL_TRIANGLES, 	 //繪制的模式
    6,				 //繪制頂點的個數
    GL_UNSIGNED_INT, //索引的類型
    0 				 //偏移量
);

glDrawElements的參數:

  1. 第一個參數指定了我們繪制的模式,這個和glDrawArrays的一樣
  2. 第二個參數是我們打算繪制頂點的個數,這里填6,也就是說我們一共需要繪制6個頂點
  3. 第三個參數是索引的類型,這里是GL_UNSIGNED_INT
  4. 最后一個參數里我們可以指定EBO中的偏移量(或者傳遞一個索引數組,但是這是當你不在使用索引緩沖對象的時候),但是我們會在這里填寫0

glDrawElements函數從當前綁定到GL_ELEMENT_ARRAY_BUFFER目標的EBO中獲取索引,這意味着我們必須在每次要用索引渲染一個物體時綁定相應的EBO,還是有點麻煩
不過頂點數組對象同樣可以保存索引緩沖對象的綁定狀態,VAO綁定時正在綁定的索引緩沖對象會被保存為VAO的元素緩沖對象,綁定VAO的同時也會自動綁定EBO

當目標是GL_ELEMENT_ARRAY_BUFFER的時候,VAO會儲存glBindBuffer的函數調用,這也意味着它也會儲存解綁調用,所以確保你沒有在解綁VAO之前解綁索引數組緩沖,否則它就沒有這個EBO配置了

最后的初始化和繪制代碼現在看起來像這樣:

// 初始化代碼
// 1. 綁定頂點數組對象
glBindVertexArray(VAO);
// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設定頂點屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

//...

// .繪制代碼(渲染循環中)
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

運行程序,wow~awesome

線框模式(Wireframe Mode)

要想用線框模式繪制你的三角形,你可以通過glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)函數配置OpenGL如何繪制圖元

  1. 第一個參數表示我們打算將其應用到所有的三角形的正面和背面
  2. 第二個參數告訴我們用線來繪制

設定之后的繪制調用會一直以線框模式繪制三角形,直到我們用glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)將其設置回默認模式

可以看到這個矩形的確是由兩個三角形組成的,awesome!

現在我們完整的代碼如下:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	//連接着色器
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//檢查片段着色器是否編譯錯誤
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgram complie SUCCESS" << std::endl;
	}
	//連接后刪除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//頂點數據
	float vertices[] = {
		0.5f, 0.5f, 0.0f,   // 0號點
		0.5f, -0.5f, 0.0f,  // 1號點
		-0.5f, -0.5f, 0.0f, // 2號點
		-0.5f, 0.5f, 0.0f   // 3號點
	};
	unsigned int indices[] = { // 注意索引從0開始!
		0, 1, 3, // 第一個三角形
		1, 2, 3  // 第二個三角形
	};

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	unsigned int EBO;
	glGenBuffers(1, &EBO);

	// 初始化代碼
	// 1. 綁定頂點數組對象
	glBindVertexArray(VAO);
	// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	// 4. 設定頂點屬性指針
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	//線框模式wireframe
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
    glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}

擴展練習

兩個彼此相連的三角形

我們可以嘗試添加更多頂點到數據中,使用glDrawArrays,繪制兩個彼此相連的三角形

我們只需要更改頂點數組:

float vertices[] = {
    //第一個三角形
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
    //第二個三角形
    0.0f, -0.5f, 0.0f,  // left
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};

然后更改EBO設置(直接注了)

//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

渲染指令也要從

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

改成

glDrawArrays(GL_TRIANGLES, 0, 6);
// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

效果就很明顯了:

我的源代碼:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	//連接着色器
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//檢查片段着色器是否編譯錯誤
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgram complie SUCCESS" << std::endl;
	}
	//連接后刪除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//頂點數據
	//float vertices[] = {
	//	0.5f, 0.5f, 0.0f,   // 0號點
	//	0.5f, -0.5f, 0.0f,  // 1號點
	//	-0.5f, -0.5f, 0.0f, // 2號點
	//	-0.5f, 0.5f, 0.0f   // 3號點
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 3, // 第一個三角形
	//	1, 2, 3  // 第二個三角形
	//};

	float vertices[] = {
		//第一個三角形
		-0.9f, -0.5f, 0.0f,  // left 
		-0.0f, -0.5f, 0.0f,  // right
		-0.45f, 0.5f, 0.0f,  // top 
		//第二個三角形
		0.0f, -0.5f, 0.0f,  // left
		0.9f, -0.5f, 0.0f,  // right
		0.45f, 0.5f, 0.0f   // top 
	};

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	unsigned int EBO;
	glGenBuffers(1, &EBO);

	// 初始化代碼
	// 1. 綁定頂點數組對象
	glBindVertexArray(VAO);
	// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	// 4. 設定頂點屬性指針
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	////線框模式wireframe
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	//glDeleteBuffers(1, &EBO);
	
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}

當然你也可以用上EBO,更簡單,我們需要這樣改下頂點數組

float vertices[] = {
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};
unsigned int indices[] = { // 注意索引從0開始!
    0, 1, 2, // 第一個三角形
    1, 3, 4  // 第二個三角形
};

EBO設置和上文相同

結果是一樣的

參考源碼:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	//連接着色器
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//檢查片段着色器是否編譯錯誤
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgram complie SUCCESS" << std::endl;
	}
	//連接后刪除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//頂點數據
	//float vertices[] = {
	//	0.5f, 0.5f, 0.0f,   // 0號點
	//	0.5f, -0.5f, 0.0f,  // 1號點
	//	-0.5f, -0.5f, 0.0f, // 2號點
	//	-0.5f, 0.5f, 0.0f   // 3號點
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 3, // 第一個三角形
	//	1, 2, 3  // 第二個三角形
	//};

	float vertices[] = {
		-0.9f, -0.5f, 0.0f,  // left 
		-0.0f, -0.5f, 0.0f,  // right
		-0.45f, 0.5f, 0.0f,  // top 
		0.9f, -0.5f, 0.0f,  // right
		0.45f, 0.5f, 0.0f   // top 
	};
	unsigned int indices[] = { // 注意索引從0開始!
		0, 1, 2, // 第一個三角形
		1, 3, 4  // 第二個三角形
	};

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	unsigned int EBO;
	glGenBuffers(1, &EBO);

	// 初始化代碼
	// 1. 綁定頂點數組對象
	glBindVertexArray(VAO);
	// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	// 4. 設定頂點屬性指針
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	////線框模式wireframe
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}

使用不同的VAO和VBO創建相同的兩個三角形

表面的效果和之前是相同的,但是我們分別創建了兩個不同的VAO和兩個不同的VBO

所以頂點數據也要分成兩個數組

float firstTriangle[] = {
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
};
float secondTriangle[] = {
    0.0f, -0.5f, 0.0f,  // left
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};

VAO,VBo代碼如下:

//unsigned int VBO;
//glGenBuffers(1, &VBO);
//unsigned int VAO;
//glGenVertexArrays(1, &VAO);
//unsigned int EBO;
//glGenBuffers(1, &EBO);
unsigned int VBOs[2], VAOs[2];
glGenVertexArrays(2, VAOs);
glGenBuffers(2, VBOs);

//// 初始化代碼
//// 1. 綁定頂點數組對象
//glBindVertexArray(VAO);
//// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
//glBindBuffer(GL_ARRAY_BUFFER, VBO);
//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//// 4. 設定頂點屬性指針
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//glEnableVertexAttribArray(0);

glBindVertexArray(VAOs[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glBindVertexArray(VAOs[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

當然別忘了渲染指令

glUseProgram(shaderProgram);
//glBindVertexArray(VAO);
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(VAOs[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(VAOs[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);

和釋放資源

// 釋放之前的分配的所有資源
glfwTerminate();
//glDeleteVertexArrays(1, &VAO);
//glDeleteBuffers(1, &VBO);
//glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(2, VAOs);
glDeleteBuffers(2, VBOs);

參考源碼:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//檢查片段着色器是否編譯錯誤
	glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShader complie SUCCESS" << std::endl;
	}
	//連接着色器
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	//檢查片段着色器是否編譯錯誤
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgram complie SUCCESS" << std::endl;
	}
	//連接后刪除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//頂點數據
	//float vertices[] = {
	//	0.5f, 0.5f, 0.0f,   // 0號點
	//	0.5f, -0.5f, 0.0f,  // 1號點
	//	-0.5f, -0.5f, 0.0f, // 2號點
	//	-0.5f, 0.5f, 0.0f   // 3號點
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 3, // 第一個三角形
	//	1, 2, 3  // 第二個三角形
	//};

	//float vertices[] = {
	//	-0.9f, -0.5f, 0.0f,  // left 
	//	-0.0f, -0.5f, 0.0f,  // right
	//	-0.45f, 0.5f, 0.0f,  // top 
	//	0.9f, -0.5f, 0.0f,  // right
	//	0.45f, 0.5f, 0.0f   // top 
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 2, // 第一個三角形
	//	1, 3, 4  // 第二個三角形
	//};

	float firstTriangle[] = {
	-0.9f, -0.5f, 0.0f,  // left 
	-0.0f, -0.5f, 0.0f,  // right
	-0.45f, 0.5f, 0.0f,  // top 
	};
	float secondTriangle[] = {
		0.0f, -0.5f, 0.0f,  // left
		0.9f, -0.5f, 0.0f,  // right
		0.45f, 0.5f, 0.0f   // top 
	};

	//unsigned int VBO;
	//glGenBuffers(1, &VBO);
	//unsigned int VAO;
	//glGenVertexArrays(1, &VAO);
	//unsigned int EBO;
	//glGenBuffers(1, &EBO);
	unsigned int VBOs[2], VAOs[2];
	glGenVertexArrays(2, VAOs);
	glGenBuffers(2, VBOs);

	//// 初始化代碼
	//// 1. 綁定頂點數組對象
	//glBindVertexArray(VAO);
	//// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
	//glBindBuffer(GL_ARRAY_BUFFER, VBO);
	//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//// 4. 設定頂點屬性指針
	//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//glEnableVertexAttribArray(0);

	glBindVertexArray(VAOs[0]);
	glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	glBindVertexArray(VAOs[1]);
	glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	////線框模式wireframe
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		//glBindVertexArray(VAO);
		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(VAOs[0]);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindVertexArray(VAOs[1]);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindVertexArray(0);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
	//glDeleteVertexArrays(1, &VAO);
	//glDeleteBuffers(1, &VBO);
	//glDeleteBuffers(1, &EBO);
	glDeleteVertexArrays(2, VAOs);
	glDeleteBuffers(2, VBOs);

	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}

創建兩個着色器程序

第二個程序使用一個不同的片段着色器(頂點着色器無需改動),再次繪制這兩個三角形,讓其中一個輸出為黃色

兩套着色器代碼如下:

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//const char *fragmentShaderSource = "#version 330 core\n"
//"out vec4 FragColor;\n"
//"void main()\n"
//"{\n"
//"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
//"}\n\0";
const char *fragmentShader1Source = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
const char *fragmentShader2Source = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n"
"}\n\0";

參考源碼:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//const char *fragmentShaderSource = "#version 330 core\n"
//"out vec4 FragColor;\n"
//"void main()\n"
//"{\n"
//"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
//"}\n\0";
const char* fragmentShader1Source = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
const char* fragmentShader2Source = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n"
"}\n\0";

int main()
{
	// 實例化GLFW窗口
	glfwInit();//glfw初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本號
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	//(寬,高,窗口名)返回一個GLFWwindow類的實例:window
	if (window == NULL)
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 告訴GLFW我們希望每當窗口調整大小的時候調用改變窗口大小的函數
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad管理opengl函數指針,初始化glad
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		// 生成錯誤則輸出錯誤信息
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//build and compile 着色器程序
	//頂點着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	//檢查頂點着色器是否編譯錯誤
	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "vertexShader complie SUCCESS" << std::endl;
	}
	//片段着色器
	//unsigned int fragmentShader;
	//fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	//glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	//glCompileShader(fragmentShader);
	unsigned int fragmentShaderOrange;
	fragmentShaderOrange = glCreateShader(GL_FRAGMENT_SHADER);
	unsigned int fragmentShaderYellow;
	fragmentShaderYellow = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShaderOrange, 1, &fragmentShader1Source, NULL);
	glCompileShader(fragmentShaderOrange);
	glShaderSource(fragmentShaderYellow, 1, &fragmentShader2Source, NULL);
	glCompileShader(fragmentShaderYellow);

	//檢查片段着色器是否編譯錯誤
	//glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
	//if (!success) {
	//	glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
	//	std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	//}
	//else {
	//	std::cout << "fragmentShader complie SUCCESS" << std::endl;
	//}
	glGetShaderiv(fragmentShaderOrange, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShaderOrange, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShaderOrange complie SUCCESS" << std::endl;
	}
	glGetShaderiv(fragmentShaderYellow, GL_LINK_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShaderYellow, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "fragmentShaderYellow complie SUCCESS" << std::endl;
	}

	//連接着色器
	//unsigned int shaderProgram;
	//shaderProgram = glCreateProgram();
	//glAttachShader(shaderProgram, vertexShader);
	//glAttachShader(shaderProgram, fragmentShader);
	//glLinkProgram(shaderProgram);
	unsigned int shaderProgramOrange;
	shaderProgramOrange = glCreateProgram();
	unsigned int shaderProgramYellow;
	shaderProgramYellow = glCreateProgram();
	glAttachShader(shaderProgramOrange, vertexShader);
	glAttachShader(shaderProgramOrange, fragmentShaderOrange);
	glLinkProgram(shaderProgramOrange);
	glAttachShader(shaderProgramYellow, vertexShader);
	glAttachShader(shaderProgramYellow, fragmentShaderYellow);
	glLinkProgram(shaderProgramYellow);


	//檢查片段着色器是否編譯錯誤
	//glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	//if (!success) {
	//	glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
	//	std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	//}
	//else {
	//	std::cout << "shaderProgram complie SUCCESS" << std::endl;
	//}
	glGetProgramiv(shaderProgramOrange, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgramOrange, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgramOrange complie SUCCESS" << std::endl;
	}
	glGetProgramiv(shaderProgramYellow, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgramYellow, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	else {
		std::cout << "shaderProgramYellow complie SUCCESS" << std::endl;
	}

	//連接后刪除
	glDeleteShader(vertexShader);
	//glDeleteShader(fragmentShader);
	glDeleteShader(fragmentShaderOrange);
	glDeleteShader(fragmentShaderYellow);

	//頂點數據
	//float vertices[] = {
	//	0.5f, 0.5f, 0.0f,   // 0號點
	//	0.5f, -0.5f, 0.0f,  // 1號點
	//	-0.5f, -0.5f, 0.0f, // 2號點
	//	-0.5f, 0.5f, 0.0f   // 3號點
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 3, // 第一個三角形
	//	1, 2, 3  // 第二個三角形
	//};

	//float vertices[] = {
	//	-0.9f, -0.5f, 0.0f,  // left 
	//	-0.0f, -0.5f, 0.0f,  // right
	//	-0.45f, 0.5f, 0.0f,  // top 
	//	0.9f, -0.5f, 0.0f,  // right
	//	0.45f, 0.5f, 0.0f   // top 
	//};
	//unsigned int indices[] = { // 注意索引從0開始!
	//	0, 1, 2, // 第一個三角形
	//	1, 3, 4  // 第二個三角形
	//};

	float firstTriangle[] = {
	-0.9f, -0.5f, 0.0f,  // left 
	-0.0f, -0.5f, 0.0f,  // right
	-0.45f, 0.5f, 0.0f,  // top 
	};
	float secondTriangle[] = {
		0.0f, -0.5f, 0.0f,  // left
		0.9f, -0.5f, 0.0f,  // right
		0.45f, 0.5f, 0.0f   // top 
	};

	//unsigned int VBO;
	//glGenBuffers(1, &VBO);
	//unsigned int VAO;
	//glGenVertexArrays(1, &VAO);
	//unsigned int EBO;
	//glGenBuffers(1, &EBO);
	unsigned int VBOs[2], VAOs[2];
	glGenVertexArrays(2, VAOs);
	glGenBuffers(2, VBOs);

	//// 初始化代碼
	//// 1. 綁定頂點數組對象
	//glBindVertexArray(VAO);
	//// 2. 把我們的頂點數組復制到一個頂點緩沖中,供OpenGL使用
	//glBindBuffer(GL_ARRAY_BUFFER, VBO);
	//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//// 3. 復制我們的索引數組到一個索引緩沖中,供OpenGL使用
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//// 4. 設定頂點屬性指針
	//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//glEnableVertexAttribArray(0);

	glBindVertexArray(VAOs[0]);
	glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	glBindVertexArray(VAOs[1]);
	glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	////線框模式wireframe
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// 渲染循環
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		
		//glUseProgram(shaderProgram);
		//glBindVertexArray(VAO);
		//glDrawArrays(GL_TRIANGLES, 0, 6);

		glUseProgram(shaderProgramOrange);
		glBindVertexArray(VAOs[0]);
		glDrawArrays(GL_TRIANGLES, 0, 3);

		glUseProgram(shaderProgramYellow);
		glBindVertexArray(VAOs[1]);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		
		glBindVertexArray(0);

		// 檢查並調用事件,交換緩沖
		glfwSwapBuffers(window);

		// 檢查觸發什么事件,更新窗口狀態
		glfwPollEvents();
	}

	// 釋放之前的分配的所有資源
	glfwTerminate();
	//glDeleteVertexArrays(1, &VAO);
	//glDeleteBuffers(1, &VBO);
	//glDeleteBuffers(1, &EBO);
	glDeleteVertexArrays(2, VAOs);
	glDeleteBuffers(2, VBOs);

	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// 每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
	// 返回這個按鍵是否正在被按下
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
		glfwSetWindowShouldClose(window, true);
}


免責聲明!

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



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