摄像机基础
Camera
Camera 类中持有 CameraPositionerInterface 类的实例,用来切换不同的摄像机;
#pragma once
#include <assert.h>
#include <algorithm>
#include "shared/UtilsMath.h"
#include "shared/Trackball.h"
#include "glm/gtx/euler_angles.hpp"
class CameraPositionerInterface
{
public:
virtual ~CameraPositionerInterface() = default;
virtual glm::mat4 getViewMatrix() const = 0;
virtual glm::vec3 getPosition() const = 0;
};
class Camera final
{
public:
explicit Camera(CameraPositionerInterface& positioner)
: positioner_(&positioner)
{}
Camera(const Camera&) = default;
Camera& operator = (const Camera&) = default;
glm::mat4 getViewMatrix() const { return positioner_->getViewMatrix(); }
glm::vec3 getPosition() const { return positioner_->getPosition(); }
private:
const CameraPositionerInterface* positioner_;
};
CameraPositioner_FirstPerson
第一人称摄像机,最常使用的一种,下面来具体分析
class CameraPositioner_FirstPerson final: public CameraPositionerInterface
{
public:
CameraPositioner_FirstPerson() = default;
CameraPositioner_FirstPerson(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up)
: cameraPosition_(pos)
, cameraOrientation_(glm::lookAt(pos, target, up))
, up_(up)
{}
void update(double deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
{
if (mousePressed)
{
const glm::vec2 delta = mousePos - mousePos_;
const glm::quat deltaQuat = glm::quat(glm::vec3(-mouseSpeed_ * delta.y, mouseSpeed_ * delta.x, 0.0f));
cameraOrientation_ = deltaQuat * cameraOrientation_;
cameraOrientation_ = glm::normalize(cameraOrientation_);
setUpVector(up_);
}
mousePos_ = mousePos;
const glm::mat4 v = glm::mat4_cast(cameraOrientation_);
const glm::vec3 forward = -glm::vec3(v[0][2], v[1][2], v[2][2]);
const glm::vec3 right = glm::vec3(v[0][0], v[1][0], v[2][0]);
const glm::vec3 up = glm::cross(right, forward);
glm::vec3 accel(0.0f);
if (movement_.forward_) accel += forward;
if (movement_.backward_) accel -= forward;
if (movement_.left_) accel -= right;
if (movement_.right_) accel += right;
if (movement_.up_) accel += up;
if (movement_.down_) accel -= up;
if (movement_.fastSpeed_) accel *= fastCoef_;
if (accel == glm::vec3(0))
{
// decelerate naturally according to the damping value
moveSpeed_ -= moveSpeed_ * std::min((1.0f / damping_) * static_cast<float>(deltaSeconds), 1.0f);
}
else
{
// acceleration
moveSpeed_ += accel * acceleration_ * static_cast<float>(deltaSeconds);
const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_;
if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed;
}
cameraPosition_ += moveSpeed_ * static_cast<float>(deltaSeconds);
}
virtual glm::mat4 getViewMatrix() const override
{
const glm::mat4 t = glm::translate(glm::mat4(1.0f), -cameraPosition_);
const glm::mat4 r = glm::mat4_cast(cameraOrientation_);
return r * t;
}
virtual glm::vec3 getPosition() const override
{
return cameraPosition_;
}
void setPosition(const glm::vec3& pos)
{
cameraPosition_ = pos;
}
void resetMousePosition(const glm::vec2& p) { mousePos_ = p; };
void setUpVector(const glm::vec3& up)
{
const glm::mat4 view = getViewMatrix();
const glm::vec3 dir = -glm::vec3(view[0][2], view[1][2], view[2][2]);
cameraOrientation_ = glm::lookAt(cameraPosition_, cameraPosition_ + dir, up);
}
inline void lookAt(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up) {
cameraPosition_ = pos;
cameraOrientation_ = glm::lookAt(pos, target, up);
}
public:
struct Movement
{
bool forward_ = false;
bool backward_ = false;
bool left_ = false;
bool right_ = false;
bool up_ = false;
bool down_ = false;
//
bool fastSpeed_ = false;
} movement_;
public:
float mouseSpeed_ = 4.0f;
float acceleration_ = 150.0f;
float damping_ = 0.2f;
float maxSpeed_ = 10.0f;
float fastCoef_ = 10.0f;
private:
glm::vec2 mousePos_ = glm::vec2(0);
glm::vec3 cameraPosition_ = glm::vec3(0.0f, 10.0f, 10.0f);
glm::quat cameraOrientation_ = glm::quat(glm::vec3(0));
glm::vec3 moveSpeed_ = glm::vec3(0.0f);
glm::vec3 up_ = glm::vec3(0.0f, 0.0f, 1.0f);
};
先看属性:
- 定义了自定义结构类型 Movement,用来和具体按下的按键解耦;
- 定义了 mouseSpeed_ 等属性,用来控制摄像机的运动参数;
- 定义了 cameraPosition_ 等内部属性,用来计算 ViewMatrix; 再看方法:
构造函数
CameraPositioner_FirstPerson(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up) : cameraPosition_(pos) , cameraOrientation_(glm::lookAt(pos, target, up)) , up_(up) {}
使用 glm::lookAt 函数,更新 cameraOrientation_ 摄像机朝向;
update,更新 camera 位置
void update(double deltaSeconds, const glm::vec2& mousePos, bool mousePressed) { if (mousePressed) { const glm::vec2 delta = mousePos - mousePos_; const glm::quat deltaQuat = glm::quat(glm::vec3(-mouseSpeed_ * delta.y, mouseSpeed_ * delta.x, 0.0f)); cameraOrientation_ = deltaQuat * cameraOrientation_; cameraOrientation_ = glm::normalize(cameraOrientation_); setUpVector(up_); } mousePos_ = mousePos; const glm::mat4 v = glm::mat4_cast(cameraOrientation_); const glm::vec3 forward = -glm::vec3(v[0][2], v[1][2], v[2][2]); const glm::vec3 right = glm::vec3(v[0][0], v[1][0], v[2][0]); const glm::vec3 up = glm::cross(right, forward); glm::vec3 accel(0.0f); if (movement_.forward_) accel += forward; if (movement_.backward_) accel -= forward; if (movement_.left_) accel -= right; if (movement_.right_) accel += right; if (movement_.up_) accel += up; if (movement_.down_) accel -= up; if (movement_.fastSpeed_) accel *= fastCoef_; if (accel == glm::vec3(0)) { // decelerate naturally according to the damping value moveSpeed_ -= moveSpeed_ * std::min((1.0f / damping_) * static_cast<float>(deltaSeconds), 1.0f); } else { // acceleration moveSpeed_ += accel * acceleration_ * static_cast<float>(deltaSeconds); const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_; if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed; } cameraPosition_ += moveSpeed_ * static_cast<float>(deltaSeconds); }
update 方法每一帧都会被调用,传入帧与帧之间间隔的时间、当前鼠标的位置,以及鼠标是否被按下:
当鼠标被按下时,会根据当前传入的鼠标位置,与上一帧之间的插值,构造旋转用的四元数,这里有三点需要注意:
- 四元数的构造函数 glm::quat(angle.x, angle.y, angle.z),这里需要分别传入绕 x 轴旋转的角度、绕 y 轴旋转的角度和绕 z 轴旋转的角度,注意这里需要传入 radiance ,也就是弧度值,另外需要引用欧拉角的头文件
#include "glm/gtx/euler_angles.hpp"
; 四元数乘以一个三维向量,直接就是得到该三维向量旋转后的结果,这里的乘法运算被重载了;
//rotate vector vec3 qrot(vec4 q, vec3 v) { return v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); } //rotate vector (alternative) vec3 qrot_2(vec4 q, vec3 v) { return v*(q.w*q.w - dot(q.xyz,q.xyz)) + 2.0*q.xyz*dot(q.xyz,v) + 2.0*q.w*cross(q.xyz,v); }
四元数乘以一个四元数,得到的是两个四元数旋转合并后的四元数;
- 四元数的构造函数 glm::quat(angle.x, angle.y, angle.z),这里需要分别传入绕 x 轴旋转的角度、绕 y 轴旋转的角度和绕 z 轴旋转的角度,注意这里需要传入 radiance ,也就是弧度值,另外需要引用欧拉角的头文件
- 调用 setUpVector,保证摄像机不会出现上下翻转的情况;
- 更新鼠标位置;
- 通过 glm::mat4_cast 获取旋转矩阵,根据旋转矩阵,解析出 forword、up、right 方向;
- 下面一段是模拟加减速的过程,不再赘述;
综上,一个基本的 Camera 类就配置完成了,下面我们看一下应该怎么使用:
首先,声明 Camera 与具体实现类,并且保存当前的鼠标状态;
CameraPositioner_FirstPerson positioner( vec3(0.0f), vec3(0.0f, 0.0f, -1.0f), vec3(0.0f, 1.0f, 0.0f)); //positioner.setPosition(vec3(0.0f, 0.0f, 0.0f)); Camera camera(positioner); struct MouseState { glm::vec2 pos = glm::vec2(0.0f); bool pressedLeft = false; } mouseState;
设置监听窗口鼠标事件的回调函数,更新鼠标状态
glfwSetCursorPosCallback( window, [](auto* window, double x, double y) { int width, height; glfwGetFramebufferSize(window, &width, &height); mouseState.pos.x = static_cast<float>(x / width); mouseState.pos.y = static_cast<float>(y / height); } ); glfwSetMouseButtonCallback( window, [](auto* window, int button, int action, int mods) { if (button == GLFW_MOUSE_BUTTON_LEFT) mouseState.pressedLeft = action == GLFW_PRESS; } );
设置监听按键的回调函数,更新摄像机的前进方向
glfwSetKeyCallback( window, [](GLFWwindow* window, int key, int scancode, int action, int mods) { const bool pressed = action != GLFW_RELEASE; if (key == GLFW_KEY_ESCAPE && pressed) glfwSetWindowShouldClose(window, GLFW_TRUE); if (key == GLFW_KEY_W) positioner.movement_.forward_ = pressed; if (key == GLFW_KEY_S) positioner.movement_.backward_ = pressed; if (key == GLFW_KEY_A) positioner.movement_.left_ = pressed; if (key == GLFW_KEY_D) positioner.movement_.right_ = pressed; if (key == GLFW_KEY_1) positioner.movement_.up_ = pressed; if (key == GLFW_KEY_2) positioner.movement_.down_ = pressed; if (mods & GLFW_MOD_SHIFT) positioner.movement_.fastSpeed_ = pressed; if (key == GLFW_KEY_SPACE) positioner.setUpVector(vec3(0.0f, 1.0f, 0.0f)); } );
在渲染循环的开始,更新摄像机的状态
while (!glfwWindowShouldClose(window)) { positioner.update(deltaSeconds, mouseState.pos, mouseState.pressedLeft);
获取更新后的视图矩阵,并更新 Uniform 变量
const mat4 p = glm::perspective(45.0f, ratio, 0.1f, 1000.0f); const mat4 view = camera.getViewMatrix(); const PerFrameData perFrameData = { .view = view, .proj = p, .cameraPos = glm::vec4(camera.getPosition(), 1.0f) }; glNamedBufferSubData(perFrameDataBuffer, 0, kUniformBufferSize, &perFrameData);