OpenGL(八) 座標系統

座標系統

在上一個教程中,我們學習瞭如何有效地利用矩陣的變換來對所有頂點進行變換。OpenGL希望在每次頂點着色器運行後,我們可見的所有頂點都爲標準化設備座標(Normalized Device Coordinate, NDC)。也就是說,每個頂點的x,y,z座標都應該在-1.0到1.0之間,超出這個座標範圍的頂點都將不可見。我們通常會自己設定一個座標的範圍,之後再在頂點着色器中將這些座標變換爲標準化設備座標。然後將這些標準化設備座標傳入光柵器(Rasterizer),將它們變換爲屏幕上的二維座標或像素。

將座標變換爲標準化設備座標,接着再轉化爲屏幕座標的過程通常是分步進行的,也就是類似於流水線那樣子。
在流水線中,物體的頂點在最終轉化爲屏幕座標之前還會被變換到多個座標系統(Coordinate System)。
將物體的座標變換到幾個過渡座標系(Intermediate Coordinate System)的優點在於,在這些特定的座標系統中,一些操作或運算更加方便和容易,這一點很快就會變得很明顯。對我們來說比較重要的總共有5個不同的座標系統:

  • 局部空間(Local Space,或者稱爲物體空間(Object Space))
  • 世界空間(World Space)
  • 觀察空間(View Space,或者稱爲視覺空間(Eye Space))
  • 裁剪空間(Clip Space)
  • 屏幕空間(Screen Space)

概述

爲了將座標從一個座標系變換到另一個座標系,我們需要用到幾個變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣。我們的頂點座標起始於局部空間(Local Space),在這裏它稱爲局部座標(Local Coordinate),它在之後會變爲世界座標(World Coordinate),觀察座標(View Coordinate),裁剪座標(Clip Coordinate),並最後以屏幕座標(Screen Coordinate)的形式結束。下面的這張圖展示了整個流程以及各個變換過程做了什麼:
在這裏插入圖片描述

  • 局部座標是對象相對於局部原點的座標,也是物體起始的座標。
  • 下一步是將局部座標變換爲世界空間座標,世界空間座標是處於一個更大的空間範圍的。這些座標相對於世界的全局原點,它們會和其它物體一起相對於世界的原點進行擺放。
  • 接下來我們將世界座標變換爲觀察空間座標,使得每個座標都是從攝像機或者說觀察者的角度進行觀察的。
  • 座標到達觀察空間之後,我們需要將其投影到裁剪座標。裁剪座標會被處理至-1.0到1.0的範圍內,並判斷哪些頂點將會出現在屏幕上。
  • 最後,我們將裁剪座標變換爲屏幕座標,我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位於-1.0到1.0範圍的座標變換到由glViewport函數所定義的座標範圍內。最後變換出來的座標將會送到光柵器,將其轉化爲片段

我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的座標系統中才有意義且更方便。
例如,當需要對物體進行修改的時候,在局部空間中來操作會更說得通;如果要對一個物體做出一個相對於其它物體位置的操作時,在世界座標系中來做這個才更說得通,等等。
如果我們願意,我們也可以定義一個直接從局部空間變換到裁剪空間的變換矩陣,但那樣會失去很多靈活性。

局部空間

局部空間是指物體所在的座標空間,即對象最開始所在的地方。想象你在一個建模軟件(比如說Blender)中創建了一個立方體。你創建的立方體的原點有可能位於(0, 0, 0),即便它有可能最後在程序中處於完全不同的位置。甚至有可能你創建的所有模型都以(0, 0, 0)爲初始位置(譯註:然而它們會最終出現在世界的不同位置)。所以,你的模型的所有頂點都是在局部空間中:它們相對於你的物體來說都是局部的

我們一直使用的那個箱子的頂點是被設定在-0.5到0.5的座標範圍中,(0, 0)是它的原點。這些都是局部座標

世界空間

如果我們將我們所有的物體導入到程序當中,它們有可能會全擠在世界的原點(0, 0, 0)上,這並不是我們想要的結果。我們想爲每一個物體定義一個位置,從而能在更大的世界當中放置它們。世界空間中的座標正如其名:是指頂點相對於(遊戲)世界的座標。如果你希望將物體分散在世界上擺放(特別是非常真實的那樣),這就是你希望物體變換到的空間。物體的座標將會從局部變換到世界空間;該變換是由模型矩陣(Model Matrix)實現的。

模型矩陣是一種變換矩陣,它能通過對物體進行位移、縮放、旋轉來將它置於它本應該在的位置或朝向。你可以將它想像爲變換一個房子,你需要先將它縮小(它在局部空間中太大了),並將其位移至郊區的一個小鎮,然後在y軸上往左旋轉一點以搭配附近的房子。你也可以把上一節將箱子到處擺放在場景中用的那個矩陣大致看作一個模型矩陣;我們將箱子的局部座標變換到場景/世界中的不同位置。

觀察空間

觀察空間經常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱爲攝像機空間(Camera Space)或視覺空間(Eye Space))。
觀察空間是將世界空間座標轉化爲用戶視野前方的座標而產生的結果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)裏,它被用來將世界座標變換到觀察空間。在下一節中我們將深入討論如何創建一個這樣的觀察矩陣來模擬一個攝像機。

裁剪空間

在一個頂點着色器運行的最後,OpenGL期望所有的座標都能落在一個特定的範圍內,且任何在這個範圍之外的點都應該被裁剪掉(Clipped)。被裁剪掉的座標就會被忽略,所以剩下的座標就將變爲屏幕上可見的片段。這也就是裁剪空間(Clip Space)名字的由來。

因爲將所有可見的座標都指定在-1.0到1.0的範圍內不是很直觀,所以我們會指定自己的座標集(Coordinate Set)並將它變換回標準化設備座標系,就像OpenGL期望的那樣。

爲了將頂點座標從觀察變換到裁剪空間,我們需要定義一個投影矩陣(Projection Matrix),它指定了一個範圍的座標,比如在每個維度上的-1000到1000。投影矩陣接着會將在這個指定的範圍內的座標變換爲標準化設備座標的範圍(-1.0, 1.0)。所有在範圍外的座標不會被映射到在-1.0到1.0的範圍之間,所以會被裁剪掉。在上面這個投影矩陣所指定的範圍內,座標(1250, 500, 750)將是不可見的,這是由於它的x座標超出了範圍,它被轉化爲一個大於1.0的標準化設備座標,所以被裁剪掉了。
在這裏插入圖片描述
由投影矩陣創建的觀察箱(Viewing Box)被稱爲平截頭體(Frustum),每個出現在平截頭體範圍內的座標都會最終出現在用戶的屏幕上
特定範圍內的座標轉化到標準化設備座標系的過程(而且它很容易被映射到2D觀察空間座標)被稱之爲投影(Projection),因爲使用投影矩陣能將3D座標投影(Project)到很容易映射到2D的標準化設備座標系中。

一旦所有頂點被變換到裁剪空間,最終的操作——視除法(Perspective Division)將會執行,在這個過程中我們將位置向量的x,y,z分量分別除以向量的齊次w分量;透視除法是將4D裁剪空間座標變換爲3D標準化設備座標的過程。這一步會在每一個頂點着色器運行的最後被自動執行

在這一階段之後,最終的座標將會被映射到屏幕空間中(使用glViewport中的設定),並被變換成片段

將觀察座標變換爲裁剪座標的投影矩陣可以爲兩種不同的形式,每種形式都定義了不同的平截頭體。
我們可以選擇創建一個正射投影矩陣(Orthographic Projection Matrix)或一個視投影矩陣(Perspective Projection Matrix)。

正射投影

正射投影矩陣定義了一個類似立方體的平截頭箱,它定義了一個裁剪空間,在這空間之外的頂點都會被裁剪掉。創建一個正射投影矩陣需要指定可見平截頭體的寬、高和長度。在使用正射投影矩陣變換至裁剪空間之後處於這個平截頭體內的所有座標將不會被裁剪掉。它的平截頭體看起來像一個容器:
在這裏插入圖片描述
上面的平截頭體定義了可見的座標,它由由寬、高、近(Near)平面和遠(Far)平面所指定。任何出現在近平面之前或遠平面之後的座標都會被裁剪掉。
正射平截頭體直接將平截頭體內部的所有座標映射爲標準化設備座標,因爲每個向量的w分量都沒有進行改變;如果w分量等於1.0,透視除法則不會改變這個座標

要創建一個正射投影矩陣,我們可以使用GLM的內置函數glm::ortho

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

前兩個參數指定了平截頭體的左右座標,第三和第四參數指定了平截頭體的底部和頂部。通過這四個參數我們定義了近平面和遠平面的大小,
然後第五和第六個參數則定義了近平面和遠平面的距離。
這個投影矩陣會將處於這些x,y,z值範圍內的座標變換爲標準化設備座標

正射投影矩陣直接將座標映射到2D平面中,即你的屏幕,但實際上一個直接的投影矩陣會產生不真實的結果,因爲這個投影沒有將透視(Perspective)考慮進去。所以我們需要透視投影矩陣來解決這個問題。

透視投影

如果你曾經體驗過實際生活給你帶來的景象,你就會注意到離你越遠的東西看起來更小。這個奇怪的效果稱之爲透視(Perspective)。透視的效果在我們看一條無限長的高速公路或鐵路時尤其明顯,正如下面圖片顯示的那樣:
在這裏插入圖片描述
正如你看到的那樣,由於透視,這兩條線在很遠的地方看起來會相交。這正是透視投影想要模仿的效果,它是使用透視投影矩陣來完成的
這個投影矩陣將給定的平截頭體範圍映射到裁剪空間,除此之外還修改了每個頂點座標的w值,從而使得離觀察者越遠的頂點座標w分量越大
被變換到裁剪空間的座標都會在-w到w的範圍之間(任何大於這個範圍的座標都會被裁剪掉)。OpenGL要求所有可見的座標都落在-1.0到1.0範圍內,作爲頂點着色器最後的輸出,因此,一旦座標在裁剪空間內之後,透視除法就會被應用到裁剪空間座標上
在這裏插入圖片描述
頂點座標的每個分量都會除以它的w分量,距離觀察者越遠頂點座標就會越小(離觀察者越遠的頂點座標w分量越大)。這是也是w分量非常重要的另一個原因,它能夠幫助我們進行透視投影。最後的結果座標就是處於標準化設備空間中的。如果你對正射投影矩陣和透視投影矩陣是如何計算的很感興趣(且不會對數學感到恐懼的話)我推薦這篇由Songho寫的文章

在GLM中可以這樣創建一個透視投影矩陣:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

同樣,glm::perspective所做的其實就是創建了一個定義了可視空間的大平截頭體,任何在這個平截頭體以外的東西最後都不會出現在裁剪空間體積內,並且將會受到裁剪。
一個透視平截頭體可以被看作一個不均勻形狀的箱子,在這個箱子內部的每個座標都會被映射到裁剪空間上的一個點。下面是一張透視平截頭體的圖片:
在這裏插入圖片描述
它的第一個參數定義了fov的值,它表示的是視野(Field of View),並且設置了觀察空間的大小。如果想要一個真實的觀察效果,它的值通常設置爲45.0f,但想要一個末日風格的結果你可以將其設置一個更大的值。
第二個參數設置了寬高比,由視口的寬除以高所得。
第三和第四個參數設置了平截頭體的近和遠平面。我們通常設置近距離爲0.1f,而遠距離設爲100.0f。所有在近平面和遠平面內且處於平截頭體內的頂點都會被渲染。
在這裏插入圖片描述當使用正射投影時,每一個頂點座標都會直接映射到裁剪空間中而不經過任何精細的透視除法(它仍然會進行透視除法,只是w分量沒有被改變(它保持爲1),因此沒有起作用)。
因爲正射投影沒有使用透視,遠處的物體不會顯得更小,所以產生奇怪的視覺效果。由於這個原因,正射投影主要用於二維渲染以及一些建築或工程的程序,在這些場景中我們更希望頂點不會被透視所幹擾。某些如 Blender 等進行三維建模的軟件有時在建模時也會使用正射投影,因爲它在各個維度下都更準確地描繪了每個物體。下面你能夠看到在Blender裏面使用兩種投影方式的對比:
在這裏插入圖片描述使用透視投影的話,遠處的頂點看起來比較小,而在正射投影中每個頂點距離觀察者的距離都是一樣的。

把它們都組合到一起

我們爲上述的每一個步驟都創建了一個變換矩陣:模型矩陣、觀察矩陣和投影矩陣。一個頂點座標將會根據以下過程被變換到裁剪座標:
在這裏插入圖片描述
注意矩陣運算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)。最後的頂點應該被賦值到頂點着色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪
在這裏插入圖片描述

進入3D

既然我們知道了如何將3D座標變換爲2D座標,我們可以開始使用真正的3D物體,而不是枯燥的2D平面了。

在開始進行3D繪圖時,我們首先創建一個模型矩陣。這個模型矩陣包含了位移、縮放與旋轉操作,它們會被應用到所有物體的頂點上,以變換它們到全局的世界空間。讓我們變換一下我們的平面,將其繞着x軸旋轉,使它看起來像放在地上一樣。這個模型矩陣看起來是這樣的:

glm::mat4 model = glm::mat4(1.0f);  // 初始化模型矩陣爲4x4單位矩陣
// 繞x軸旋轉,模型矩陣包含了一系列位移、縮放、旋轉,將局部座標變換到世界座標位置,只是這裏只進行了旋轉而已
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));  

通過將頂點座標乘以這個模型矩陣,我們將該頂點座標變換到世界座標。我們的平面看起來就是在地板上,代表全局世界裏的平面。

接下來我們需要創建一個觀察矩陣。我們想要在場景裏面稍微往後移動,以使得物體變成可見的(當在世界空間時,我們位於原點(0,0,0))。要想在場景裏面移動,先仔細想一想下面這個句子:

  • 將攝像機向後移動,和將整個場景向前移動是一樣的。

這正是觀察矩陣所做的,我們以相反於攝像機移動的方向移動整個場景。因爲我們想要往後移動,並且OpenGL是一個右手座標系(Right-handed System),所以我們需要沿着z軸的正方向移動。我們會通過將場景沿着z軸負方向平移來實現。它會給我們一種我們在往後移動的感覺。

這正是觀察矩陣所做的,我們以相反於攝像機移動的方向移動整個場景。因爲我們想要往後移動,並且OpenGL是一個右手座標系(Right-handed System),所以我們需要沿着z軸的正方向移動。我們會通過將場景沿着z軸負方向平移來實現。它會給我們一種我們在往後移動的感覺。
在這裏插入圖片描述在下一個教程中我們將會詳細討論如何在場景中移動。就目前來說,觀察矩陣是這樣的:

glm::mat4 view = glm::mat4(1.0f);   // 初始化觀察矩陣
// 觀察矩陣通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

最後我們需要做的是定義一個投影矩陣。我們希望在場景中使用透視投影,所以像這樣聲明一個投影矩陣

glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);

既然我們已經創建了變換矩陣,我們應該將它們傳入着色器。
首先,讓我們在頂點着色器中聲明一個uniform變換矩陣然後將它乘以頂點座標:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	// 注意乘法要從右往左讀,即先乘模型矩陣,再觀察矩陣,最後乘以透視投影矩陣
	gl_Position =  projection * view * model * vec(aPos, 1.0);
	ourColor = aColor;
	TexCoord = aTexCoord;
}

我們還應該將矩陣傳入着色器(這通常在每次的渲染迭代中進行,因爲變換矩陣會經常變動):

int modelLoc = glGetUniformLocation(ourShader.ID, "model");
int viewLoc = glGetUniformLocation(ourShader.ID, "view");
int projectionLoc = glGetUniformLocation(ourShader.ID, "projection");

// 設置uniform變量之前必須先激活着色器程序對象
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0][0]);
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0][0]);

我們的頂點座標已經使用模型、觀察和投影矩陣進行變換了,最終的物體應該會:

  • 稍微向後傾斜至地板方向。
  • 離我們有一些距離。
  • 有透視效果(頂點越遠,變得越小)。

結果圖:
在這裏插入圖片描述

更多的3D

到目前爲止,我們一直都在使用一個2D平面,而且甚至是在3D空間裏!所以,讓我們大膽地拓展我們的2D平面爲一個3D立方體。
要想渲染一個立方體,我們一共需要36個頂點(6個面 x 每個面有2個三角形組成 x 每個三角形有3個頂點),這36個頂點的位置如下:

float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 位置屬性
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
//glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)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);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);

爲了有趣一點,我們將讓立方體隨着時間旋轉:

model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));

然後我們使用glDrawArrays來繪製立方體,但這一次總共有36個頂點。

// 綁定VAO後會綁定紋理,它會自動把紋理賦值給片段着色器的採樣器
glBindVertexArray(VAO);   // 前面並沒有把VAO解綁定,其實這裏不重新綁定也是可以的,但有多個VAO就需要綁定
//glDrawArrays(GL_TRIANGLES, 0, 3);   // 從頂點數組對象中進行繪製
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);   // 矩形由兩個三角形組成,故是6個頂點,畫第一個箱子,會使用當前綁定的索引緩衝對象中的索引進行繪製
glDrawArrays(GL_TRIANGLES, 0, 36);

如果一切順利的話你應該能得到下面這樣的效果

這的確有點像是一個立方體,但又有種說不出的奇怪。立方體的某些本應被遮擋住的面被繪製在了這個立方體其他面之上。之所以這樣是因爲OpenGL是一個三角形一個三角形地來繪製你的立方體的,所以即便之前那裏有東西它也會覆蓋之前的像素。因爲這個原因,有些三角形會被繪製在其它三角形上面,雖然它們本不應該是被覆蓋的。

幸運的是,OpenGL存儲深度信息在一個叫做Z緩衝(Z-buffer)的緩衝中,它允許OpenGL決定何時覆蓋一個像素而何時不覆蓋。通過使用Z緩衝,我們可以配置OpenGL來進行深度測試。

Z緩衝

OpenGL存儲它的所有深度信息於一個Z緩衝(Z-buffer)中,也被稱爲深度緩衝(Depth Buffer)。GLFW會自動爲你生成這樣一個緩衝(就像它也有一個顏色緩衝來存儲輸出圖像的顏色)。深度值存儲在每個片段裏面(作爲片段的z值),當片段想要輸出它的顏色時,OpenGL會將它的深度值和z緩衝進行比較,如果當前的片段在其它片段之後,它將會被丟棄,否則將會覆蓋。這個過程稱爲深度測試(Depth Testing),它是由OpenGL自動完成的。

然而,如果我們想要確定OpenGL真的執行了深度測試,首先我們要告訴OpenGL我們想要啓用深度測試;它默認是關閉的。我們可以通過glEnable函數來開啓深度測試。glEnable和glDisable函數允許我們啓用或禁用某個OpenGL功能。這個功能會一直保持啓用/禁用狀態,直到另一個調用來禁用/啓用它。現在我們想啓用深度測試,需要開啓GL_DEPTH_TEST

glEnable(GL_DEPTH_TEST);

因爲我們使用了深度測試,我們也想要在每次渲染迭代之前清除深度緩衝(否則前一幀的深度信息仍然保存在緩衝中)。就像清除顏色緩衝一樣,我們可以通過在glClear函數中指定DEPTH_BUFFER_BIT位來清除深度緩衝:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

我們來重新運行下程序看看OpenGL是否執行了深度測試:
鏈接
就是這樣!一個開啓了深度測試,各個面都是紋理,並且還在旋轉的立方體!

更多的立方體!

現在我們想在屏幕上顯示10個立方體。每個立方體看起來都是一樣的,區別在於它們在世界的位置及旋轉角度不同。立方體的圖形佈局已經定義好了,所以當渲染更多物體的時候我們不需要改變我們的緩衝數組和屬性數組,我們唯一需要做的只是改變每個對象的模型矩陣來將立方體變換到世界座標系中

首先,讓我們爲每個立方體定義一個位移向量來指定它在世界空間的位置。我們將在一個glm::vec3數組中定義10個立方體位置:

	glm::vec3 cubePositions[] = {
		glm::vec3( 0.0f,  0.0f,  0.0f),
		glm::vec3( 2.0f,  5.0f, -15.0f),
		glm::vec3(-1.5f, -2.2f, -2.5f),
		glm::vec3(-3.8f, -2.0f, -12.3f),
		glm::vec3( 2.4f, -0.4f, -3.5f),
		glm::vec3(-1.7f,  3.0f, -5.5f),
		glm::vec3( 1.3f, -2.0f, -2.5f),
		glm::vec3( 1.5f,  2.0f, -2.5f),
		glm::vec3( 1.5f,  0.2f, -1.5f),
		glm::vec3(-1.3f,  1.0f, -1.5f)
	};

現在,在遊戲循環中,我們調用glDrawArrays 10次,但這次在我們渲染之前每次傳入一個不同的模型矩陣到頂點着色器中。我們將會在遊戲循環中創建一個小的循環用不同的模型矩陣渲染我們的物體10次。注意我們也對每個箱子加了一點旋轉

	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		//glClear(GL_COLOR_BUFFER_BIT);  // 清理顏色緩衝
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清理顏色緩衝和深度緩衝
		
		ourShader.setFloat("mixValue", mixValue);  // 循環前調用了use(),所以可以設置uniform

		glActiveTexture(GL_TEXTURE0);  // 在綁定紋理之前先激活紋理單元,只有一個紋理時,可以不用手動激活,默認激活
		glBindTexture(GL_TEXTURE_2D, texture1);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);

		glm::mat4 view = glm::mat4(1.0f);   // 初始化觀察矩陣
		// 觀察矩陣通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方
		view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

		glm::mat4 projection = glm::mat4(1.0f);  // 初始化投影矩陣
		projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);

		//int modelLoc = glGetUniformLocation(ourShader.ID, "model");
		int viewLoc = glGetUniformLocation(ourShader.ID, "view");
		int projectionLoc = glGetUniformLocation(ourShader.ID, "projection");

		// 設置uniform變量之前必須先激活着色器程序對象
		//glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0][0]);
		glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
		glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0][0]);

		// 綁定VAO後會綁定紋理,它會自動把紋理賦值給片段着色器的採樣器
		glBindVertexArray(VAO);   // 前面並沒有把VAO解綁定,其實這裏不重新綁定也是可以的,但有多個VAO就需要綁定
		//glDrawArrays(GL_TRIANGLES, 0, 3);   // 從頂點數組對象中進行繪製
		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);   // 矩形由兩個三角形組成,故是6個頂點,畫第一個箱子,會使用當前綁定的索引緩衝對象中的索引進行繪製
		// 循環改變10次模型矩陣,並繪10個箱子
		for (unsigned int i = 0; i < 10; ++i)
		{
			glm::mat4 model = glm::mat4(1.0f);
			model = glm::translate(model, cubePositions[i]);
			float angle = 20.0f * (i+1);
			// 讓角度隨時間變化,形成旋轉的效果
			model = glm::rotate(model, (float)glfwGetTime() * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
			ourShader.setMat4("model", model);

			glDrawArrays(GL_TRIANGLES, 0, 36);
		}

		glfwSwapBuffers(window);
		glfwPollEvents();

	}

完整源碼

// main.cpp

// 使用着色器類的代碼
#define STB_IMAGE_IMPLEMENTATION
#include <glad/glad.h>
#include <glfw3.h>
#include <stb_image.h>

// glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "../include/shader.h" // 從main.cpp所在文件夾開始檢索

#include <iostream>


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

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

float mixValue = 0.2f;   // 兩張紋理的混合比例


//int main(int argc, char** argv)
int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "learnOpenGL", nullptr, nullptr);

	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// 配置全局opengl狀態,啓用深度測試
	glEnable(GL_DEPTH_TEST);

	// 創建着色器類對象
	Shader ourShader("src/coord_system.vs", "src/coord_system.fs");  // 從工程文件目錄的位置開始檢索

	//float vertices[] = {
	//// 位置                    // 顏色              // 紋理座標
	//	0.5f,  0.5f,  0.0f,   1.0f, 0.0f, 0.0f,     1.0f, 1.0f, 
	//	0.5f, -0.5f,  0.0f,   0.0f, 1.0f, 0.0f,     1.0f, 0.0f, 
	//   -0.5f, -0.5f,  0.0f,   0.0f, 0.0f, 1.0f,     0.0f, 0.0f,
	//   -0.5f,  0.5f,  0.0f,   1.0f, 1.0f, 0.0f,     0.0f, 1.0f, 
	//};

	//unsigned int indices[] = {
	//0, 1, 3,  // 第一個三角形
	//1, 2, 3   // 第二個三角形
	//};

	// 局部空間座標
	float vertices[] = {
		// 位置               // 紋理
		-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
		 0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
		 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
		 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
		-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
		-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

		-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
		 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
		 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
		-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
		-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

		-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
		-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
		-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
		-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
		-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
		-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

		 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
		 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
		 0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

		-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
		 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
		 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
		-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
		-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

		-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
		 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
		 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
		-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
		-0.5f,  0.5f, -0.5f,  0.0f, 1.0f
	};

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

	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// 位置屬性
	//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
	//glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)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);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(2);


	// 加載並創建第一個紋理(opengl保證至少有16個紋理單元可供使用)
	unsigned int texture1;   // 如果要加載多個紋理,這裏設置爲數組
	glGenTextures(1, &texture1);
	glBindTexture(GL_TEXTURE_2D, texture1);
	// 爲當前綁定的紋理對象設置環繞、過濾方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//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_LINEAR);  // 縮小操作時過濾方式
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  // 放大操作時過濾方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	// 加載紋理圖片並附到紋理對象上
	int width, height, nrChannels;
	stbi_set_flip_vertically_on_load(true); // 告訴stb_image.h在y軸上翻轉加載的紋理,後面加載的所有紋理圖片都將翻轉
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
	if (data)
	{
		// 當調用glTexImage2D時,當前綁定的紋理對象就會被附加上紋理圖像
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);   // 生成多級漸遠紋理
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	// 生成了紋理和相應的多級漸遠紋理後,釋放圖像的內存
	stbi_image_free(data);


	// 加載並創建第二個紋理
	unsigned int texture2;
	glGenTextures(1, &texture2);
	glBindTexture(GL_TEXTURE_2D, texture2);
	// 爲當前綁定的第二個紋理對象設置環繞、過濾方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	// 加載紋理圖片並附到紋理對象上
	data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		// awesomeface.png具有透明度,因此是alpha通道,所以一定要告訴OpenGL數據類型是GL_RGBA
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	stbi_image_free(data);


	// 解除綁定
	//glBindBuffer(GL_ARRAY_BUFFER, 0);
	//glBindVertexArray(0);

	// 告訴opengl每個採樣器屬於哪個紋理單元(只需要做一次)
	ourShader.use();  // 在設置uniform之前,不要忘記激活/使用着色器
	//glUniform1i(glGetUniformLocation(ourShader.ID, "ourTexture1"), 0);
	ourShader.setInt("ourTexture1", 0);
	ourShader.setInt("ourTexture2", 1);

	glm::vec3 cubePositions[] = {
		glm::vec3( 0.0f,  0.0f,  0.0f),
		glm::vec3( 2.0f,  5.0f, -15.0f),
		glm::vec3(-1.5f, -2.2f, -2.5f),
		glm::vec3(-3.8f, -2.0f, -12.3f),
		glm::vec3( 2.4f, -0.4f, -3.5f),
		glm::vec3(-1.7f,  3.0f, -5.5f),
		glm::vec3( 1.3f, -2.0f, -2.5f),
		glm::vec3( 1.5f,  2.0f, -2.5f),
		glm::vec3( 1.5f,  0.2f, -1.5f),
		glm::vec3(-1.3f,  1.0f, -1.5f)
	};


	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		//glClear(GL_COLOR_BUFFER_BIT);  // 清理顏色緩衝
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清理顏色緩衝和深度緩衝


		ourShader.setFloat("mixValue", mixValue);  // 循環前調用了use(),所以可以設置uniform

		glActiveTexture(GL_TEXTURE0);  // 在綁定紋理之前先激活紋理單元,只有一個紋理時,可以不用手動激活,默認激活
		glBindTexture(GL_TEXTURE_2D, texture1);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);

		glm::mat4 view = glm::mat4(1.0f);   // 初始化觀察矩陣
		// 觀察矩陣通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方
		view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

		glm::mat4 projection = glm::mat4(1.0f);  // 初始化投影矩陣
		projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);

		//int modelLoc = glGetUniformLocation(ourShader.ID, "model");
		int viewLoc = glGetUniformLocation(ourShader.ID, "view");
		int projectionLoc = glGetUniformLocation(ourShader.ID, "projection");

		// 設置uniform變量之前必須先激活着色器程序對象
		//glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0][0]);
		glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
		glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0][0]);

		// 綁定VAO後會綁定紋理,它會自動把紋理賦值給片段着色器的採樣器
		glBindVertexArray(VAO);   // 前面並沒有把VAO解綁定,其實這裏不重新綁定也是可以的,但有多個VAO就需要綁定
		//glDrawArrays(GL_TRIANGLES, 0, 3);   // 從頂點數組對象中進行繪製
		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);   // 矩形由兩個三角形組成,故是6個頂點,畫第一個箱子,會使用當前綁定的索引緩衝對象中的索引進行繪製
		// 循環改變10次模型矩陣,並繪10個箱子
		for (unsigned int i = 0; i < 10; ++i)
		{
			glm::mat4 model = glm::mat4(1.0f);
			model = glm::translate(model, cubePositions[i]);
			float angle = 20.0f * (i+1);
			model = glm::rotate(model, (float)glfwGetTime() * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
			ourShader.setMat4("model", model);

			glDrawArrays(GL_TRIANGLES, 0, 36);
		}

		glfwSwapBuffers(window);
		glfwPollEvents();

	}

	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);

	glfwTerminate();

	return 0;
}


void framebuffer_size_callback(GLFWwindow * window, int width, int height)
{
	glViewport(0, 0, width, height);  // 視口
}


// 處理所有輸入:查詢GLFW是否按下/釋放了此幀的相關鍵,並做出相應的反應
void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)  // 鍵盤esc
	{
		glfwSetWindowShouldClose(window, true);
	}
	if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)     // 鍵盤up
	{
		mixValue += 0.01f;
		if (mixValue >= 1.0f)
			mixValue = 1.0f;
	}
	if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)  // 鍵盤down
	{
		mixValue -= 0.01f;
		if (mixValue <= 0.0f)
			mixValue = 0.0f;
	}
}
// shader.h

//#pragma once
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>   // OpenGL Mathematics 0.9.8.0

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>


class Shader
{
public:
	// 着色器程序ID
	unsigned int ID;

	// 構造着色器
	Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr);

	// 使用激活程序
	void use();

	// uniform工具函數,要設置uniform,必須要激活函數之後,即調用use()之後
	void setBool(const std::string &name, bool value) const;
	void setInt(const std::string &name, int value) const;
	void setFloat(const std::string &name, float value) const;

	void setVec2(const std::string &name, const glm::vec2 &value) const;
	void setVec2(const std::string &name, float x, float y) const;

	void setVec3(const std::string &name, const glm::vec3 &value) const;
	void setVec3(const std::string &name, float x, float y, float z) const;

	void setVec4(const std::string &name, const glm::vec4 &value) const;
	void setVec4(const std::string &name, float x, float y, float z, float w) const;

	void setMat2(const std::string &name, const glm::mat2 &mat) const;
	
	void setMat3(const std::string &name, const glm::mat3 &mat) const;

	void setMat4(const std::string &name, const glm::mat4 &mat) const;

private:
	// 用於檢查着色器編譯/鏈接錯誤的實用函數
	void checkCompileErrors(unsigned int shader, std::string type);

};

#endif
// shader.cpp

#include "../include/shader.h"


// 構造讀取並構造着色器
Shader::Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath)
{
	// 從文件路徑中獲取頂點/片段着色器
	std::string vertexCode;
	std::string fragmentCode;
	std::string geometryCode;

	std::ifstream vShaderFile;
	std::ifstream fShaderFile;
	std::ifstream gShaderFile;

	// 保證ifstream對象可以拋出異常:
	vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try
	{
		// 打開文件
		vShaderFile.open(vertexPath);
		fShaderFile.open(fragmentPath);
		
		std::stringstream vShaderStream, fShaderStream;  // try之外無法訪問try裏面定義的變量
		
		// 讀取文件的緩衝內容到數據流中
		vShaderStream << vShaderFile.rdbuf();
		fShaderStream << fShaderFile.rdbuf();

		// 關閉文件處理器
		vShaderFile.close();
		fShaderFile.close();

		// 轉換數據流到string
		vertexCode = vShaderStream.str();
		fragmentCode = fShaderStream.str();

		if (geometryPath != nullptr)
		{
			gShaderFile.open(geometryPath);
			std::stringstream gShaderStream;
			gShaderStream << gShaderFile.rdbuf();
			gShaderFile.close();
			geometryCode = gShaderStream.str();
		}
	}
	catch (std::ifstream::failure e)
	{
		std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
	}

	// string轉換爲char*
	const char* vShaderCode = vertexCode.c_str();
	const char* fShaderCode = fragmentCode.c_str();

	unsigned int vertex, fragment;

	// 頂點着色器
	vertex = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex, 1, &vShaderCode, NULL);
	glCompileShader(vertex);
	// 檢測編譯是否成功
	checkCompileErrors(vertex, "VERTEX");

	// 片段着色器
	fragment = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment, 1, &fShaderCode, NULL);
	glCompileShader(fragment);
	// 檢測編譯是否成功
	checkCompileErrors(fragment, "FRAGMENT");

	// 如果給定了幾何着色器,編譯幾何着色器
	unsigned int geometry;
	if (geometryPath != nullptr)
	{
		const char* gShaderCode = geometryCode.c_str();
		geometry = glCreateShader(GL_GEOMETRY_SHADER);
		glShaderSource(geometry, 1, &gShaderCode, NULL);
		glCompileShader(geometry);
		// 檢測編譯是否成功
		checkCompileErrors(geometry, "GEOMETRY");
	}

	// 着色器程序
	ID = glCreateProgram();
	glAttachShader(ID, vertex);
	glAttachShader(ID, fragment);
	if (geometryPath != nullptr)
	{
		glAttachShader(ID, geometry);
	}
	glLinkProgram(ID);
	// 檢測着色器鏈接是否成功
	checkCompileErrors(ID, "PROGRAM");

	// 鏈接成程序之後就不需要着色器對象了
	glDeleteShader(vertex);
	glDeleteShader(fragment);
	if (geometryPath != nullptr)
		glDeleteShader(geometry);
}


// 使用激活程序
void Shader::use()
{
	glUseProgram(ID);
}


// uniform工具函數
void Shader::setBool(const std::string &name, bool value) const
{
	glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setInt(const std::string &name, int value) const
{
	glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setFloat(const std::string &name, float value) const
{
	glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec2(const std::string &name, const glm::vec2 &value) const
{
	glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec2(const std::string &name, float x, float y) const
{
	glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec3(const std::string &name, const glm::vec3 &value) const
{
	glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec3(const std::string &name, float x, float y, float z) const
{
	glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec4(const std::string &name, const glm::vec4 &value) const
{
	glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setVec4(const std::string &name, float x, float y, float z, float w) const
{
	glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setMat2(const std::string &name, const glm::mat2 &mat) const
{
	glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setMat3(const std::string &name, const glm::mat3 &mat) const
{
	glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}

void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const
{
	glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	if (glGetUniformLocation(ID, name.c_str()) == -1)
	{
		std::cout << "Don't find " << name.c_str() << std::endl;
	}
}



// 用於檢查着色器編譯/鏈接錯誤的實用函數
void Shader::checkCompileErrors(unsigned int shader, std::string type)
{
	int  success;
	char infoLog[1024];
	if (type != "PROGRAM")
	{
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(shader, 1024, NULL, infoLog);
			std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- ------------------------------------ -- " << std::endl;
		}
	}
	else
	{
		glGetProgramiv(shader, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(shader, 1024, NULL, infoLog);
			std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------- -- " << std::endl;
		}
	}

}
// coor_system.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	// 注意乘法要從右往左讀,即先乘模型矩陣,再觀察矩陣,最後乘以透視投影矩陣
	gl_Position =  projection * view * model * vec4(aPos, 1.0);
	ourColor = aColor;
	TexCoord = aTexCoord;
}
// coord_system.fs

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
uniform float mixValue;

void main()
{
	//FragColor = vec4(ourColor, 1.0f);
	//FragColor = texture(ourTexture, TexCoord);
	//FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);
	FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), mixValue);
	//FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, vec2(1.0 - TexCoord.x, TexCoord.y)), 0.2);
}

練習

使用模型矩陣只讓是3倍數的箱子旋轉(以及第1個箱子),而讓剩下的箱子保持靜止。

	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		//glClear(GL_COLOR_BUFFER_BIT);  // 清理顏色緩衝
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清理顏色緩衝和深度緩衝


		ourShader.setFloat("mixValue", mixValue);  // 循環前調用了use(),所以可以設置uniform

		glActiveTexture(GL_TEXTURE0);  // 在綁定紋理之前先激活紋理單元,只有一個紋理時,可以不用手動激活,默認激活
		glBindTexture(GL_TEXTURE_2D, texture1);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);

		glm::mat4 view = glm::mat4(1.0f);   // 初始化觀察矩陣
		// 觀察矩陣通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方
		view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

		glm::mat4 projection = glm::mat4(1.0f);  // 初始化投影矩陣
		projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);

		//int modelLoc = glGetUniformLocation(ourShader.ID, "model");
		int viewLoc = glGetUniformLocation(ourShader.ID, "view");
		int projectionLoc = glGetUniformLocation(ourShader.ID, "projection");

		// 設置uniform變量之前必須先激活着色器程序對象
		//glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0][0]);
		glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
		glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0][0]);

		// 綁定VAO後會綁定紋理,它會自動把紋理賦值給片段着色器的採樣器
		glBindVertexArray(VAO);   // 前面並沒有把VAO解綁定,其實這裏不重新綁定也是可以的,但有多個VAO就需要綁定
		//glDrawArrays(GL_TRIANGLES, 0, 3);   // 從頂點數組對象中進行繪製
		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);   // 矩形由兩個三角形組成,故是6個頂點,畫第一個箱子,會使用當前綁定的索引緩衝對象中的索引進行繪製
		// 循環改變10次模型矩陣,並繪10個箱子
		for (unsigned int i = 0; i < 10; ++i)
		{
			glm::mat4 model = glm::mat4(1.0f);
			model = glm::translate(model, cubePositions[i]);
			float angle = 20.0f * (i+1);
			if (i % 3 == 0)
				model = glm::rotate(model, (float)glfwGetTime() * glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
			model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
			ourShader.setMat4("model", model);

			glDrawArrays(GL_TRIANGLES, 0, 36);
		}

		glfwSwapBuffers(window);
		glfwPollEvents();

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