opengl開發語言,現代opengl 設計入門,攝像頭

 2023-10-15 阅读 26 评论 0

摘要:前面的教程 現代opengl 設計入門,坐標系統和立方體的紋理貼圖 中討論了觀察矩陣view以及如何使用觀察矩陣移動場景。OpenGL本身沒有攝像機(Camera)的概念,但我們完全可以類比攝像頭,來構造我們的view 變換矩陣。攝像或拍照時,我們變換攝像頭的位置

前面的教程 現代opengl 設計入門,坐標系統和立方體的紋理貼圖 中討論了觀察矩陣view以及如何使用觀察矩陣移動場景。OpenGL本身沒有攝像機(Camera)的概念,但我們完全可以類比攝像頭,來構造我們的view 變換矩陣。攝像或拍照時,我們變換攝像頭的位置,對準目標,但可以左右偏擺一點,上下一點,還可以改變焦距改變取景的范圍,進而控制我們的成像。

本節將討論如何在OpenGL中配置一個攝像機,設計一個攝像機類,利用鍵盤和鼠標輸入,實現我們操作攝像時的所有控制。WASD四個按鍵移動攝像位置前后左右控制,鼠標移動實現偏航角和俯仰角,方向控制,鼠標滾輪實現焦距或視角控制。

如果你看了前面的教程,可以簡單看看中間內容,比較細節說明,然后直接看攝像頭類,代碼要點說明 及最后的代碼運行就好。要完整明白,還是要看看細節介紹。我個人習慣先把代碼運行起來,再看內容。

opengl開發語言。本文參照?https://learnopengl.com/? 和?https://learnopengl-cn.github.io/?學習而來,同時包含自己的學習體會和圖示在內。在visual studio 2010下完成代碼測試的。

攝像機/觀察空間

當我們討論攝像機/觀察空間(Camera/View Space)的時候,是在討論以攝像機的視角作為場景原點時場景中所有的頂點坐標:觀察矩陣把所有的世界坐標變換為相對于攝像機位置與方向的觀察坐標。要定義一個攝像機,我們需要它在世界空間中的位置、觀察的方向、一個指向它右測的向量以及一個指向它上方的向量。細心的讀者可能已經注意到我們實際上創建了一個三個單位軸相互垂直的、以攝像機的位置為原點的坐標系。

1. 攝像機位置

獲取攝像機位置很簡單。攝像機位置簡單來說就是世界空間中一個指向攝像機位置的向量(上圖中 1.Position 中的(0,0,2)。攝像機位置設置為如下glm 表述:

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 2.0f);

opengl shader、不要忘記正z軸是從屏幕指向你的,如果我們希望攝像機向后移動,我們就沿著z軸的正方向移動。

2. 攝像機方向

下一個需要的向量是攝像機的方向,這里指的是攝像機指向哪個方向。現在我們讓攝像機指向場景原點:(0, 0, 0)。用場景原點向量減去攝像機位置向量的結果就是攝像機的指向向量。由于我們知道攝像機指向z軸負方向,但我們希望方向向量(Direction Vector)指向攝像機的z軸正方向。如果我們交換相減的順序,我們就會獲得一個指向攝像機正z軸方向的向量(上圖中 1.Direction 中藍色箭頭線)的:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

方向向量(Direction Vector)并不是最好的名字,因為它實際上指向從它到目標向量的相反方向。

3. 右軸

我們需要的另一個向量是一個右向量(Right Vector),它代表攝像機空間的x軸的正方向(上圖中 3.Right 中的紅箭頭(0,1,0))。為獲取右向量我們需要先使用一個小技巧:先定義一個上向量(Up Vector)。接下來把上向量和第二步得到的方向向量進行叉乘。兩個向量叉乘的結果會同時垂直于兩向量,因此我們會得到指向x軸正方向的那個向量(如果我們交換兩個向量叉乘的順序就會得到相反的指向x軸負方向的向量):

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

4. 上軸

opengl從入門到精通、現在我們已經有了x軸向量和z軸向量,獲取一個指向攝像機的正y軸向量就相對簡單了(上圖中 4.Up 中的綠箭頭):我們把右向量和方向向量進行叉乘:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

在叉乘和一些小技巧的幫助下,我們創建了所有構成觀察/攝像機空間的向量。對于想學到更多數學原理的讀者,提示一下,在線性代數中這個處理叫做格拉姆—施密特正交化(Gram-Schmidt Process)。使用這些攝像機向量我們就可以創建一個LookAt矩陣了,它在創建攝像機的時候非常有用。

Look At

使用矩陣的好處之一是如果你使用3個相互垂直(或非線性)的軸定義了一個坐標空間,你可以用這3個軸外加一個平移向量來創建一個矩陣,并且你可以用這個矩陣乘以任何向量來將其變換到那個坐標空間。這正是LookAt矩陣所做的,現在我們有了3個相互垂直的軸和一個定義攝像機空間的位置坐標,我們可以創建我們自己的LookAt矩陣了:

軟件實現opengl。其中R是右向量,U是上向量,D是方向向量P是攝像機位置向量。注意,位置向量是相反的,因為我們最終希望把世界平移到與我們自身移動的相反方向。把這個LookAt矩陣作為觀察矩陣可以很高效地把所有世界坐標變換到剛剛定義的觀察空間。LookAt矩陣就像它的名字表達的那樣:它會創建一個看著(Look at)給定目標的觀察矩陣。

幸運的是,GLM已經提供了這些支持。我們要做的只是定義一個攝像機位置,一個目標位置和一個表示世界空間中的上向量的向量(我們計算右向量使用的那個上向量)。接著GLM就會創建一個LookAt矩陣,我們可以把它當作我們的觀察矩陣:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

glm::LookAt函數需要一個位置、目標和上向量。它會創建一個和在上一節使用的一樣的觀察矩陣。

自由移動

設置一個攝像機系統,所以在程序前面定義一些攝像機變量:

glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

opengl opencv?LookAt函數現在成了:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

我們首先將攝像機位置設置為之前定義的cameraPos。方向是當前的位置加上我們剛剛定義的方向向量。這樣能保證無論我們怎么移動,攝像機都會注視著目標方向。讓我們擺弄一下這些向量,在按下某些按鈕時更新cameraPos向量。

我們已經為GLFW的鍵盤輸入定義過一個processInput函數了,我們來新添加幾個需要檢查WASD的按鍵命令:

void processInput(GLFWwindow *window)
{...float cameraSpeed = 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

當我們按下WASD鍵的任意一個,攝像機的位置都會相應更新。如果我們希望向前或向后移動(W和S按鍵),我們就把位置向量加上或減去方向向量。如果我們希望向左右移動(S和D按鍵),我們使用叉乘來創建一個右向量(Right Vector),并沿著它相應移動就可以了。這樣就創建了使用攝像機時熟悉的橫移(Strafe)效果。

opengl開發環境搭建?注意,我們對右向量進行了標準化。如果我們沒對這個向量進行標準化,最后的叉乘結果會根據cameraFront變量返回大小不同的向量。如果我們不對向量進行標準化,我們就得根據攝像機的朝向不同加速或減速移動了,但如果進行了標準化移動就是勻速的。

現在你就應該能夠移動攝像機了,雖然移動速度和系統有關,你可能會需要調整一下cameraSpeed。

移動速度

目前我們的移動速度是個常量。理論上沒什么問題,但是實際情況下根據處理器的能力不同,有些人可能會比其他人每秒繪制更多幀,也就是以更高的頻率調用processInput函數。結果就是,根據配置的不同,有些人可能移動很快,而有些人會移動很慢。當你發布你的程序的時候,你必須確保它在所有硬件上移動速度都一樣。

圖形程序和游戲通常會跟蹤一個時間差(Deltatime)變量,它儲存了渲染上一幀所用的時間。我們把所有速度都去乘以deltaTime值。結果就是,如果我們的deltaTime很大,就意味著上一幀的渲染花費了更多時間,所以這一幀的速度需要變得更高來平衡渲染所花去的時間。使用這種方法時,無論你的電腦快還是慢,攝像機的速度都會相應平衡,這樣每個用戶的體驗就都一樣了。

opengl書籍、我們跟蹤兩個全局變量來計算出deltaTime值:

float deltaTime = 0.0f; // 當前幀與上一幀的時間差
float lastFrame = 0.0f; // 上一幀的時間

在每一幀中我們計算出新的deltaTime以備后用。

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

現在我們有了deltaTime,在計算速度的時候可以將其考慮進去了:

void processInput(GLFWwindow *window)
{float cameraSpeed = 2.5f * deltaTime;...
}

現在我們有了一個在任何系統上移動速度都一樣的攝像機。

視角移動

opengl教程七?只用鍵盤移動沒什么意思。特別是我們還不能轉向,移動很受限制。是時候加入鼠標了!

為了能夠改變視角,我們需要根據鼠標的輸入改變cameraFront向量。然而,根據鼠標移動改變方向向量有點復雜,需要一些三角學知識。如果你對三角學知之甚少,別擔心,你可以跳過這一部分,直接復制粘貼我們的代碼;當你想了解更多的時候再回來看。

歐拉角

歐拉角(Euler Angle)是可以表示3D空間中任何旋轉的3個值,由萊昂哈德·歐拉(Leonhard Euler)在18世紀提出。一共有3種歐拉角:俯仰角(Pitch)、偏航角(Yaw)和滾轉角(Roll),下面的圖片展示了它們的含義:

opengl視頻教程、俯仰角是描述我們如何往上或往下看的角,可以在第一張圖中看到。第二張圖展示了偏航角,偏航角表示我們往左和往右看的程度。滾轉角代表我們如何翻滾攝像機,通常在太空飛船的攝像機中使用。每個歐拉角都有一個值來表示,把三個角結合起來我們就能夠計算3D空間中任何的旋轉向量了。

對于我們的攝像機系統來說,我們只關心俯仰角和偏航角,所以我們不會討論滾轉角。給定一個俯仰角和偏航角,我們可以把它們轉換為一個代表新的方向向量的3D向量。俯仰角和偏航角轉換為方向向量的處理需要一些三角學知識,我們先從最基本的情況開始:

如果我們把斜邊邊長定義為1,我們就能知道鄰邊的長度是cos?x/h=cos?x/1=cos?xcos??x/h=cos??x/1=cos??x,它的對邊是sin?y/h=sin?y/1=sin?ysin??y/h=sin??y/1=sin??y。這樣我們獲得了能夠得到x和y方向長度的通用公式,它們取決于所給的角度。我們使用它來計算方向向量的分量:

opengl實現,

這個三角形看起來和前面的三角形很像,所以如果我們想象自己在xz平面上,看向y軸,我們可以基于第一個三角形計算來計算它的長度/y方向的強度(Strength)(我們往上或往下看多少)。從圖中我們可以看到對于一個給定俯仰角的y值等于sin?θsin??θ:

direction.y = sin(glm::radians(pitch)); // 注意我們先把角度轉為弧度

這里我們只更新了y值,仔細觀察x和z分量也被影響了。從三角形中我們可以看到它們的值等于:

direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));

看看我們是否能夠為偏航角找到需要的分量:

菜鳥教程opengl?

就像俯仰角的三角形一樣,我們可以看到x分量取決于cos(yaw)的值,z值同樣取決于偏航角的正弦值。把這個加到前面的值中,會得到基于俯仰角和偏航角的方向向量:

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 譯注:direction代表攝像機的前軸(Front),這個前軸是和本文第一幅圖片的第二個攝像機的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

這樣我們就有了一個可以把俯仰角和偏航角轉化為用來自由旋轉視角的攝像機的3維方向向量了。你可能會奇怪:我們怎么得到俯仰角和偏航角?

鼠標輸入

偏航角和俯仰角是通過鼠標(或手柄)移動獲得的,水平的移動影響偏航角,豎直的移動影響俯仰角。它的原理就是,儲存上一幀鼠標的位置,在當前幀中我們當前計算鼠標位置與上一幀的位置相差多少。如果水平/豎直差別越大那么俯仰角或偏航角就改變越大,也就是攝像機需要移動更多的距離。

首先我們要告訴GLFW,它應該隱藏光標,并捕捉(Capture)它。捕捉光標表示的是,如果焦點在你的程序上(譯注:即表示你正在操作這個程序,Windows中擁有焦點的程序標題欄通常是有顏色的那個,而失去焦點的程序標題欄則是灰色的),光標應該停留在窗口中(除非程序失去焦點或者退出)。我們可以用一個簡單地配置調用來完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

在調用這個函數之后,無論我們怎么去移動鼠標,光標都不會顯示了,它也不會離開窗口。對于FPS攝像機系統來說非常完美。

為了計算俯仰角和偏航角,我們需要讓GLFW監聽鼠標移動事件。(和鍵盤輸入相似)我們會用一個回調函數來完成,函數的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

這里的xpos和ypos代表當前鼠標的位置。當我們用GLFW注冊了回調函數之后,鼠標一移動mouse_callback函數就會被調用:

glfwSetCursorPosCallback(window, mouse_callback);

在處理FPS風格攝像機的鼠標輸入的時候,我們必須在最終獲取方向向量之前做下面這幾步:

  1. 計算鼠標距上一幀的偏移量。
  2. 把偏移量添加到攝像機的俯仰角和偏航角中。
  3. 對偏航角和俯仰角進行最大和最小值的限制。
  4. 計算方向向量。

第一步是計算鼠標自上一幀的偏移量。我們必須先在程序中儲存上一幀的鼠標位置,我們把它的初始值設置為屏幕的中心(屏幕的尺寸是800x600):

float lastX = 400, lastY = 300;

然后在鼠標的回調函數中我們計算當前幀和上一幀鼠標位置的偏移量:

float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 注意這里是相反的,因為y坐標是從底部往頂部依次增大的
lastX = xpos;
lastY = ypos;float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;

注意我們把偏移量乘以了sensitivity(靈敏度)值。如果我們忽略這個值,鼠標移動就會太大了;你可以自己實驗一下,找到適合自己的靈敏度值。

接下來我們把偏移量加到全局變量pitch和yaw上:

yaw   += xoffset;
pitch += yoffset;

第三步,我們需要給攝像機添加一些限制,這樣攝像機就不會發生奇怪的移動了(這樣也會避免一些奇怪的問題)。對于俯仰角,要讓用戶不能看向高于89度的地方(在90度時視角會發生逆轉,所以我們把89度作為極限),同樣也不允許小于-89度。這樣能夠保證用戶只能看到天空或腳下,但是不能超越這個限制。我們可以在值超過限制的時候將其改為極限值來實現:

if(pitch > 89.0f)pitch =  89.0f;
if(pitch < -89.0f)pitch = -89.0f;

注意我們沒有給偏航角設置限制,這是因為我們不希望限制用戶的水平旋轉。當然,給偏航角設置限制也很容易,如果你愿意可以自己實現。

第四也是最后一步,就是通過俯仰角和偏航角來計算以得到真正的方向向量:

glm::vec3 front;
front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
front.y = sin(glm::radians(pitch));
front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(front);

計算出來的方向向量就會包含根據鼠標移動計算出來的所有旋轉了。由于cameraFront向量已經包含在GLM的lookAt函數中,我們這就沒什么問題了。

如果你現在運行代碼,你會發現在窗口第一次獲取焦點的時候攝像機會突然跳一下。這個問題產生的原因是,在你的鼠標移動進窗口的那一刻,鼠標回調函數就會被調用,這時候的xpos和ypos會等于鼠標剛剛進入屏幕的那個位置。這通常是一個距離屏幕中心很遠的地方,因而產生一個很大的偏移量,所以就會跳了。我們可以簡單的使用一個bool變量檢驗我們是否是第一次獲取鼠標輸入,如果是,那么我們先把鼠標的初始位置更新為xpos和ypos值,這樣就能解決這個問題;接下來的鼠標移動就會使用剛進入的鼠標位置坐標來計算偏移量了:

if(firstMouse) // 這個bool變量初始時是設定為true的
{lastX = xpos;lastY = ypos;firstMouse = false;
}

最后的代碼應該是這樣的:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if(firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; lastX = xpos;lastY = ypos;float sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;yaw   += xoffset;pitch += yoffset;if(pitch > 89.0f)pitch = 89.0f;if(pitch < -89.0f)pitch = -89.0f;glm::vec3 front;front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));front.y = sin(glm::radians(pitch));front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));cameraFront = glm::normalize(front);
}

現在我們就可以自由地在3D場景中移動了!

縮放

作為我們攝像機系統的一個附加內容,我們還會來實現一個縮放(Zoom)接口。在之前的教程中我們說視野(Field of View)或fov定義了我們可以看到場景中多大的范圍。當視野變小時,場景投影出來的空間就會減小,產生放大(Zoom In)了的感覺。我們會使用鼠標的滾輪來放大。與鼠標移動、鍵盤輸入一樣,我們需要一個鼠標滾輪的回調函數:

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{if(fov >= 1.0f && fov <= 45.0f)fov -= yoffset;if(fov <= 1.0f)fov = 1.0f;if(fov >= 45.0f)fov = 45.0f;
}

當滾動鼠標滾輪的時候,yoffset值代表我們豎直滾動的大小。當scroll_callback函數被調用后,我們改變全局變量fov變量的內容。因為45.0f是默認的視野值,我們將會把縮放級別(Zoom Level)限制在1.0f45.0f

我們現在在每一幀都必須把透視投影矩陣上傳到GPU,但現在使用fov變量作為它的視野:

projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

最后不要忘記注冊鼠標滾輪的回調函數:

glfwSetScrollCallback(window, scroll_callback);

現在,我們就實現了一個簡單的攝像機系統了,它能夠讓我們在3D環境中自由移動。?

攝像頭類

攝像頭類實現我們上面介紹的功能。ProcessKeyboard處理鍵盤WASD輸入,ProcessMouseMovement 處理鼠標移動,ProcessMouseScroll處理鼠標滾輪,GetViewMatrix() 獲取我們處理好的view 矩陣。

camera.h 全部代碼如下,可以放在include 目錄下,主程序包含就好,不要鏈接lib庫。

#ifndef CAMERA_H
#define CAMERA_H#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>#include <vector>// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {FORWARD,BACKWARD,LEFT,RIGHT
};// Default camera values
const float YAW         = -90.0f;
const float PITCH       =  0.0f;
const float SPEED       =  2.5f;
const float SENSITIVITY =  0.1f;
const float ZOOM        =  45.0f;// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:// Camera Attributesglm::vec3 Position;glm::vec3 Front;glm::vec3 Up;glm::vec3 Right;glm::vec3 WorldUp;// Euler Anglesfloat Yaw;float Pitch;// Camera optionsfloat MovementSpeed;float MouseSensitivity;float Zoom;// Constructor with vectorsCamera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM){Position = position;WorldUp = up;Yaw = yaw;Pitch = pitch;updateCameraVectors();}// Constructor with scalar valuesCamera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM){Position = glm::vec3(posX, posY, posZ);WorldUp = glm::vec3(upX, upY, upZ);Yaw = yaw;Pitch = pitch;updateCameraVectors();}// Returns the view matrix calculated using Euler Angles and the LookAt Matrixglm::mat4 GetViewMatrix(){return glm::lookAt(Position, Position + Front, Up);}// Processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)void ProcessKeyboard(Camera_Movement direction, float deltaTime){float velocity = MovementSpeed * deltaTime;if (direction == FORWARD)Position += Front * velocity;if (direction == BACKWARD)Position -= Front * velocity;if (direction == LEFT)Position -= Right * velocity;if (direction == RIGHT)Position += Right * velocity;}// Processes input received from a mouse input system. Expects the offset value in both the x and y direction.void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true){xoffset *= MouseSensitivity;yoffset *= MouseSensitivity;Yaw   += xoffset;Pitch += yoffset;// Make sure that when pitch is out of bounds, screen doesn't get flippedif (constrainPitch){if (Pitch > 89.0f)Pitch = 89.0f;if (Pitch < -89.0f)Pitch = -89.0f;}// Update Front, Right and Up Vectors using the updated Euler anglesupdateCameraVectors();}// Processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axisvoid ProcessMouseScroll(float yoffset){if (Zoom >= 1.0f && Zoom <= 85.0f)Zoom -= yoffset;if (Zoom <= 1.0f)Zoom = 1.0f;if (Zoom >= 85.0f)Zoom = 85.0f;}private:// Calculates the front vector from the Camera's (updated) Euler Anglesvoid updateCameraVectors(){// Calculate the new Front vectorglm::vec3 front;front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));front.y = sin(glm::radians(Pitch));front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));Front = glm::normalize(front);// Also re-calculate the Right and Up vectorRight = glm::normalize(glm::cross(Front, WorldUp));  // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.Up    = glm::normalize(glm::cross(Right, Front));}
};
#endif

代碼要點說明

這里著重說明,本節內容相對于?現代opengl 設計入門,坐標系統和立方體的紋理貼圖?的代碼添加變化。

其變化就是增加了一個攝像頭類,見上面代碼和說明,其他就是在主程序中引入camera類, view矩陣通過camera類來獲取:

glm::mat4 view = camera.GetViewMatrix();

添加了2個回調函數:鼠標移動 mouse_callback, scroll_callback, 修改了鍵盤處理函數processInput,這3個處理函數里調用camera里3個對應成員函數。內容如下:

// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{camera.ProcessMouseScroll(yoffset);
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime);
}

在主程序里

注冊回調函數,在開頭也要聲明這2個函數。

    glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);

?

camera 及參數 全局變量

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;// timing
float deltaTime = 0.0f;	// time between current frame and last frame
float lastFrame = 0.0f;

著色器內容不變。

openglA代碼如下:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include <shader_s.h>
#include <camera.h>#include <iostream>void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;// timing
float deltaTime = 0.0f;	// time between current frame and last frame
float lastFrame = 0.0f;int main()
{// glfw: initialize and configure// ------------------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif// glfw window creation// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "www.liwenz.com", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);// tell GLFW to capture our mouseglfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);// glad: load all OpenGL function pointers// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// configure global opengl state// -----------------------------glEnable(GL_DEPTH_TEST);// build and compile our shader zprogram// ------------------------------------Shader ourShader("6.2.coordinate_systems.vs", "6.2.coordinate_systems.fs");// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------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;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// texture coord attributeglVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// load and create a texture // -------------------------unsigned int texture1;// texture 1// ---------glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);// set the texture wrapping parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.unsigned char *data = stbi_load("lena.jpg", &width, &height, &nrChannels, 0);if (data){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);// tell opengl for each sampler to which texture unit it belongs to (only has to be done once)// -------------------------------------------------------------------------------------------ourShader.use();ourShader.setInt("texture1", 0);// render loop// -----------while (!glfwWindowShouldClose(window)){// per-frame time logic// --------------------float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;// input// -----processInput(window);// render// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // bind textures on corresponding texture unitsglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);// activate shaderourShader.use();// pass projection matrix to shader (note that in this case it could change every frame)glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);ourShader.setMat4("projection", projection);// camera/view transformationglm::mat4 view = camera.GetViewMatrix();ourShader.setMat4("view", view);// render boxesglBindVertexArray(VAO);// calculate the model matrix for each object and pass it to shader before drawingglm::mat4 model = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix firstmodel = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));//隨時間旋轉一個角度,構造modelourShader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose:// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);// glfw: terminate, clearing all previously allocated GLFW resources.// ------------------------------------------------------------------glfwTerminate();return 0;
}// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime);
}// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height);
}// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{camera.ProcessMouseScroll(yoffset);
}

?

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/143915.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息