iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
自我挑戰組

寫遊戲初體驗系列 第 15

Day 15 [OpenGL] Camera

  • 分享至 

  • xImage
  •  

Camera

OpenGL本身沒有 Camera 的概念,但我們可以通過把場景中的所有物體往相反方向移動的方式來模擬,產生一種我們在移動的感覺,而不是場景在移動。

View(Camera) Space

View(Camera) Space 是指將 Camera 當作原點(0, 0, 0)座標,並將世界座標變換為相對於 Camera 位置與方向的座標。
要定義一個 Camera,我們需要以下的東西。

  • 位置
  • 觀察的方向
  • 相機頭頂的方向向量
    • 可以用外積求出

位置

要獲得 Camera 的位置很簡單。簡單來說就是世界中的一個指向 Camera 的向量。我們就把 Camera 的位置設置成語上篇中的一樣。

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

觀察方向

下一個需要的向量是 Camera 的方向,這裡指的是 Camera 指向哪個方向。現在我們讓 Camera 指向場景原點:(0, 0, 0)。如果將兩個矢量相減,我們就能得到這兩個矢量的差,用場景原點向量減去 Camera 位置向量的結果就是攝像機的指向向量。由於我們知道攝像機指向z軸負方向,但我們希望方向向量(Direction Vector)指向攝像機的z軸正方向。如果我們交換相減的順序,我們就會獲得一個指向攝像機正z軸方向的向量。(上圖2)

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

右軸

我們需要的另一個向量是一個右向量(Right Vector),它代表攝像機空間的x軸的正方向。
為了獲取右向量,我們要先定義一個上向量(world space),接著跟觀察方向作外積,就可以得到了。

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

上軸

有了x(右)z(觀察方向)之後,要得出上向量就簡單了,將兩個外積就行了。

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

在外積的幫助下我們成功的構成了 View Space。如果對於外積有困惑者,可以看看這裡

Look At 矩陣

R 是右向量、U是上向量、D是方向向量,P 是相機位置,注意位移是相反方向,是因為前面提到我們希望將世界往相機的相反方向移動(因為 OpenGL 的相機在 (0, 0, 0)

使用矩陣的好處之一是如果你使用3個相互垂直(或非線性)的軸定義了一個坐標空間,你可以用這3個軸外加一個平移向量來創建一個矩陣,並且你可以用這個矩陣乘以任何向量來將其變換到那個坐標空間。

  • GLM 提供了直接產生 Look At Matrix 的實用功能,只要提供相機位置、觀察目標、跟世界空間的上向量
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)   // 上向量
);

自由移動

如果能夠自己控制 Camera 的話就可以想看哪就看哪對吧,所以得改一下實作。

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);
glm::vec3 cameraRight = glm::normalize(glm::cross(cameraFront, cameraUp));

Look At 變成:

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

接下來新增移動功能

if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
    cameraPos += cameraSpeed * cameraFront;
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
    cameraPos -= cameraSpeed * cameraFront;
//
if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
    cameraPos -= cameraSpeed * cameraRight;
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
    cameraPos += cameraSpeed * cameraRight;
//
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space))
    cameraPos += cameraSpeed * cameraUp;
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl))
    cameraPos -= cameraSpeed * cameraUp;

這樣就可以用 WASD 移動了!

視角移動

歐拉角

歐拉角(Euler Angle)可以表示空間中三軸的旋轉。俯仰角(Pitch)、偏擺角(Yaw)和翻滾角(Roll)

在傳統 FPS 中不會有 Roll 所以這裡不討論,有了歐拉角後就可以算出旋轉後的新的方向向量

滑鼠輸入

通常根據滑鼠輸入變動視角的處理方法是:

  • 計算滑鼠位置的偏移量(相對上個 frame)
  • 把偏移量加到 pitchyaw
  • 限制最大跟最小值
  • 計算方向向量
// 滑鼠目前為位置
auto [x, y] = sf::Mouse::getPosition(window);
glm::vec2 nowCursorPos(x, window.getSize().y - y);
// 螢幕中心
static glm::vec2 screenCenter(window.getSize().x / 2, window.getSize().y / 2);
sf::Mouse::setPosition({window.getSize().x / 2, window.getSize().y / 2} , window);
// 偏移量
glm::vec2 off = nowCursorPos - screenCenter;

把偏移量乘上滑鼠敏感度

off *= sensitivity;

把角度加上偏移量,並限制最大、最小 (不限制水平旋轉是因為古早 FPS 都是視角轉 = 人轉)

static float pitch  = 0.f;
static float yaw = -90.f;  // 面向 -z
static float sensitivity = 0.05f;

pitch += off.y;
yaw += off.x;
// 避免 yaw 因為 float 精度問題
if(yaw > 360.f || yaw < -360.f) yaw = glm::mod(yaw, 360.f);
// 限制 pitch
if(pitch > 89.f) pitch = 89.f;
if(pitch < -89.f) pitch = -89.f;

最後計算 Camera 新的方向向量

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

視角縮放

把 FOV 變小時,會產生一種放大的感覺,我們使用滑鼠滾輪來進行縮放。SFML 裏頭偵測滑鼠滾輪只能用事件

// Event update
if(event.type == sf::Event::MouseWheelScrolled)
{
    mouseWheelDelta = event.mouseWheelScroll.delta;
}

// In Update
if(mouseWheelDelta != 0.f)
{
    fov += mouseWheelDelta;
    if(fov < 1.f) fov = 1.f;
    if(fov > 45.f) fov = 45.f;
    mouseWheelDelta = 0.f;
}

完整code在這裡

參考資料

https://learnopengl.com/Getting-started/Camera


上一篇
Day 14 [OpenGL] Coordinate
下一篇
Day 16 ImGui
系列文
寫遊戲初體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言