【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】
? 重點參考了 LearnOpenGL CN 的內容,但大部分知識內容,小編已作改寫,以方便讀者理解。
文章目錄
- 零、 成果預覽圖
- 一、 光照原理與投光物的配置
- 1.1 光照原理
- 1.2 投光物
- 二、馮氏光照模型
- 三、環境光照
- 四、漫反射光照
- 4.1 法向量
- 4.2 光源發射的光線
- 4.3 法向量與光線的夾角
- 五、鏡面光照
- 六、主函數中光源的配置
- 七、鍵盤控制正方體
- 八、完整代碼(主函數)
- 九、運行結果
- 十、參考附錄:
光照 ??
上一篇文章鏈接: 【OpenGL學習筆記⑦】——鍵盤控制鏡頭的平移【3D正方體 透視投影 觀察矩陣 對LookAt的理解】.
下一篇文章鏈接: 【OpenGL學習筆記⑨】——鼠標控制鏡頭 + 滾輪控制鏡頭縮放.
OpenGL總學習目錄: https://blog.csdn.net/Wang_Dou_Dou_/article/details/121240714.
零、 成果預覽圖
??● 說明:實現光照效果 + 依次實現正方體的 左移、右移、上移、下移、前進、后退 功能。
一、 光照原理與投光物的配置
1.1 光照原理
??● 現實世界中顏色由紅色(Red)、綠色(Green)和藍色(Blue)三個分量組成,它們通常被縮寫為 RGB。僅僅用這三個值就可以組合出任意一種顏色。
glm::vec3 color_Red(1.0f, 0.0f, 0.0f); // 紅色 glm::vec3 color_Green(0.0f, 1.0f, 0.0f); // 綠色 glm::vec3 color_Blue(0.0f, 0.0f, 1.0f); // 藍色??● 我們在現實生活中看到某一物體的顏色并不是這個物體真正擁有的顏色,而是它所 反射的(Reflected) 顏色。換句話說,那些不能被物體所吸收(Absorb)的顏色(被拒絕的顏色)就是我們能夠感知到的物體的顏色。
??● 例如,太陽光(白光)其實是由許多不同的顏色光組合而成的(如下圖所示)。如果我們將白光照在一個藍色的玩具上,這個藍色的玩具會吸收白光中除了藍色以外的所有子顏色,不被吸收的藍色光就被反射到我們的眼中,讓這個玩具看起來是藍色的。下圖顯示的是一個珊瑚紅的玩具,它以不同強度反射了多個顏色:【主要反射了紅色】
??● 這些顏色反射的定律被直接地運用在圖形領域。當我們在 OpenGL 中創建一個光源時,我們希望給光源一個顏色。樣例如下:
"樣例一" glm::vec3 light_Color(1.0f, 1.0f, 1.0f); // 白光源 glm::vec3 object_Color(1.0f, 0.5f, 0.31f); // 珊瑚紅色 glm::vec3 result = lightColor * object_Color; // 結果仍然為珊瑚紅色 = (1.0f, 0.5f, 0.31f);"樣例二" glm::vec3 light_Color(0.0f, 1.0f, 0.0f); // 綠光源 glm::vec3 object_Color(1.0f, 0.5f, 0.31f); // 珊瑚紅色 glm::vec3 result = lightColor * toyColor; // 結果變為半綠色 = (0.0f, 0.5f, 0.0f);??◆ 說明:當我們把光源的顏色與物體的顏色值相乘,所得到的就是這個物體所反射的顏色。
1.2 投光物
??● 在 OpenGL 中,我們想要將光源顯示為可見的物體(就像限時中到太陽一樣)。在這里我們依然沿用前面的 “3D正方體” 來承載光源,成為 “投光物”。
??● 為了讓 投光物 和 被照正方體 相互獨立,我們需要給投光物單獨開一個頂點著色器和一個片元著色器。
??● 投光物的頂點著色器 light_v.txt:【和原先 3D 立方體的頂點著色器一樣】
#version 330 core layout (location = 0) in vec3 position; uniform mat4 transform_2; uniform mat4 projection_2; uniform mat4 view_2; void main() {gl_Position = projection_2 * view_2 * transform_2 * vec4(position, 1.0f); }??● 投光物的片元著色器 light_f.txt:【白光源】
#version 330 core out vec4 FragColor; void main() {FragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); // 白色光 }??● 我們希望 投光物 和 攝像機 一樣,成為一個封裝類,方便我們創建和調用,故其我 創建了 Point_Light.h 如下:
#include "Shader.h" #include <glew.h> // 根據自身情況進行設置 #include "glm/glm.hpp" #include "glm/gtc/matrix_transform.hpp"GLfloat vertices_2[] = { // 坐標... // 為了節省篇幅, 此處省略. (同上一篇文章的立方體坐標) };class Point_Light { public:Point_Light() // 構造函數{this->update();}void Draw(Shader &shader) // 繪制函數{glBindVertexArray(light_VAO);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);}~Point_Light() // 析構函數{glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glDeleteVertexArrays(1, &light_VAO);glDeleteBuffers(1, &light_VBO);}private:GLuint light_VAO, light_VBO;void update(){/* 設置頂點緩沖對象(VBO) + 設置頂點數組對象(VAO) */glGenVertexArrays(1, &light_VAO);glGenBuffers(1, &light_VBO);glBindVertexArray(light_VAO);glBindBuffer(GL_ARRAY_BUFFER, light_VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_2), vertices_2, GL_STATIC_DRAW);/* 設置鏈接頂點屬性 */glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0); } };二、馮氏光照模型
??● 就像現實生活中的一樣,不同光源的光照效果是及其不同的,比如:太陽與日光燈、手電筒與激光。不同光源有不同的光照模型,在這里,我們使用 OpenGL 中較為簡單的光照模型:馮氏光照模型。
??● 馮氏光照模型的主要結構由 3 個分量組成而成:環境(Ambient)光照、漫反射(Diffuse)光照和鏡面(Specular)光照。下面這張圖展示了這些光照分量看起來的樣子:
??◆ 說明:
??① 環境光照(Ambient Lighting):即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠處的光),所以物體幾乎永遠不會是完全黑暗的。為了模擬這個,我們會使用一個環境光照常量,它永遠會給物體一些顏色。
??② 漫反射光照(Diffuse Lighting):模擬光源對物體的方向性影響(Directional Impact)【后面將會重點講解這個】。它是馮氏光照模型中視覺上最顯著的分量。物體的某一部分越是正對著光源,它就會越亮。
??③ 鏡面反射光照(Specular Lighting):模擬有光澤物體上面出現的亮點。鏡面光照的顏色相比于物體的本身顏色會更傾向于光的顏色。
??④ 第四個圖形就是把前三者結合在一起,得到的光照效果。
三、環境光照
??● 有一種環境光照,用的是全局照明的算法,但太復雜了。這里呢,我們簡單一點,使用一個很小的常量(光照)顏色,添加到物體片段著色器中,這樣子的話即便場景中沒有直接的光源也能看見一極其淡淡的光。
??● 被照物體的片元著色器 shader_f.txt【理解版】:
#version 330 core ... "為突出重點內容, 已省去部分內容" in vec3 ourColor; // 傳進來的顏色 out vec4 FragColor; // 傳出去的片元顏色 ... void main() {...float ambientStrength = 0.1; // 常量(光照)顏色因子vec3 ambient = ambientStrength * ourColor; // 環境光照...FragColor = vec4(ambient + 漫反射光照 + 鏡面反射光照, 1.0f) * ourColor; // 三種光照的組合 }四、漫反射光照
??● 漫反射光照能讓物體產生顯著的視覺影響(就像那種,這邊亮一點,那邊暗一點的效果)。漫反射光照使物體上與光線方向越接近的片段能從光源處獲得更多的亮度。為了能夠更好的理解漫反射光照,請看下圖:
??◆ 說明:
????① 圖左上方有一個光源,它所發出的光線落在物體的一個片段上。我們需要測量這個光線是以什么角度接觸到這個片段的(如圖中的 θθθ 和 φφφ)。如果光線垂直于物體表面,這束光對物體的影響會最大化(即最亮)。
????② 為了測量光線和片段的角度,我們使用一個叫做 法向量(Normal Vector) 的東西,它是垂直于片段表面的一個向量(即圖中的 N ̄\overline{N}N ),我們在后面再講這個東西。這兩個向量之間的角度很容易就能夠通過點乘計算出來。
????③ 兩個單位向量的夾角越小,它們點乘的結果越傾向于1。當兩個向量的夾角為 90 度的時候,點乘會變為 0。這同樣適用于 θ,θ 越大,光對片段顏色的影響就應該越小。
??● 特別注意:為了(只)得到兩個向量夾角的余弦值,我們使用的是單位向量(長度為 1 的向量),所以我們需要確保所有的向量都是標準化的,否則點乘返回的就不僅僅是余弦值了。
??● 計算漫反射光照需要:
??① 法向量(Normal Vector):一個垂直于頂點表面的向量。
??② 光源發射的光線:即圖中黑色的那條線。為了計算這個光線,我們需要光的位置(向量)和片段的位置(向量)。
??③ 法向量與光線的夾角:即圖中的 θθθ 和 φφφ。
4.1 法向量
??● 法向量是一個垂直于頂點表面的(單位)向量。由于頂點本身并沒有表面(它只是空間中一個獨立的點),我們利用它周圍的頂點來計算出這個頂點的表面。由于 3D 立方體不是一個復雜的形狀,所以我們可以簡單地把法線數據手工添加到頂點數據中。
??● 這些法向量垂直于立方體各個平面的表面的(一個立方體由 6 個平面組成)。
??● 3D正方體(不是投光物)的頂點屬性數組改寫如下:【注:和官方的不一樣,因為我采用的是自己建立的一套世界坐標體系,詳見【OpenGL學習筆記⑦】——鍵盤控制鏡頭的平移【3D正方體 透視投影 觀察矩陣 對LookAt的理解】】
/* 編寫各頂點位置 */ float vertices_1[] = {// x、y、z 坐標 // color // normal-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, // red 紅色面0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,-0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // green 綠色面0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, // blue 藍色面(不是圖中那種藍)-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, // yellow 黃色面0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, // purple 紫色面0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // cyan 青藍色面0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f };??● 被照物體的頂點著色器 shader_v.txt【理解版】:
#version 330 core ... "為突出重點內容, 已省去部分內容" layout(location = 2) in vec3 normal; // 新加的法向量通道 ... ... out vec3 FragNormal; // 法向量輸出通道, 將傳給片元著色器 ...void main() {...FragNormal = normal; ... }??● 在后面我們將知道片段著色器里的計算都是在世界空間坐標中進行的。所以,在這里我們應該把法向量也轉換為世界空間坐標。我們可以將其乘以一個 轉換矩陣(Transform Matrix) 來搞定。
??● 但是,每當我們將 法向量 進行一個不等比縮放時,法向量就不會再垂直于對應的表面了,這樣光照就會被破壞。(注意:等比縮放不會破壞法線,因為法線的方向沒被改變,僅僅改變了法線的長度,而這很容易通過標準化來修復)。
??● 修復這個行為的方法是使用一個為法向量專門定制的矩陣。這個矩陣稱之為 法線矩陣(Normal Matrix),它使用了一些線性代數的操作來移除對法向量錯誤縮放的影響。【后面需要時再學習這個】
4.2 光源發射的光線
??● 經過 4.1 后,每個頂點都有了法向量。現在我們需要光源的位置和片段的位置,由它們倆來計算 “光源發射的光線”。
"該代碼在被著色物體的片段著色器 shader_f.txt 中" ... uniform vec3 LightPos; // 光源位置 ... void main() {... "為突出重點內容, 已省去部分內容"vec3 SendLight= normalize(LightPos - FragPos);... }??◆ 代碼說明:當計算光照時,我們通常不關心一個向量的模長或它的位置,我們只關心它們的方向。所以,幾乎所有的計算都使用單位向量完成,因為這簡化了大部分的計算(比如點乘)。所以當進行光照計算時,確保你總是對相關向量進行標準化normalize()。
??● 最后,我們還需要片元的位置,因為是片元發生的 “明暗變化” 。我們可以通過把頂點位置屬性乘以轉換矩陣來把它變換到世界空間坐標。這個在頂點著色器中完成,如下代碼所示。
??● 被照物體的頂點著色器 shader_v.txt 【理解版】:
#version 330 core ... "為突出重點內容, 已省去部分內容" layout (location = 0) in vec3 position; // 將片元的坐標位置傳入 ...... out vec3 FragPos; // 將計算后的片元坐標傳給片段著色器 ..void main() {...FragPos = vec3(轉換矩陣 * vec4(position, 1.0f));... }4.3 法向量與光線的夾角
??● 我們對 法向量 和 發射光線 向量進行點乘,即可計算光源對當前片段實際的漫發射影響。結果值再乘以光的顏色,得到漫反射分量。兩個向量之間的角度越大,漫反射分量就會越小。
"該代碼也在被著色物體的片段著色器 shader_f.txt 中" ... in vec3 FragNormal; ...void main() {... "為突出重點內容, 已省去部分內容" float diff = 0.6 * max(dot(FragNormal, SendLight), 0.0); // 乘以 0.6 是為了不讓漫反射太亮 vec3 diffuse = diff * 光源顏色;......vec3 result = (環境光照 + diffuse + 鏡面反射光照) * 被照物體的顏色;FragColor = vec4(result, 1.0);... }??◆ 代碼說明:如果兩個向量之間的角度大于 90 度,點乘的結果就會變成負數,這樣會導致漫反射分量變為負數。為此,我們使用max()函數返回兩個參數之間較大的參數,從而保證漫反射分量不會變成負數。(負數顏色的光照是沒有定義的,所以最好避免它)
五、鏡面光照
??● 鏡面光照是基于光的反射特性。如果我們想象物體表面像一面鏡子一樣。和漫反射光照一樣,鏡面光照也是依據 光的方向向量 和 物體的法向量 來決定的,同時它也依賴于(攝像機的)觀察方向。
??● 我們首先計算 反射向量(即圖中深橙黃色的光線)。然后我們計算反射向量和視線方向的 角度差 γγγ ,如果夾角越小,那么鏡面光的影響就會越大。
??● 和漫反射一樣,我們也需要計算出 光源反射的光線(圖中的虛黑線)。此時我們就需要 片元位置(反射點)和攝像機位置 ,兩個相減就能得到:
"這段代碼在被照物體的片元著色器 shader_f.txt 中:" ... uniform vec3 CameraPos; ... void main() {... "為突出重點內容, 已省去部分內容" vec3 CameraDir = normalize(CameraPos - FragPos); // 片元指向攝像機的方向(圖中的虛黑線)vec3 ReflectLight = reflect(-SendLight, FragNormal); // 反射光線...float specularStrength = 0.8;vec3 specular = specularStrength * pow(max(dot(CameraDir, ReflectLight), 0.0), 32) * 光源顏色; // 計算圖中的藍虛線...vec3 result = (環境光照 + 漫反射光照 + specular) * 被照物體顏色;FragColor = vec4(result, 1.0);... }??◆ 代碼說明:
????① reflect()函數要求第一個向量是從光源指向片元位置的向量,但是我們這里的 SendLight 當前正好相反,是從片元指向光源(由先前我們計算 SendLight 向量時,減法的順序決定)。為了保證我們得到正確的 ReflectLight 向量,我們通過對 SendLight 向量取反來獲得相反的方向。第二個參數要求是一個以標準化后的法向量。
????② specularStrength:鏡面強度變量,給鏡面高光一個中等亮度顏色,讓它不要產生過度的影響。
????③ 計算鏡面分量(即計算圖中的藍虛線):我們先計算視線方向與反射方向的點乘(并確保它不是負值),然后取它的 32 次冪。這個 32 是鏡面反射光的反光度(Shininess)。一個物體的反光度越高,反射光的能力越強,散射得越少,高光點就會越小。在下面的圖片里,你會看到不同反光度的視覺效果影響:
六、主函數中光源的配置
??● 主函數的代碼和 第⑦篇 文章類似。我們只是新增了一個 “投光物”,然后原來的 “被照物體” 的頂點/片元著色器的通道上進行了相應的配置。
/* 引入相應的庫 */ ... "為了突出重點內容, 已省去部分內容" #include"Shader.h" #include"Camera.h" #include "Point_Light.h" .../* 編寫各頂點位置 */ float vertices_1[] = {... };... glm::vec3 lightPos = glm::vec3(0.0f, 0.0f, 1.0f); // 光源位置初始化 ...int main() {.../* 初始化 glew */glewInit();Point_Light My_light = Point_Light(); // 新建一個光源(必須在 glew 初始化后才行)....../* 將我們自己設置的著色器文本傳進來 */Shader ourShader = Shader("shader_v.txt", "shader_f.txt"); // 相對路徑Shader lightShader = Shader("light_v.txt", "light_f.txt"); // 相對路徑.../* draw loop 畫圖循環 */while (!glfwWindowShouldClose(window_1)){...Square_Movement(..., ..., ...); // 正方體移動.../* 繪制光照 */...lightShader.Use();glm::mat4 transform_1 = glm::mat4(1.0f);lightPos = glm::rotate(lightPos, glm::radians(0.1f)/2, glm::vec3(0.0f, 1.0f, 0.0f));transform_1 = glm::translate(transform_1, lightPos);transform_1 = glm::scale(transform_1, glm::vec3(0.1f, 0.1f, 0.1f));glm::mat4 projection_1 = glm::perspective(glm::radians(45.0f), (float)screenWidth_1/(float)screenHeight_1, 0.1f, 100.0f);glm::mat4 view_1 = camera.GetViewMatrix(); // 求得觀察矩陣int transform_1_Location = glGetUniformLocation(lightShader.Program, "transform_2");glUniformMatrix4fv(transform_1_Location, 1, GL_FALSE, glm::value_ptr(transform_1));int projection_1_Location = glGetUniformLocation(lightShader.Program, "projection_2");glUniformMatrix4fv(projection_1_Location, 1, GL_FALSE, glm::value_ptr(projection_1));int view_1_Location = glGetUniformLocation(lightShader.Program, "view_2");glUniformMatrix4fv(view_1_Location, 1, GL_FALSE, glm::value_ptr(view_1));My_light.Draw(lightShader);transform_1 = glm::mat4(1.0f); // 這里需要重新初始化.../* 繪制正方體 */ourShader.Use(); // 調用著色器程序...}...return 0; } ...七、鍵盤控制正方體
??● 這和上一篇文章第⑦篇——鍵盤控制移動攝像機的原理差不多。我們只需要在上面的主函數中加如下按鍵響應代碼即可:
"這段代碼在主函數中:" ... "為突出重點內容, 已省去部分內容" void Square_Movement(GLfloat&, GLfloat&, GLfloat&); // 正方體移動 ... int main() {...GLfloat up_down_move = 0.0f; // 上下移動的變量GLfloat left_right_move = 0.0f; // 左右移動的變量GLfloat front_back_move = 0.0f; // 前后移動的變量...Square_Movement(up_down_move, left_right_move, front_back_move); // 正方體移動.../* draw loop 畫圖循環 */while (!glfwWindowShouldClose(window_1)){...transform_1 = glm::translate(transform_1, glm::vec3(left_right_move, up_down_move, front_back_move)); // 轉換矩陣...}...return 0; }... void Square_Movement(GLfloat& shang_and_xia_move, GLfloat& left_and_right_move, GLfloat &up_and_down_move) // 正方體移動 {if (keys[GLFW_KEY_UP]) // 向上{shang_and_xia_move += 0.0005f;}if (keys[GLFW_KEY_DOWN]) // 向下{shang_and_xia_move -= 0.0005f;}if (keys[GLFW_KEY_LEFT]) // 向左{left_and_right_move += 0.0005f;}if (keys[GLFW_KEY_RIGHT]) // 向右{left_and_right_move -= 0.0005f;}if (keys[GLFW_KEY_F]) // 向前(按 F 鍵){up_and_down_move += 0.0005f;}if (keys[GLFW_KEY_B]) // 向后(按 B 鍵){up_and_down_move -= 0.0005f;} }八、完整代碼(主函數)
??● 頭文件 Shader.h 依舊沿用第③篇中的代碼【OpenGL學習筆記③】——?著色器【GLSL Uniform 彩色三角形 變色正方形】?
??● 頭文件 Camera.h 依舊沿用第⑦篇中的代碼【OpenGL學習筆記⑦】——鍵盤控制鏡頭的平移【3D正方體 透視投影 觀察矩陣】
??● shader_v.txt的完整代碼如下:
#version 330 core layout (location = 0) in vec3 position; layout(location = 1) in vec3 color; layout(location = 2) in vec3 normal;out vec3 ourColor; out vec3 FragNormal; out vec3 FragPos;uniform mat4 transform_1; uniform mat4 projection_1; uniform mat4 view_1;void main() {ourColor = color;gl_Position = projection_1 * view_1 * transform_1 * vec4(position, 1.0f);FragPos = vec3(transform_1 * vec4(position, 1.0f));FragNormal = normal; }??● shader_f.txt的完整代碼如下:
#version 330 core in vec3 ourColor; in vec3 FragNormal; in vec3 FragPos;out vec4 FragColor;uniform vec3 LightPos; uniform vec3 CameraPos;void main() {vec3 std_norm = normalize(FragNormal);// 環境光照float ambientStrength = 0.1; // 常量(光照)顏色因子vec3 ambient = ambientStrength * ourColor; // 環境光照// 漫反射光照vec3 SendLight = normalize(LightPos - FragPos);float diff = 0.6 * max(dot(FragNormal, SendLight), 0.0f);vec3 diffuse = diff * ourColor;// 鏡面反射光照vec3 ReflectLight = reflect(-SendLight, FragNormal);float specularStrength = 0.8;vec3 CameraDir = normalize(CameraPos - FragPos);vec3 specular = specularStrength * pow(max(dot(CameraDir, ReflectLight), 0.0), 32) * ourColor; FragColor = vec4(ambient + diffuse + specular, 1.0f); }??● 投光物的頂點著色器 light_v.txt 和投光物的片元著色器 light_f.txt 在本文開頭已經寫了,關于投光物 的封裝類 Point_Light.h 也在文章前面配置好了,其所有環境配置如下圖所示:
??● 最后主函數代碼如下:
/* 引入相應的庫 */ #include <iostream> using namespace std; #define GLEW_STATIC #include"Shader.h" #include"Camera.h" #include "Point_Light.h" #include<glew.h> // 注:這一部分要根據個人情況進行設定 #include<glfw3.h> #include"glm\glm.hpp" #include"glm\gtc\matrix_transform.hpp" #include"glm\gtc\type_ptr.hpp" #include"glm/gtx/rotate_vector.hpp"/* 編寫各頂點位置 */ float vertices_1[] = {... "前文已寫" // 前文已寫 };const GLint WIDTH = 600, HEIGHT = 600;bool keys[1024]; // 專門存儲按過的鍵 Camera camera(glm::vec3(1.0f, 1.0f, -5.0f), glm::vec3(-1.0f, -1.0f, 5.0f), glm::vec3(0.0f, 1.0f, 0.0f)); void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mode); void Key_Movement(); void square_Movement(GLfloat&, GLfloat&, GLfloat&); GLfloat deltaTime = 0.0f; GLfloat lastTime = 0.0f;glm::vec3 lightPos = glm::vec3(0.0f, 0.0f, 1.0f); const double Shift_pix = 0.0005; // 正方體移動速度 int main() {/* 初始化 glfw */glfwInit();glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // 縮放關閉/* 窗口捕獲與處理 */GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL Light Test", nullptr, nullptr);int screenWidth_1, screenHeight_1;glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);glfwMakeContextCurrent(window_1);glfwSetKeyCallback(window_1, KeyCallback);/* 初始化 glew + 光照生成 */glewInit();Point_Light my_light = Point_Light();/* 深度測試開啟 */glEnable(GL_DEPTH_TEST);/* 將我們自己設置的著色器文本傳進來 */Shader ourShader = Shader("shader_v.txt", "shader_f.txt"); // 相對路徑Shader lightShader = Shader("light_v.txt", "light_f.txt"); // 相對路徑/* 設置頂點緩沖對象(VBO) + 設置頂點數組對象(VAO) */GLuint VAO, VBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);/* 設置鏈接頂點屬性 */glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0); // 通道 0 打開glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1); // 通道 1 打開glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));glEnableVertexAttribArray(2); // 通道 2 打開GLfloat up_down_move = 0.0f; // 上下移動的變量GLfloat left_right_move = 0.0f; // 左右移動的變量GLfloat front_back_move = 0.0f; // 前后移動的變量/* draw loop 畫圖循環 */while (!glfwWindowShouldClose(window_1)){/* 時間獲取 */GLfloat currentTime = glfwGetTime();deltaTime = currentTime - lastTime;lastTime = (float)currentTime;/* 視口 + 鍵鼠捕獲 */glViewport(0, 0, screenWidth_1, screenHeight_1);glfwPollEvents(); // 獲取鍵盤鼠標Key_Movement(); // 獲取鍵盤square_Movement(up_down_move, left_right_move, front_back_move ); // 正方體移動/* 渲染 + 清除顏色緩沖 */glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);/* 光照繪制 */lightShader.Use();glm::mat4 my_transform = glm::mat4(1.0f);lightPos = glm::rotate(lightPos, glm::radians(0.1f), glm::vec3(0.0f, 1.0f, 0.0f)); // 旋轉my_transform = glm::translate(my_transform, lightPos); // 平移my_transform = glm::scale(my_transform, glm::vec3(0.1f, 0.1f, 0.1f)); // 縮放glm::mat4 my_projection = glm::perspective(glm::radians(45.0f), (float)screenWidth_1 / (float)screenHeight_1, 0.1f, 100.0f); glm::mat4 my_view = camera.GetViewMatrix(); // 求得觀察矩陣int my_transform_Location = glGetUniformLocation(lightShader.Program, "transform_2");glUniformMatrix4fv(my_transform_Location, 1, GL_FALSE, glm::value_ptr(my_transform));int my_projection_Location = glGetUniformLocation(lightShader.Program, "projection_2");glUniformMatrix4fv(my_projection_Location, 1, GL_FALSE, glm::value_ptr(my_projection));int my_view_Location = glGetUniformLocation(lightShader.Program, "view_2");glUniformMatrix4fv(my_view_Location, 1, GL_FALSE, glm::value_ptr(my_view));my_light.Draw(lightShader);/* 正方體繪制 */my_transform = glm::mat4(1.0f); // 初始化是必要的ourShader.Use(); // 調用著色器程序glBindVertexArray(VAO); // 綁定 VAOmy_transform = glm::translate(my_transform, glm::vec3(left_right_move, up_down_move, front_back_move ));my_transform = glm::scale(my_transform, glm::vec3(0.5, 0.5, 0.5));my_projection = glm::perspective(glm::radians(45.0f), (float)screenWidth_1 / (float)screenHeight_1, 0.1f, 100.0f);my_view = camera.GetViewMatrix();my_transform_Location = glGetUniformLocation(ourShader.Program, "transform_1");glUniformMatrix4fv(my_transform_Location, 1, GL_FALSE, glm::value_ptr(my_transform));my_projection_Location = glGetUniformLocation(ourShader.Program, "projection_1");glUniformMatrix4fv(my_projection_Location, 1, GL_FALSE, glm::value_ptr(my_projection));my_view_Location = glGetUniformLocation(ourShader.Program, "view_1");glUniformMatrix4fv(my_view_Location, 1, GL_FALSE, glm::value_ptr(my_view));int LightPos_Location = glGetUniformLocation(ourShader.Program, "LightPos");glUniform3f(LightPos_Location, lightPos.x, lightPos.y, lightPos.z);int CameraPos_Location = glGetUniformLocation(ourShader.Program, "CameraPos");glUniform3f(CameraPos_Location, camera.GetPosition().x, camera.GetPosition().y, camera.GetPosition().z);glDrawArrays(GL_TRIANGLES, 0, 36); // 繪制 36 個點(正方體)glBindVertexArray(0); // 解綁定 VAOglfwSwapBuffers(window_1);}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glfwTerminate();return 0; }void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mode) // 按鍵捕獲(固定函數格式) {if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS){glfwSetWindowShouldClose(window, GL_TRUE);}if (key >= 0 && key <= 1024){if (action == GLFW_PRESS)keys[key] = true;else if (action == GLFW_RELEASE)keys[key] = false;} }void Key_Movement() // Camera {if (keys[GLFW_KEY_Q]) // 向前camera.ProcessKeyboard(FORWARD, deltaTime);if (keys[GLFW_KEY_E]) // 向后camera.ProcessKeyboard(BACKWARD, deltaTime);if (keys[GLFW_KEY_A]) // 向左camera.ProcessKeyboard(LEFT, deltaTime);if (keys[GLFW_KEY_D]) // 向右camera.ProcessKeyboard(RIGHT, deltaTime);if (keys[GLFW_KEY_W]) // 向上camera.ProcessKeyboard(UPWARD, deltaTime);if (keys[GLFW_KEY_S]) // 向下camera.ProcessKeyboard(DOWNWARD, deltaTime); }void square_Movement(GLfloat& up_down_move, GLfloat& left_right_move, GLfloat& front_back_move) // Square {if (keys[GLFW_KEY_UP]) // 向上{up_down_move += Shift_pix;}if (keys[GLFW_KEY_DOWN]) // 向下{up_down_move -= Shift_pix;}if (keys[GLFW_KEY_LEFT]) // 向左{left_right_move += Shift_pix;}if (keys[GLFW_KEY_RIGHT]) // 向右{left_right_move -= Shift_pix;}if (keys[GLFW_KEY_F]) // 向前(按 F 鍵){front_back_move += Shift_pix;}if (keys[GLFW_KEY_B]) // 向后(按 B 鍵){front_back_move -= Shift_pix;} }九、運行結果
??● 如果你認真按照上訴內容一步一步地進行配置,即可得到如下結果:
十、參考附錄:
[1] 《Learn OpenGL——顏色》
鏈接: https://learnopengl-cn.github.io/02%20Lighting/01%20Colors/.
[2] 《Learn OpenGL——基礎光照》
鏈接: https://learnopengl-cn.github.io/02%20Lighting/02%20Basic%20Lighting/#_1.
上一篇文章鏈接: 【OpenGL學習筆記⑦】——鍵盤控制鏡頭的平移【3D正方體 透視投影 觀察矩陣 對LookAt的理解】.
下一篇文章鏈接: 【OpenGL學習筆記⑨】——鼠標控制鏡頭 + 滾輪控制鏡頭縮放.
OpenGL總學習目錄: https://blog.csdn.net/Wang_Dou_Dou_/article/details/121240714.
?? ??
總結
以上是生活随笔為你收集整理的【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电话号码生成图片格式
- 下一篇: android RecyclerView