渲染架构已准备好加载完整场景,但如果我们有一个固定的相机,那就没什么用处了。让我们设置一个带有鼠标外观的交互式飞行相机,以便我们可以探索我们加载的关卡。
相机更像是一个游戏玩法层对象。我们将把它添加到 VulkanEngine 中,但在真实的架构中,您可能不希望在引擎本身内进行输入事件和游戏逻辑,而是只存储一个包含渲染参数的相机结构,当您更新游戏逻辑时,您会刷新这些矩阵,以便它们可以用于渲染。
Camera 结构看起来像这样。我们将它添加到 camera.h 中
#include <vk_types.h>
#include <SDL_events.h>
class Camera {
public:
glm::vec3 velocity;
glm::vec3 position;
// vertical rotation
float pitch { 0.f };
// horizontal rotation
float yaw { 0.f };
glm::mat4 getViewMatrix();
glm::mat4 getRotationMatrix();
void processSDLEvent(SDL_Event& e);
void update();
};
我们实际上不会保留矩阵存储,而是在需要时计算它。这样我们保证矩阵始终与参数同步。
我们保留一个速度向量来跟踪 WASD 按键状态,并可能稍后添加加速度。Position 包含相机在世界中的位置。
我们不会使用完整的旋转矩阵,而是只运行俯仰 + 偏航。这模仿了虚幻引擎相机之类的工作方式,并使其更容易处理 FPS 逻辑。
在函数方面,我们有一个 Update() 调用,它将按速度修改位置,以及一个充当输入逻辑的 processSDLEvent。
让我们在 camera.cpp 上编写这些
#include <camera.h>
#include <glm/gtx/transform.hpp>
#include <glm/gtx/quaternion.hpp>
void Camera::update()
{
glm::mat4 cameraRotation = getRotationMatrix();
position += glm::vec3(cameraRotation * glm::vec4(velocity * 0.5f, 0.f));
}
void Camera::processSDLEvent(SDL_Event& e)
{
if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.sym == SDLK_w) { velocity.z = -1; }
if (e.key.keysym.sym == SDLK_s) { velocity.z = 1; }
if (e.key.keysym.sym == SDLK_a) { velocity.x = -1; }
if (e.key.keysym.sym == SDLK_d) { velocity.x = 1; }
}
if (e.type == SDL_KEYUP) {
if (e.key.keysym.sym == SDLK_w) { velocity.z = 0; }
if (e.key.keysym.sym == SDLK_s) { velocity.z = 0; }
if (e.key.keysym.sym == SDLK_a) { velocity.x = 0; }
if (e.key.keysym.sym == SDLK_d) { velocity.x = 0; }
}
if (e.type == SDL_MOUSEMOTION) {
yaw += (float)e.motion.xrel / 200.f;
pitch -= (float)e.motion.yrel / 200.f;
}
}
在计算位置更新时,我们正在使用旋转矩阵,这是因为速度将处于相机相对空间中,以便按下 W 向前移动。“向前”的含义取决于旋转。
在输入逻辑方面,SDL 为我们提供了输入事件,用于按键按下/释放和鼠标移动。我们将速度设置为按键按下事件的正确值,按键释放将其设置为 0。这并没有完全正确地处理输入,因为如果您同时按下 W 和 S,然后释放其中一个,移动就会停止,这会让人感觉有点尴尬。改进它是留给读者的练习。
对于鼠标移动,我们将鼠标的水平和垂直移动累积到俯仰和偏航属性中。
此代码中的移动是帧相关的,因为我们没有考虑引擎的速度。这样做是为了简单起见,如果您想改进它,您需要将 deltaTime(帧之间的时间)传递给 update() 函数,并将速度乘以它。在本教程中,由于我们在交换链中使用的选项,我们或多或少将 FPS 锁定到显示器速度,并且我们没有渲染足够的数据来减慢引擎速度。
矩阵函数看起来像这样
glm::mat4 Camera::getViewMatrix()
{
// to create a correct model view, we need to move the world in opposite
// direction to the camera
// so we will create the camera model matrix and invert
glm::mat4 cameraTranslation = glm::translate(glm::mat4(1.f), position);
glm::mat4 cameraRotation = getRotationMatrix();
return glm::inverse(cameraTranslation * cameraRotation);
}
glm::mat4 Camera::getRotationMatrix()
{
// fairly typical FPS style camera. we join the pitch and yaw rotations into
// the final rotation matrix
glm::quat pitchRotation = glm::angleAxis(pitch, glm::vec3 { 1.f, 0.f, 0.f });
glm::quat yawRotation = glm::angleAxis(yaw, glm::vec3 { 0.f, -1.f, 0.f });
return glm::toMat4(yawRotation) * glm::toMat4(pitchRotation);
}
对于旋转矩阵,我们正在计算 2 个四元数。一个将是水平旋转,另一个是垂直旋转。我们为此使用俯仰和偏航属性,并使用它来组合我们想要的旋转矩阵。
在视图矩阵上,我们通过组合旋转矩阵和平移矩阵来计算相机的最终变换矩阵。然后我们反转矩阵。这是因为要获得真实的相机矩阵,您所做的实际上不是移动相机,而是以与相机移动相反的方式移动世界,因为矩阵是在着色器中累积的。
这真的是全部内容,所以让我们将其挂钩到 VulkanEngine 类中
首先我们需要将相机添加为成员
#include <camera.h>
class VulkanEngine{
Camera mainCamera;
}
现在我们将其挂钩到渲染器中。首先进入 run()
函数,我们将在其中挂钩 SDL 事件。
// Handle events on queue
while (SDL_PollEvent(&e) != 0) {
// close the window when user alt-f4s or clicks the X button
if (e.type == SDL_QUIT)
bQuit = true;
mainCamera.processSDLEvent(e);
ImGui_ImplSDL2_ProcessEvent(&e);
}
接下来,在 update_scene() 函数中,我们添加相机逻辑以更新并将相机矩阵复制到渲染器 sceneData 结构中。
void VulkanEngine::update_scene()
{
mainCamera.update();
glm::mat4 view = mainCamera.getViewMatrix();
// camera projection
glm::mat4 projection = glm::perspective(glm::radians(70.f), (float)_windowExtent.width / (float)_windowExtent.height, 10000.f, 0.1f);
// invert the Y direction on projection matrix so that we are more similar
// to opengl and gltf axis
projection[1][1] *= -1;
sceneData.view = view;
sceneData.proj = projection;
sceneData.viewproj = projection * view;
}
从 init() 函数的末尾,我们将设置相机的初始变量。相机将朝向原点,因此 0,0,0 坐标处的网格应该是可见的。
mainCamera.velocity = glm::vec3(0.f);
mainCamera.position = glm::vec3(0, 0, 5);
mainCamera.pitch = 0;
mainCamera.yaw = 0;
就是这样,我们现在有了一个基本的交互式相机。尝试使用它并环顾四周,您将能够看到我们之前在世界中拥有的网格,使用 WASD 移动相机,并使用鼠标环顾四周。
下一步: GLTF 场景节点