版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2017.09.05 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò)议经,最近也想學(xué)著使用這個(gè)圖形庫(kù),感覺(jué)還是很有意思谴返,也就自然想著好好的總結(jié)一下煞肾,希望對(duì)大家能有所幫助。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式嗓袱、對(duì)象籍救、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
歐拉角
歐拉角(Euler Angle)
是可以表示3D空間中任何旋轉(zhuǎn)的3個(gè)值渠抹,由萊昂哈德·歐拉(Leonhard Euler)在18世紀(jì)提出蝙昙。一共有3種歐拉角:俯仰角(Pitch)、偏航角(Yaw)和滾轉(zhuǎn)角(Roll)
梧却,下面的圖片展示了它們的含義:
俯仰角是描述我們?nèi)绾瓮匣蛲驴吹慕瞧娴撸梢栽诘谝粡垐D中看到。第二張圖展示了偏航角放航,偏航角表示我們往左和往右看的程度烈拒。滾轉(zhuǎn)角代表我們?nèi)绾畏瓭L攝像機(jī),通常在太空飛船的攝像機(jī)中使用。每個(gè)歐拉角都有一個(gè)值來(lái)表示荆几,把三個(gè)角結(jié)合起來(lái)我們就能夠計(jì)算3D空間中任何的旋轉(zhuǎn)向量了吓妆。
對(duì)于我們的攝像機(jī)系統(tǒng)來(lái)說(shuō),我們只關(guān)心俯仰角和偏航角伴郁,所以我們不會(huì)討論滾轉(zhuǎn)角耿战。給定一個(gè)俯仰角和偏航角,我們可以把它們轉(zhuǎn)換為一個(gè)代表新的方向向量的3D向量焊傅。俯仰角和偏航角轉(zhuǎn)換為方向向量的處理需要一些三角學(xué)知識(shí)剂陡,我們先從最基本的情況開(kāi)始:
如果我們把斜邊邊長(zhǎng)定義為1,我們就能知道鄰邊的長(zhǎng)度是cos x/h=cos x/1=cos x
狐胎,它的對(duì)邊是sin y/h=sin y/1=sin y
鸭栖。這樣我們獲得了能夠得到x和y方向長(zhǎng)度的通用公式,它們?nèi)Q于所給的角度握巢。我們使用它來(lái)計(jì)算方向向量的分量:
這個(gè)三角形看起來(lái)和前面的三角形很像晕鹊,所以如果我們想象自己在xz平面上,看向y軸暴浦,我們可以基于第一個(gè)三角形計(jì)算來(lái)計(jì)算它的長(zhǎng)度/y方向的強(qiáng)度(Strength)(我們往上或往下看多少)溅话。從圖中我們可以看到對(duì)于一個(gè)給定俯仰角的y值等于sin θ。
direction.y = sin(glm::radians(pitch)); // 注意我們先把角度轉(zhuǎn)為弧度
這里我們只更新了y值歌焦,仔細(xì)觀察x和z分量也被影響了飞几。從三角形中我們可以看到它們的值等于:
direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));
看看我們是否能夠?yàn)槠浇钦业叫枰姆至浚?/p>
就像俯仰角的三角形一樣,我們可以看到x分量取決于cos(yaw)
的值独撇,z值同樣取決于偏航角的正弦值屑墨。把這個(gè)加到前面的值中,會(huì)得到基于俯仰角和偏航角的方向向量:
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 譯注:direction代表攝像機(jī)的前軸(Front)纷铣,這個(gè)前軸是和本文第一幅圖片的第二個(gè)攝像機(jī)的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
這樣我們就有了一個(gè)可以把俯仰角和偏航角轉(zhuǎn)化為用來(lái)自由旋轉(zhuǎn)視角的攝像機(jī)的3維方向向量了卵史。你可能會(huì)奇怪:我們?cè)趺吹玫礁┭鼋呛推浇牵?/p>
鼠標(biāo)輸入
偏航角和俯仰角是通過(guò)鼠標(biāo)(或手柄)移動(dòng)獲得的,水平的移動(dòng)影響偏航角搜立,豎直的移動(dòng)影響俯仰角以躯。它的原理就是,儲(chǔ)存上一幀鼠標(biāo)的位置啄踊,在當(dāng)前幀中我們當(dāng)前計(jì)算鼠標(biāo)位置與上一幀的位置相差多少寸潦。如果水平/豎直差別越大那么俯仰角或偏航角就改變?cè)酱螅簿褪菙z像機(jī)需要移動(dòng)更多的距離社痛。
首先我們要告訴GLFW
见转,它應(yīng)該隱藏光標(biāo),并捕捉(Capture)
它蒜哀。捕捉光標(biāo)表示的是斩箫,如果焦點(diǎn)在你的程序上(譯注:即表示你正在操作這個(gè)程序吏砂,Windows中擁有焦點(diǎn)的程序標(biāo)題欄通常是有顏色的那個(gè),而失去焦點(diǎn)的程序標(biāo)題欄則是灰色的)乘客,光標(biāo)應(yīng)該停留在窗口中(除非程序失去焦點(diǎn)或者退出)狐血。我們可以用一個(gè)簡(jiǎn)單地配置調(diào)用來(lái)完成:
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
在調(diào)用這個(gè)函數(shù)之后,無(wú)論我們?cè)趺慈ヒ苿?dòng)鼠標(biāo)易核,光標(biāo)都不會(huì)顯示了匈织,它也不會(huì)離開(kāi)窗口。對(duì)于FPS攝像機(jī)系統(tǒng)來(lái)說(shuō)非常完美牡直。
為了計(jì)算俯仰角和偏航角缀匕,我們需要讓GLFW監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件。(和鍵盤輸入相似)我們會(huì)用一個(gè)回調(diào)函數(shù)來(lái)完成碰逸,函數(shù)的原型如下:
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
這里的xpos
和ypos
代表當(dāng)前鼠標(biāo)的位置乡小。當(dāng)我們用GLFW注冊(cè)了回調(diào)函數(shù)之后,鼠標(biāo)一移動(dòng)mouse_callback
函數(shù)就會(huì)被調(diào)用:
glfwSetCursorPosCallback(window, mouse_callback);
在處理FPS風(fēng)格攝像機(jī)的鼠標(biāo)輸入的時(shí)候饵史,我們必須在最終獲取方向向量之前做下面這幾步:
- 計(jì)算鼠標(biāo)距上一幀的偏移量满钟。
- 把偏移量添加到攝像機(jī)的俯仰角和偏航角中。
- 對(duì)偏航角和俯仰角進(jìn)行最大和最小值的限制胳喷。
- 計(jì)算方向向量湃番。
第一步是計(jì)算鼠標(biāo)自上一幀的偏移量。我們必須先在程序中儲(chǔ)存上一幀的鼠標(biāo)位置吭露,我們把它的初始值設(shè)置為屏幕的中心(屏幕的尺寸是800x600):
float lastX = 400, lastY = 300;
然后在鼠標(biāo)的回調(diào)函數(shù)中我們計(jì)算當(dāng)前幀和上一幀鼠標(biāo)位置的偏移量:
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 注意這里是相反的吠撮,因?yàn)閥坐標(biāo)是從底部往頂部依次增大的
lastX = xpos;
lastY = ypos;
float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;
注意我們把偏移量乘以了sensitivity(靈敏度)值。如果我們忽略這個(gè)值奴饮,鼠標(biāo)移動(dòng)就會(huì)太大了;你可以自己實(shí)驗(yàn)一下择浊,找到適合自己的靈敏度值戴卜。
接下來(lái)我們把偏移量加到全局變量pitch
和yaw
上:
yaw += xoffset;
pitch += yoffset;
第三步,我們需要給攝像機(jī)添加一些限制琢岩,這樣攝像機(jī)就不會(huì)發(fā)生奇怪的移動(dòng)了(這樣也會(huì)避免一些奇怪的問(wèn)題)投剥。對(duì)于俯仰角,要讓用戶不能看向高于89
度的地方(在90度時(shí)視角會(huì)發(fā)生逆轉(zhuǎn)担孔,所以我們把89度作為極限)江锨,同樣也不允許小于-89
度。這樣能夠保證用戶只能看到天空或腳下糕篇,但是不能超越這個(gè)限制啄育。我們可以在值超過(guò)限制的時(shí)候?qū)⑵涓臑闃O限值來(lái)實(shí)現(xiàn):
if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0f)
pitch = -89.0f;
注意我們沒(méi)有給偏航角設(shè)置限制,這是因?yàn)槲覀儾幌M拗朴脩舻乃叫D(zhuǎn)拌消。當(dāng)然挑豌,給偏航角設(shè)置限制也很容易,如果你愿意可以自己實(shí)現(xiàn)。
第四也是最后一步氓英,就是通過(guò)俯仰角和偏航角來(lái)計(jì)算以得到真正的方向向量:
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);
計(jì)算出來(lái)的方向向量就會(huì)包含根據(jù)鼠標(biāo)移動(dòng)計(jì)算出來(lái)的所有旋轉(zhuǎn)了侯勉。由于cameraFront
向量已經(jīng)包含在GLM
的lookAt
函數(shù)中,我們這就沒(méi)什么問(wèn)題了铝阐。
如果你現(xiàn)在運(yùn)行代碼址貌,你會(huì)發(fā)現(xiàn)在窗口第一次獲取焦點(diǎn)的時(shí)候攝像機(jī)會(huì)突然跳一下。這個(gè)問(wèn)題產(chǎn)生的原因是徘键,在你的鼠標(biāo)移動(dòng)進(jìn)窗口的那一刻练对,鼠標(biāo)回調(diào)函數(shù)就會(huì)被調(diào)用,這時(shí)候的xpos和ypos會(huì)等于鼠標(biāo)剛剛進(jìn)入屏幕的那個(gè)位置啊鸭。這通常是一個(gè)距離屏幕中心很遠(yuǎn)的地方锹淌,因而產(chǎn)生一個(gè)很大的偏移量,所以就會(huì)跳了赠制。我們可以簡(jiǎn)單的使用一個(gè)bool變量檢驗(yàn)我們是否是第一次獲取鼠標(biāo)輸入赂摆,如果是,那么我們先把鼠標(biāo)的初始位置更新為xpos和ypos值钟些,這樣就能解決這個(gè)問(wèn)題烟号;接下來(lái)的鼠標(biāo)移動(dòng)就會(huì)使用剛進(jìn)入的鼠標(biāo)位置坐標(biāo)來(lái)計(jì)算偏移量了:
if(firstMouse) // 這個(gè)bool變量初始時(shí)是設(shè)定為true的
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
最后的代碼應(yīng)該是這樣的:
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);
}
現(xiàn)在我們就可以自由地在3D場(chǎng)景中移動(dòng)了!
縮放
作為我們攝像機(jī)系統(tǒng)的一個(gè)附加內(nèi)容政恍,我們還會(huì)來(lái)實(shí)現(xiàn)一個(gè)縮放(Zoom)
接口汪拥。在之前的教程中我們說(shuō)視野(Field of View)
或fov定義了我們可以看到場(chǎng)景中多大的范圍。當(dāng)視野變小時(shí)篙耗,場(chǎng)景投影出來(lái)的空間就會(huì)減小迫筑,產(chǎn)生放大(Zoom In)
了的感覺(jué)。我們會(huì)使用鼠標(biāo)的滾輪來(lái)放大宗弯。與鼠標(biāo)移動(dòng)脯燃、鍵盤輸入一樣,我們需要一個(gè)鼠標(biāo)滾輪的回調(diào)函數(shù):
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;
}
當(dāng)滾動(dòng)鼠標(biāo)滾輪的時(shí)候蒙保,yoffset值代表我們豎直滾動(dòng)的大小辕棚。當(dāng)scroll_callback
函數(shù)被調(diào)用后,我們改變?nèi)肿兞縡ov變量的內(nèi)容邓厕。因?yàn)?5.0f是默認(rèn)的視野值逝嚎,我們將會(huì)把縮放級(jí)別(Zoom Level)
限制在1.0f到45.0f。
我們現(xiàn)在在每一幀都必須把透視投影矩陣上傳到GPU详恼,但現(xiàn)在使用fov變量作為它的視野:
projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
最后不要忘記注冊(cè)鼠標(biāo)滾輪的回調(diào)函數(shù):
glfwSetScrollCallback(window, scroll_callback);
現(xiàn)在补君,我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的攝像機(jī)系統(tǒng)了,它能夠讓我們?cè)?D環(huán)境中自由移動(dòng)昧互。
你可以去自由地實(shí)驗(yàn)赚哗,如果遇到困難她紫,可以對(duì)比源代碼。
注意屿储,使用歐拉角的攝像機(jī)系統(tǒng)并不完美贿讹。根據(jù)你的視角限制或者是配置,你仍然可能引入萬(wàn)向節(jié)死鎖問(wèn)題够掠。最好的攝像機(jī)系統(tǒng)是使用四元數(shù)(Quaternions)的民褂,但我們將會(huì)把這個(gè)留到后面討論。(譯注:這里可以查看四元數(shù)攝像機(jī)的實(shí)現(xiàn))
攝像機(jī)類
接下來(lái)的教程中疯潭,我們將會(huì)一直使用一個(gè)攝像機(jī)來(lái)瀏覽場(chǎng)景赊堪,從各個(gè)角度觀察結(jié)果。然而竖哩,由于一個(gè)攝像機(jī)會(huì)占用每篇教程很大的篇幅哭廉,我們將會(huì)從細(xì)節(jié)抽象出來(lái),創(chuàng)建我們自己的攝像機(jī)對(duì)象相叁,它會(huì)完成大多數(shù)的工作遵绰,而且還會(huì)提供一些附加的功能。與著色器教程不同增淹,我們不會(huì)帶你一步一步創(chuàng)建攝像機(jī)類椿访,我們只會(huì)提供你一份(有完整注釋的)代碼,如果你想知道它的內(nèi)部構(gòu)造的話可以自己去閱讀虑润。
和著色器對(duì)象一樣成玫,我們把攝像機(jī)類寫在一個(gè)單獨(dú)的頭文件中。你可以在這里找到它拳喻,你現(xiàn)在應(yīng)該能夠理解所有的代碼了哭当。我們建議您至少看一看這個(gè)類,看看如何創(chuàng)建一個(gè)自己的攝像機(jī)類冗澈。
我們介紹的攝像機(jī)系統(tǒng)是一個(gè)FPS風(fēng)格的攝像機(jī)钦勘,它能夠滿足大多數(shù)情況需要,而且與歐拉角兼容渗柿,但是在創(chuàng)建不同的攝像機(jī)系統(tǒng)个盆,比如飛行模擬攝像機(jī)脖岛,時(shí)就要當(dāng)心朵栖。每個(gè)攝像機(jī)系統(tǒng)都有自己的優(yōu)點(diǎn)和不足,所以確保對(duì)它們進(jìn)行了詳細(xì)研究柴梆。比如陨溅,這個(gè)FPS攝像機(jī)不允許俯仰角大于90度,而且我們使用了一個(gè)固定的上向量(0, 1, 0)绍在,這在需要考慮滾轉(zhuǎn)角的時(shí)候就不能用了门扇。
使用新攝像機(jī)對(duì)象雹有,更新后版本的源碼可以在這里找到。
后記
未完臼寄,待續(xù)~~~