版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2017.09.05 |
前言
OpenGL 圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫,感覺還是很有意思,也就自然想著好好的總結(jié)一下谆级,希望對(duì)大家能有所幫助。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式讼积、對(duì)象肥照、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
攝像機(jī)
以下內(nèi)容來自LearnOpenGL CN
OpenGL
本身沒有攝像機(jī)(Camera)
的概念勤众,但我們可以通過把場(chǎng)景中的所有物體往相反方向移動(dòng)的方式來模擬出攝像機(jī)舆绎,產(chǎn)生一種我們?cè)谝苿?dòng)的感覺,而不是場(chǎng)景在移動(dòng)们颜。
本節(jié)我們將會(huì)討論如何在OpenGL中配置一個(gè)攝像機(jī)吕朵,并且將會(huì)討論FPS
風(fēng)格的攝像機(jī),讓你能夠在3D場(chǎng)景中自由移動(dòng)窥突。我們也會(huì)討論鍵盤和鼠標(biāo)輸入边锁,最終完成一個(gè)自定義的攝像機(jī)類。
攝像機(jī)/觀察空間
當(dāng)我們討論攝像機(jī)/觀察空間(Camera/View Space)的時(shí)候波岛,是在討論以攝像機(jī)的視角作為場(chǎng)景原點(diǎn)時(shí)場(chǎng)景中所有的頂點(diǎn)坐標(biāo):觀察矩陣把所有的世界坐標(biāo)變換為相對(duì)于攝像機(jī)位置與方向的觀察坐標(biāo)。要定義一個(gè)攝像機(jī)音半,我們需要它在世界空間中的位置则拷、觀察的方向、一個(gè)指向它右測(cè)的向量以及一個(gè)指向它上方的向量曹鸠。細(xì)心的讀者可能已經(jīng)注意到我們實(shí)際上創(chuàng)建了一個(gè)三個(gè)單位軸相互垂直的煌茬、以攝像機(jī)的位置為原點(diǎn)的坐標(biāo)系。
1. 攝像機(jī)位置
獲取攝像機(jī)位置很簡(jiǎn)單彻桃。攝像機(jī)位置簡(jiǎn)單來說就是世界空間中一個(gè)指向攝像機(jī)位置的向量坛善。我們把攝像機(jī)位置設(shè)置為上一節(jié)中的那個(gè)相同的位置:
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
不要忘記正z軸是從屏幕指向你的,如果我們希望攝像機(jī)向后移動(dòng)邻眷,我們就沿著z軸的正方向移動(dòng)眠屎。
2. 攝像機(jī)方向
下一個(gè)需要的向量是攝像機(jī)的方向,這里指的是攝像機(jī)指向哪個(gè)方向∷寥模現(xiàn)在我們讓攝像機(jī)指向場(chǎng)景原點(diǎn):(0, 0, 0)改衩。還記得如果將兩個(gè)矢量相減,我們就能得到這兩個(gè)矢量的差嗎驯镊?用場(chǎng)景原點(diǎn)向量減去攝像機(jī)位置向量的結(jié)果就是攝像機(jī)的指向向量葫督。由于我們知道攝像機(jī)指向z軸負(fù)方向竭鞍,但我們希望方向向量(Direction Vector)指向攝像機(jī)的z軸正方向。如果我們交換相減的順序橄镜,我們就會(huì)獲得一個(gè)指向攝像機(jī)正z軸方向的向量:
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
方向向量(Direction Vector)
并不是最好的名字偎快,因?yàn)樗鼘?shí)際上指向從它到目標(biāo)向量的相反方向(譯注:注意看前面的那個(gè)圖,藍(lán)色的方向向量大概指向z軸的正方向洽胶,與攝像機(jī)實(shí)際指向的方向是正好相反的)晒夹。
3. 右軸
我們需要的另一個(gè)向量是一個(gè)右向量(Right Vector)
,它代表攝像機(jī)空間的x軸的正方向妖异。為獲取右向量我們需要先使用一個(gè)小技巧:先定義一個(gè)上向量(Up Vector)惋戏。接下來把上向量和第二步得到的方向向量進(jìn)行叉乘。兩個(gè)向量叉乘的結(jié)果會(huì)同時(shí)垂直于兩向量他膳,因此我們會(huì)得到指向x軸正方向的那個(gè)向量(如果我們交換兩個(gè)向量叉乘的順序就會(huì)得到相反的指向x軸負(fù)方向的向量):
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));
4. 上軸
現(xiàn)在我們已經(jīng)有了x軸向量和z軸向量响逢,獲取一個(gè)指向攝像機(jī)的正y軸向量就相對(duì)簡(jiǎn)單了:我們把右向量和方向向量進(jìn)行叉乘:
glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
Look At
使用矩陣的好處之一是如果你使用3個(gè)相互垂直(或非線性)的軸定義了一個(gè)坐標(biāo)空間,你可以用這3個(gè)軸外加一個(gè)平移向量來創(chuàng)建一個(gè)矩陣棕孙,并且你可以用這個(gè)矩陣乘以任何向量來將其變換到那個(gè)坐標(biāo)空間舔亭。這正是LookAt矩陣所做的,現(xiàn)在我們有了3個(gè)相互垂直的軸和一個(gè)定義攝像機(jī)空間的位置坐標(biāo)蟀俊,我們可以創(chuàng)建我們自己的LookAt矩陣
钦铺。
其中R是右向量,U是上向量肢预,D是方向向量P是攝像機(jī)位置向量矛洞。注意,位置向量是相反的烫映,因?yàn)槲覀冏罱K希望把世界平移到與我們自身移動(dòng)的相反方向沼本。把這個(gè)LookAt矩陣作為觀察矩陣可以很高效地把所有世界坐標(biāo)變換到剛剛定義的觀察空間。LookAt矩陣就像它的名字表達(dá)的那樣:它會(huì)創(chuàng)建一個(gè)看著(Look at)給定目標(biāo)的觀察矩陣锭沟。
幸運(yùn)的是抽兆,GLM已經(jīng)提供了這些支持。我們要做的只是定義一個(gè)攝像機(jī)位置族淮,一個(gè)目標(biāo)位置和一個(gè)表示世界空間中的上向量的向量(我們計(jì)算右向量使用的那個(gè)上向量)辫红。接著GLM就會(huì)創(chuàng)建一個(gè)LookAt矩陣,我們可以把它當(dāng)作我們的觀察矩陣:
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
函數(shù)需要一個(gè)位置祝辣、目標(biāo)和上向量贴妻。它會(huì)創(chuàng)建一個(gè)和在上一節(jié)使用的一樣的觀察矩陣。
在討論用戶輸入之前蝙斜,我們先來做些有意思的事揍瑟,把我們的攝像機(jī)在場(chǎng)景中旋轉(zhuǎn)。我們會(huì)將攝像機(jī)的注視點(diǎn)保持在(0, 0, 0)乍炉。
我們需要用到一點(diǎn)三角學(xué)的知識(shí)來在每一幀創(chuàng)建一個(gè)x和z坐標(biāo)绢片,它會(huì)代表圓上的一點(diǎn)滤馍,我們將會(huì)使用它作為攝像機(jī)的位置。通過重新計(jì)算x和y坐標(biāo)底循,我們會(huì)遍歷圓上的所有點(diǎn)巢株,這樣攝像機(jī)就會(huì)繞著場(chǎng)景旋轉(zhuǎn)了。我們預(yù)先定義這個(gè)圓的半徑radius熙涤,在每次渲染迭代中使用GLFW
的glfwGetTime
函數(shù)重新創(chuàng)建觀察矩陣阁苞,來擴(kuò)大這個(gè)圓。
float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
運(yùn)行代碼祠挫,應(yīng)該會(huì)得到下面的結(jié)果:
通過這一小段代碼那槽,攝像機(jī)現(xiàn)在會(huì)隨著時(shí)間流逝圍繞場(chǎng)景轉(zhuǎn)動(dòng)了。自己試試改變半徑和位置/方向參數(shù)等舔,看看LookAt矩陣是如何工作的骚灸。同時(shí),如果你在哪卡住的話慌植,這里有源碼甚牲。
自由移動(dòng)
讓攝像機(jī)繞著場(chǎng)景轉(zhuǎn)的確很有趣,但是讓我們自己移動(dòng)攝像機(jī)會(huì)更有趣蝶柿!首先我們必須設(shè)置一個(gè)攝像機(jī)系統(tǒng)丈钙,所以在我們的程序前面定義一些攝像機(jī)變量很有用:
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);
LookAt
函數(shù)現(xiàn)在成了:
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
我們首先將攝像機(jī)位置設(shè)置為之前定義的cameraPos。方向是當(dāng)前的位置加上我們剛剛定義的方向向量交汤。這樣能保證無論我們?cè)趺匆苿?dòng)雏赦,攝像機(jī)都會(huì)注視著目標(biāo)方向。讓我們擺弄一下這些向量芙扎,在按下某些按鈕時(shí)更新cameraPos向量喉誊。
我們已經(jīng)為GLFW
的鍵盤輸入定義過一個(gè)processInput
函數(shù)了,我們來新添加幾個(gè)需要檢查的按鍵命令:
void processInput(GLFWwindow *window)
{
...
float cameraSpeed = 0.05f; // adjust accordingly
if (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;
}
當(dāng)我們按下WASD
鍵的任意一個(gè)纵顾,攝像機(jī)的位置都會(huì)相應(yīng)更新。如果我們希望向前或向后移動(dòng)栋盹,我們就把位置向量加上或減去方向向量施逾。如果我們希望向左右移動(dòng),我們使用叉乘來創(chuàng)建一個(gè)右向量(Right Vector)
例获,并沿著它相應(yīng)移動(dòng)就可以了汉额。這樣就創(chuàng)建了使用攝像機(jī)時(shí)熟悉的橫移(Strafe)
效果。
注意榨汤,我們對(duì)右向量進(jìn)行了標(biāo)準(zhǔn)化蠕搜。如果我們沒對(duì)這個(gè)向量進(jìn)行標(biāo)準(zhǔn)化,最后的叉乘結(jié)果會(huì)根據(jù)cameraFront變量返回大小不同的向量收壕。如果我們不對(duì)向量進(jìn)行標(biāo)準(zhǔn)化妓灌,我們就得根據(jù)攝像機(jī)的朝向不同加速或減速移動(dòng)了轨蛤,但如果進(jìn)行了標(biāo)準(zhǔn)化移動(dòng)就是勻速的。
現(xiàn)在你就應(yīng)該能夠移動(dòng)攝像機(jī)了虫埂,雖然移動(dòng)速度和系統(tǒng)有關(guān)祥山,你可能會(huì)需要調(diào)整一下cameraSpeed
。
移動(dòng)速度
目前我們的移動(dòng)速度是個(gè)常量掉伏。理論上沒什么問題缝呕,但是實(shí)際情況下根據(jù)處理器的能力不同,有些人可能會(huì)比其他人每秒繪制更多幀斧散,也就是以更高的頻率調(diào)用processInput
函數(shù)供常。結(jié)果就是,根據(jù)配置的不同鸡捐,有些人可能移動(dòng)很快栈暇,而有些人會(huì)移動(dòng)很慢。當(dāng)你發(fā)布你的程序的時(shí)候闯参,你必須確保它在所有硬件上移動(dòng)速度都一樣瞻鹏。
圖形程序和游戲通常會(huì)跟蹤一個(gè)時(shí)間差(Deltatime)
變量,它儲(chǔ)存了渲染上一幀所用的時(shí)間鹿寨。我們把所有速度都去乘以deltaTime值新博。結(jié)果就是,如果我們的deltaTime很大脚草,就意味著上一幀的渲染花費(fèi)了更多時(shí)間赫悄,所以這一幀的速度需要變得更高來平衡渲染所花去的時(shí)間。使用這種方法時(shí)馏慨,無論你的電腦快還是慢埂淮,攝像機(jī)的速度都會(huì)相應(yīng)平衡,這樣每個(gè)用戶的體驗(yàn)就都一樣了写隶。
我們跟蹤兩個(gè)全局變量來計(jì)算出deltaTime值:
float deltaTime = 0.0f; // 當(dāng)前幀與上一幀的時(shí)間差
float lastFrame = 0.0f; // 上一幀的時(shí)間
在每一幀中我們計(jì)算出新的deltaTime以備后用倔撞。
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
現(xiàn)在我們有了deltaTime,在計(jì)算速度的時(shí)候可以將其考慮進(jìn)去了:
void processInput(GLFWwindow *window)
{
float cameraSpeed = 2.5f * deltaTime;
...
}
與前面的部分結(jié)合在一起慕趴,我們有了一個(gè)更流暢點(diǎn)的攝像機(jī)系統(tǒng):
現(xiàn)在我們有了一個(gè)在任何系統(tǒng)上移動(dòng)速度都一樣的攝像機(jī)痪蝇。同樣,如果你卡住了冕房,查看一下源碼躏啰。我們可以看到任何移動(dòng)都會(huì)影響返回的deltaTime值。
視角移動(dòng)
只用鍵盤移動(dòng)沒什么意思耙册。特別是我們還不能轉(zhuǎn)向给僵,移動(dòng)很受限制。是時(shí)候加入鼠標(biāo)了详拙!
為了能夠改變視角帝际,我們需要根據(jù)鼠標(biāo)的輸入改變cameraFront
向量蔓同。然而,根據(jù)鼠標(biāo)移動(dòng)改變方向向量有點(diǎn)復(fù)雜胡本,需要一些三角學(xué)知識(shí)牌柄。如果你對(duì)三角學(xué)知之甚少,別擔(dān)心侧甫,你可以跳過這一部分珊佣,直接復(fù)制粘貼我們的代碼;當(dāng)你想了解更多的時(shí)候再回來看披粟。
后記
未完咒锻,待續(xù)~~~