久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

攝像機(jī)

 方海龍的書館 2016-03-14

攝像機(jī)(Camera)

原文 Camera
作者 JoeyDeVries
翻譯 Django
校對(duì) Geequlim, BLumia

前面的教程中我們討論了觀察矩陣以及如何使用觀察矩陣移動(dòng)場景,。OpenGL本身沒有攝像機(jī)的概念,,但我們可以通過把場景中的所有物體往相反方向移動(dòng)的方式來模擬出攝像機(jī),這樣感覺就像我們?cè)谝苿?dòng),,而不是場景在移動(dòng)。

本節(jié)我們將會(huì)討論如何在OpenGL中模擬一個(gè)攝像機(jī),將會(huì)討論FPS風(fēng)格的可自由在3D場景中移動(dòng)的攝像機(jī)。我們也會(huì)討論鍵盤和鼠標(biāo)輸入,,最終完成一個(gè)自定義的攝像機(jī)類。

攝像機(jī)/觀察空間(Camera/View Space)

當(dāng)我們討論攝像機(jī)/觀察空間的時(shí)候,,是我們?cè)谟懻撘詳z像機(jī)的透視圖作為場景原點(diǎn)時(shí)場景中所有可見頂點(diǎn)坐標(biāo),。觀察矩陣把所有的世界坐標(biāo)變換到觀察坐標(biāo),這些新坐標(biāo)是相對(duì)于攝像機(jī)的位置和方向的,。定義一個(gè)攝像機(jī),,我們需要一個(gè)攝像機(jī)在世界空間中的位置、觀察的方向,、一個(gè)指向它的右測的向量以及一個(gè)指向它上方的向量,。細(xì)心的讀者可能已經(jīng)注意到我們實(shí)際上創(chuàng)建了一個(gè)三個(gè)單位軸相互垂直的、以攝像機(jī)的位置為原點(diǎn)的坐標(biāo)系,。

1. 攝像機(jī)位置

獲取攝像機(jī)位置很簡單,。攝像機(jī)位置簡單來說就是世界空間中代表攝像機(jī)位置的向量,。我們把攝像機(jī)位置設(shè)置為前面教程中的那個(gè)相同的位置:

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

Important

不要忘記正z軸是從屏幕指向你的,如果我們希望攝像機(jī)向后移動(dòng),,我們就往z軸正方向移動(dòng),。

2. 攝像機(jī)方向

下一個(gè)需要的向量是攝像機(jī)的方向,比如它指向哪個(gè)方向?,F(xiàn)在我們讓攝像機(jī)指向場景原點(diǎn):(0, 0, 0),。用攝像機(jī)位置向量減去場景原點(diǎn)向量的結(jié)果就是攝像機(jī)指向向量,。由于我們知道攝像機(jī)指向z軸負(fù)方向,,我們希望方向向量指向攝像機(jī)的z軸正方向。如果我們改變相減的順序,,我們就會(huì)獲得一個(gè)指向攝像機(jī)正z軸方向的向量(譯注:注意看前面的那個(gè)圖,,所說的「方向向量/Direction Vector」是指向z的正方向的,而不是攝像機(jī)所注視的那個(gè)方向):

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

Attention

方向向量(Direction Vector)并不是最好的名字,,因?yàn)樗弥赶驈乃侥繕?biāo)向量的相反方向,。

3. 右軸(Right axis)

我們需要的另一個(gè)向量是一個(gè)右向量(Right Vector),它代表攝像機(jī)空間的x軸的正方向,。為獲取右向量我們需要先使用一個(gè)小技巧:定義一個(gè)上向量(Up Vector),。我們把上向量和第二步得到的攝像機(jī)方向向量進(jìn)行叉乘。兩個(gè)向量叉乘的結(jié)果就是同時(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. 上軸(Up axis)

現(xiàn)在我們已經(jīng)有了x軸向量和z軸向量,,獲取攝像機(jī)的正y軸相對(duì)簡單;我們把右向量和方向向量(Direction Vector)進(jìn)行叉乘:

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

在叉乘和一些小技巧的幫助下,,我們創(chuàng)建了所有觀察/攝像機(jī)空間的向量,。對(duì)于想學(xué)到更多數(shù)學(xué)原理的讀者,提示一下,,在線性代數(shù)中這個(gè)處理叫做Gram-Schmidt(葛蘭—施密特)正交,。使用這些攝像機(jī)向量我們就可以創(chuàng)建一個(gè)LookAt矩陣了,它在創(chuàng)建攝像機(jī)的時(shí)候非常有用,。

Look At

使用矩陣的好處之一是如果你定義了一個(gè)坐標(biāo)空間,,里面有3個(gè)相互垂直的軸,你可以用這三個(gè)軸外加一個(gè)平移向量來創(chuàng)建一個(gè)矩陣,,你可以用這個(gè)矩陣乘以任何向量來變換到那個(gè)坐標(biāo)空間,。這正是LookAt矩陣所做的,現(xiàn)在我們有了3個(gè)相互垂直的軸和一個(gè)定義攝像機(jī)空間的位置坐標(biāo),,我們可以創(chuàng)建我們自己的LookAt矩陣了:

是右向量,,是上向量,是方向向量是攝像機(jī)位置向量,。注意,,位置向量是相反的,,因?yàn)槲覀冏罱K希望把世界平移到與我們自身移動(dòng)的相反方向。使用這個(gè)LookAt矩陣坐標(biāo)觀察矩陣可以很高效地把所有世界坐標(biāo)變換為觀察坐標(biāo)LookAt矩陣就像它的名字表達(dá)的那樣:它會(huì)創(chuàng)建一個(gè)觀察矩陣looks at(看著)一個(gè)給定目標(biāo),。

幸運(yùn)的是,,GLM已經(jīng)提供了這些支持。我們要做的只是定義一個(gè)攝像機(jī)位置,,一個(gè)目標(biāo)位置和一個(gè)表示上向量的世界空間中的向量(我們使用上向量計(jì)算右向量),。接著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)和上向量,。它可以創(chuàng)建一個(gè)和前面所說的同樣的觀察矩陣。

在開始做用戶輸入之前,,我們來做些有意思的事,,把我們的攝像機(jī)在場景中旋轉(zhuǎn)。我們的注視點(diǎn)保持在(0, 0, 0),。

我們?cè)诿恳粠紕?chuàng)建x和z坐標(biāo),,這要使用一點(diǎn)三角學(xué)知識(shí)。x和z表示一個(gè)在一個(gè)圓圈上的一點(diǎn),,我們會(huì)使用它作為攝像機(jī)的位置,。通過重復(fù)計(jì)算x和y坐標(biāo),遍歷所有圓圈上的點(diǎn),,這樣攝像機(jī)就會(huì)繞著場景旋轉(zhuǎn)了,。我們預(yù)先定義這個(gè)圓圈的半徑,使用glfwGetTime函數(shù)不斷增加它的值,,在每次渲染迭代創(chuàng)建一個(gè)新的觀察矩陣,。

GLfloat radius = 10.0f;
GLfloat camX = sin(glfwGetTime()) * radius;
GLfloat 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)行代碼你會(huì)得到下面的東西:

這一小段代碼中,攝像機(jī)圍繞場景轉(zhuǎn)動(dòng),。自己試試改變半徑和位置/方向參數(shù),,看看LookAt矩陣是如何工作的。同時(shí),,這里有源碼,、頂點(diǎn)片段著色器。

自由移動(dòng)

讓攝像機(jī)繞著場景轉(zhuǎn)很有趣,,但是讓我們自己移動(dòng)攝像機(jī)更有趣,!首先我們必須設(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);

我們首先設(shè)置之前定義的cameraPos為攝像機(jī)位置,。方向(Direction)是當(dāng)前的位置加上我們剛剛定義的方向向量,。這樣能保證無論我們?cè)趺匆苿?dòng),攝像機(jī)都會(huì)注視目標(biāo),。我們?cè)诎聪履硞€(gè)按鈕時(shí)更新cameraPos向量,。

我們已經(jīng)為GLFW的鍵盤輸入定義了一個(gè)key_callback函數(shù),,我們來添加幾個(gè)新按鍵命令:

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    ...
    GLfloat cameraSpeed = 0.05f;
    if(key == GLFW_KEY_W)
        cameraPos += cameraSpeed * cameraFront;
    if(key == GLFW_KEY_S)
        cameraPos -= cameraSpeed * cameraFront;
    if(key == GLFW_KEY_A)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if(key == GLFW_KEY_D)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;  
}

當(dāng)我們按下WASD鍵,攝像機(jī)的位置都會(huì)相應(yīng)更新,。如果我們希望向前或向后移動(dòng),,我們就把位置向量加上或減去方向向量。如果我們希望向旁邊移動(dòng),,我們做一個(gè)叉乘來創(chuàng)建一個(gè)右向量,,沿著它移動(dòng)就可以了。這樣就創(chuàng)建了類似使用攝像機(jī)橫向,、前后移動(dòng)的效果,。

Important

注意,我們對(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)就是勻速的,。

如果你用這段代碼更新key_callback函數(shù),,你就可以在場景中自由的前后左右移動(dòng)了。

你可能會(huì)注意到這個(gè)攝像機(jī)系統(tǒng)不能同時(shí)朝兩個(gè)方向移動(dòng),,當(dāng)你按下一個(gè)按鍵時(shí),,它會(huì)先頓一下才開始移動(dòng)。這是因?yàn)榇蠖鄶?shù)事件輸入系統(tǒng)一次只能處理一個(gè)鍵盤輸入,,它們的函數(shù)只有當(dāng)我們激活了一個(gè)按鍵時(shí)才被調(diào)用,。大多數(shù)GUI系統(tǒng)都是這樣的,它對(duì)攝像機(jī)來說用并不合理,。我們可以用一些小技巧解決這個(gè)問題,。

這個(gè)技巧是只在回調(diào)函數(shù)中跟蹤哪個(gè)鍵被按下/釋放。在游戲循環(huán)中我們讀取這些值,,檢查那個(gè)按鍵被激活了,,然后做出相應(yīng)反應(yīng)。我們只儲(chǔ)存哪個(gè)鍵被按下/釋放的狀態(tài)信息,,在游戲循環(huán)中對(duì)狀態(tài)做出反應(yīng),,我們來創(chuàng)建一個(gè)布爾數(shù)組代表按下/釋放的鍵:

bool keys[1024];

然后我們必須在key_callback函數(shù)中設(shè)置按下/釋放鍵為truefalse

if(action == GLFW_PRESS)
    keys[key] = true;
else if(action == GLFW_RELEASE)
    keys[key] = false;

我們創(chuàng)建一個(gè)新的叫做do_movement的函數(shù),用它根據(jù)按下的按鍵來更新攝像機(jī)的值:

void do_movement()
{
  // 攝像機(jī)控制
  GLfloat cameraSpeed = 0.01f;
  if(keys[GLFW_KEY_W])
    cameraPos += cameraSpeed * cameraFront;
  if(keys[GLFW_KEY_S])
    cameraPos -= cameraSpeed * cameraFront;
  if(keys[GLFW_KEY_A])
    cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
  if(keys[GLFW_KEY_D])
    cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

之前的代碼移動(dòng)到了do_movement函數(shù)中,。由于所有GLFW的按鍵枚舉都是整數(shù),,我們可以把它們當(dāng)數(shù)組索引使用。

最后,,我們需要在游戲循環(huán)中添加新函數(shù)的調(diào)用:

while(!glfwWindowShouldClose(window))
{
  // 檢測并調(diào)用事件
  glfwPollEvents();
  do_movement();  

  // 渲染
  ...
}

至此,,你可以同時(shí)向多個(gè)方向移動(dòng)了,,并且當(dāng)你按下按鈕也會(huì)立刻運(yùn)動(dòng)了。如遇困難查看源碼,。

移動(dòng)速度

目前我們的移動(dòng)速度是個(gè)常量,。看起來不錯(cuò),,但是實(shí)際情況下根據(jù)處理器的能力不同,,有的人在同一段時(shí)間內(nèi)會(huì)比其他人繪制更多幀。也就是調(diào)用了更多次do_movement函數(shù),。每個(gè)人的運(yùn)動(dòng)速度就都不同了,。當(dāng)你要發(fā)布的你應(yīng)用的時(shí)候,你必須確保在所有硬件上移動(dòng)速度都一樣,。

圖形和游戲應(yīng)用通常有回跟蹤一個(gè)deltaTime變量,,它儲(chǔ)存渲染上一幀所用的時(shí)間。我們把所有速度都去乘以deltaTime值,。當(dāng)我們的deltaTime變大時(shí)意味著上一幀渲染花了更多時(shí)間,,所以這一幀使用這個(gè)更大的deltaTime的值乘以速度,會(huì)獲得更高的速度,,這樣就與上一幀平衡了,。使用這種方法時(shí),無論你的機(jī)器快還是慢,,攝像機(jī)的速度都會(huì)保持一致,,這樣每個(gè)用戶的體驗(yàn)就都一樣了。

我們要用兩個(gè)全局變量來計(jì)算出deltaTime值:

GLfloat deltaTime = 0.0f;   // 當(dāng)前幀遇上一幀的時(shí)間差
GLfloat lastFrame = 0.0f;   // 上一幀的時(shí)間

在每一幀中我們計(jì)算出新的deltaTime以備后用

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

現(xiàn)在我們有了deltaTime在計(jì)算速度的使用可以使用了:

void Do_Movement()
{
  GLfloat cameraSpeed = 5.0f * deltaTime;
  ...
}

與前面的部分結(jié)合在一起,,我們有了一個(gè)更流暢點(diǎn)的攝像機(jī)系統(tǒng):

現(xiàn)在我們有了一個(gè)在任何系統(tǒng)上移動(dòng)速度都一樣的攝像機(jī),。這里是源碼。我們可以看到任何移動(dòng)都會(huì)影響返回的deltaTime值,。

自由觀看

只用鍵盤移動(dòng)沒什么意思,。特別是我們還不能轉(zhuǎn)向。是時(shí)候使用鼠標(biāo)了,!

為了能夠改變方向,,我們必須根據(jù)鼠標(biāo)的輸入改變cameraFront向量。然而,,根據(jù)鼠標(biāo)旋轉(zhuǎn)改變方向向量有點(diǎn)復(fù)雜,,需要更多的三角學(xué)知識(shí)。如果你對(duì)三角學(xué)知之甚少,,別擔(dān)心,,你可以跳過這一部分,直接復(fù)制粘貼我們的代碼;當(dāng)你想了解更多的時(shí)候再回來看,。

歐拉角

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

俯仰角是描述我們?nèi)绾瓮虾屯驴吹慕牵诘谝粡垐D中表示,。第二張圖顯示了偏航角,,偏航角表示我們往左和往右看的大小。滾轉(zhuǎn)角代表我們?nèi)绾畏瓭L攝像機(jī),。每個(gè)歐拉角都有一個(gè)值來表示,,把三個(gè)角結(jié)合起來我們就能夠計(jì)算3D空間中任何的旋轉(zhuǎn)了。

對(duì)于我們的攝像機(jī)系統(tǒng)來說,,我們只關(guān)心俯仰角和偏航角,,所以我們不會(huì)討論滾轉(zhuǎn)角。用一個(gè)給定的俯仰角和偏航角,,我們可以把它們轉(zhuǎn)換為一個(gè)代表新的方向向量的3D向量,。俯仰角和偏航角轉(zhuǎn)換為方向向量的處理需要一些三角學(xué)知識(shí),我們以最基本的情況開始:

如果我們把斜邊邊長定義為1,,我們就能知道鄰邊的長度是,,它的對(duì)邊是,。這樣我們獲得了能夠得到x和y方向的長度的公式,,它們?nèi)Q于所給的角度。我們使用它來計(jì)算方向向量的元素:

這個(gè)三角形看起來和前面的三角形很像,,所以如果我們想象自己在xz平面上,,正望向y軸,我們可以基于第一個(gè)三角形計(jì)算長度/y方向的強(qiáng)度(我們往上或往下看多少),。從圖中我們可以看到一個(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(偏航角)的值,,z值同樣取決于偏航角的正弦值,。把這個(gè)加到前面的值中,會(huì)得到基于俯仰角和偏航角的方向向量:

Important

譯注:這里的球坐標(biāo)與笛卡爾坐標(biāo)的轉(zhuǎn)換把x和z弄反了,,如果你去看最后的源碼,,會(huì)發(fā)現(xiàn)作者在攝像機(jī)源碼那里寫了yaw = yaw – 90,實(shí)際上在這里x就應(yīng)該是sin(glm::radians(yaw)),,z也是同樣處理,,當(dāng)然也可以認(rèn)為是這個(gè)詭異的坐標(biāo)系,但是在這里使用球坐標(biāo)轉(zhuǎn)笛卡爾坐標(biāo)有個(gè)大問題,,就是在初始渲染時(shí),,無法指定攝像機(jī)的初始朝向,,還要花一些功夫自己實(shí)現(xiàn)這個(gè);此外這只能實(shí)現(xiàn)像第一人稱游戲一樣的簡易攝像機(jī),,類似Maya,、Unity3D編輯器窗口的那種攝像機(jī)還是最好自己設(shè)置攝像機(jī)的位置、上,、右,、前軸,在旋轉(zhuǎn)時(shí)用四元數(shù)對(duì)這四個(gè)變量進(jìn)行調(diào)整,,才能獲得更好的效果,,而不是僅僅調(diào)整攝像機(jī)前軸。

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));//譯注:direction代表攝像機(jī)的“前”軸,,但此前軸是和本文第一幅圖片的第二個(gè)攝像機(jī)的direction是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

這樣我們就有了一個(gè)可以把俯仰角和偏航角轉(zhuǎn)化為用來自由旋轉(zhuǎn)的攝像機(jī)的3個(gè)維度的方向向量了,。你可能會(huì)奇怪:我們?cè)趺吹玫礁┭鼋呛推浇牵?/p>

鼠標(biāo)輸入

偏航角和俯仰角是從鼠標(biāo)移動(dòng)獲得的,鼠標(biāo)水平移動(dòng)影響偏航角,,鼠標(biāo)垂直移動(dòng)影響俯仰角,。它的思想是儲(chǔ)存上一幀鼠標(biāo)的位置,在當(dāng)前幀中我們當(dāng)前計(jì)算鼠標(biāo)位置和上一幀的位置相差多少,。如果差別越大那么俯仰角或偏航角就改變?cè)酱蟆?/p>

首先我們要告訴GLFW,,應(yīng)該隱藏光標(biāo),并捕捉(Capture)它,。捕捉鼠標(biāo)意味著當(dāng)應(yīng)用集中焦點(diǎn)到鼠標(biāo)上的時(shí)候光標(biāo)就應(yīng)該留在窗口中(除非應(yīng)用拾取焦點(diǎn)或退出),。我們可以進(jìn)行簡單的配置:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

這個(gè)函數(shù)調(diào)用后,無論我們?cè)趺慈ヒ苿?dòng)鼠標(biāo),,它都不會(huì)顯示了,,也不會(huì)離開窗口。對(duì)于FPS攝像機(jī)系統(tǒng)來說很好:

為計(jì)算俯仰角和偏航角我們需要告訴GLFW監(jiān)聽鼠標(biāo)移動(dòng)事件,。我們用下面的原型創(chuàng)建一個(gè)回調(diào)函數(shù)來做這件事(和鍵盤輸入差不多):

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

這里的xposypos代表當(dāng)前鼠標(biāo)的位置,。我們注冊(cè)了GLFW的回調(diào)函數(shù),鼠標(biāo)一移動(dòng)mouse_callback函數(shù)就被調(diào)用:

glfwSetCursorPosCallback(window, mouse_callback);

在處理FPS風(fēng)格的攝像機(jī)鼠標(biāo)輸入的時(shí)候,,我們必須在獲取最終的方向向量之前做下面這幾步:

  1. 計(jì)算鼠標(biāo)和上一幀的偏移量,。
  2. 把偏移量添加到攝像機(jī)和俯仰角和偏航角中。
  3. 對(duì)偏航角和俯仰角進(jìn)行最大和最小值的限制,。
  4. 計(jì)算方向向量,。

第一步計(jì)算鼠標(biāo)自上一幀的偏移量。我們必須先儲(chǔ)存上一幀的鼠標(biāo)位置,,我們把它的初始值設(shè)置為屏幕的中心(屏幕的尺寸是800乘600):

GLfloat lastX = 400, lastY = 300;

然后在回調(diào)函數(shù)中我們計(jì)算當(dāng)前幀和上一幀鼠標(biāo)位置的偏移量:

GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos; // 注意這里是相反的,,因?yàn)閥坐標(biāo)的范圍是從下往上的
lastX = xpos;
lastY = ypos;

GLfloat sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;

注意我們把偏移量乘以了sensitivity值。如果我們移除它,鼠標(biāo)移動(dòng)就會(huì)太大了,;你可以自己調(diào)整sensitivity的值,。

下面我們把偏移量加到全局變量pitchyaw上:

yaw   += xoffset;
pitch += yoffset;  

第三步我們給攝像機(jī)添加一些限制,這樣攝像機(jī)就不會(huì)發(fā)生奇怪的移動(dòng)了,。對(duì)于俯仰角,,要讓用戶不能看向高于89度(90度時(shí)視角會(huì)逆轉(zhuǎn),所以我們把89度作為極限)的地方,,同樣也不允許小于-89度,。這樣能夠保證用戶只能看到天空或腳下但是不能更進(jìn)一步超越過去。限制可以這樣做:

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

注意我們沒有給偏航角設(shè)置限制是因?yàn)槲覀儾幌M拗朴脩舻乃叫D(zhuǎn),。然而,,給偏航角設(shè)置限制也很容易,只要你愿意,。

第四也是最后一步,,就是通過俯仰角和偏航角來計(jì)算以得到前面提到的實(shí)際方向向量:

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ì)算出方向向量,根據(jù)鼠標(biāo)點(diǎn)的移動(dòng)它包含所有的旋轉(zhuǎn),。由于cameraFront向量已經(jīng)包含在glm::lookAt函數(shù)中,,我們直接去設(shè)置。

如果你現(xiàn)在運(yùn)行代碼,,你會(huì)發(fā)現(xiàn)當(dāng)程序運(yùn)行第一次捕捉到鼠標(biāo)的時(shí)候攝像機(jī)會(huì)突然跳一下,。原因是當(dāng)你的鼠標(biāo)進(jìn)入窗口鼠標(biāo)回調(diào)函數(shù)會(huì)使用這時(shí)的xposypos。這通常是一個(gè)距離屏幕中心很遠(yuǎn)的地方,,因而產(chǎn)生一個(gè)很大的偏移量,,所以就會(huì)跳了。我們可以簡單的使用一個(gè)布爾變量檢驗(yàn)我們是否是第一次獲取鼠標(biāo)輸入,,如果是,,那么我們先把鼠標(biāo)的位置更新為xposypos,這樣就能解決這個(gè)問題,;最后的鼠標(biāo)移動(dòng)會(huì)使用進(jìn)入以后鼠標(biāo)的位置坐標(biāo)來計(jì)算它的偏移量:

if(firstMouse) // 這個(gè)bool變量一開始是設(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;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos; 
    lastX = xpos;
    lastY = ypos;

    GLfloat 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場景中移動(dòng)了!如果你遇到困難,,這是源碼,。

縮放

我們還要往攝像機(jī)系統(tǒng)里加點(diǎn)東西,實(shí)現(xiàn)一個(gè)縮放接口,。前面教程中我們說視野(Field of View或fov)定義了我們可以看到場景中多大的范圍,。當(dāng)視野變小時(shí)可視區(qū)域就會(huì)減小,產(chǎn)生放大了的感覺,。我們用鼠標(biāo)滾輪來放大,。和鼠標(biāo)移動(dòng)、鍵盤輸入一樣我們需要一個(gè)鼠標(biāo)滾輪的回調(diào)函數(shù):

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

yoffset值代表我們滾動(dòng)的大小。當(dāng)scroll_callback函數(shù)調(diào)用后,,我們改變?nèi)?code>aspect變量的內(nèi)容,。因?yàn)?code>45.0f是默認(rèn)的fov,我們將會(huì)把縮放級(jí)別限制在1.0f45.0f,。

我們現(xiàn)在在每一幀都必須把透視投影矩陣上傳到GPU,,但這一次使aspect變量作為它的fov:

projection = glm::perspective(aspect, (GLfloat)WIDTH/(GLfloat)HEIGHT, 0.1f, 100.0f);

最后不要忘記注冊(cè)滾動(dòng)回調(diào)函數(shù):

glfwSetScrollCallback(window, scroll_callback);

現(xiàn)在我們實(shí)現(xiàn)了一個(gè)簡單的攝像機(jī)系統(tǒng),它能夠讓我們?cè)?D環(huán)境中自由移動(dòng),。

自由的去實(shí)驗(yàn),,如果遇到困難對(duì)比源代碼

Important

注意,,使用歐拉角作為攝像機(jī)系統(tǒng)并不完美,。你仍然可能遇到萬向節(jié)死鎖。最好的攝像機(jī)系統(tǒng)是使用四元數(shù)的,,后面會(huì)有討論,。

攝像機(jī)類

接下來的教程我們會(huì)使用一個(gè)攝像機(jī)來瀏覽場景,從各個(gè)角度觀察結(jié)果,。然而由于一個(gè)攝像機(jī)會(huì)占教程的很大的篇幅,,我們會(huì)從細(xì)節(jié)抽象出創(chuàng)建一個(gè)自己的攝像機(jī)對(duì)象。與著色器教程不同我們不會(huì)帶你一步一步創(chuàng)建攝像機(jī)類,,如果你想知道怎么工作的的話,,只會(huì)給你提供一個(gè)(有完整注釋的)源碼。

像著色器對(duì)象一樣,,我們把攝像機(jī)類寫在一個(gè)單獨(dú)的頭文件中,。你可以在這里找到它。你應(yīng)該能夠理解所有的代碼,。我們建議您至少看一看這個(gè)類,,看看如何創(chuàng)建一個(gè)自己的攝像機(jī)類。

Attention

我們介紹的歐拉角FPS風(fēng)格攝像機(jī)系統(tǒng)能夠滿足大多數(shù)情況需要,,但是在創(chuàng)建不同的攝像機(jī)系統(tǒng),,比如飛行模擬就要當(dāng)心。每個(gè)攝像機(jī)系統(tǒng)都有自己的有點(diǎn)和不足,,所以確保對(duì)它們進(jìn)行了詳細(xì)研究,。比如,這個(gè)FPS攝像機(jī)不允許俯仰角大于90度,,由于使用了固定的上向量(0, 1, 0),,我們就不能用滾轉(zhuǎn)角。

使用新的攝像機(jī)對(duì)象的更新后的版本源碼可以在這里找到,。(譯注:總而言之這個(gè)攝像機(jī)實(shí)現(xiàn)并不十分完美,,你可以看看最終的源碼,。建議先看這篇文章,對(duì)旋轉(zhuǎn)有更深的理解后,,你就能做出更好的攝像機(jī)類,,不過本文有些內(nèi)容比如如何防止按鍵停頓和glfw鼠標(biāo)事件實(shí)現(xiàn)攝像機(jī)的注意事項(xiàng)比較重要,其它的就要做一定的取舍了)

練習(xí)

  • 看看你是否能夠變換攝像機(jī)類從而使得其能夠變- 成一個(gè)真正的FPS攝像機(jī)(也就是說不能夠隨意飛行),;你只能夠呆在xz平面上: 參考解答

  • 試著創(chuàng)建你自己的LookAt函數(shù),,使你能夠手動(dòng)創(chuàng)建一個(gè)我們?cè)谝婚_始討論的觀察矩陣。用你的函數(shù)實(shí)現(xiàn)來替換glm的LookAt函數(shù),,看看它是否還能一樣的工作:參考解答

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多