Files
HelloVulkan/Engine/src/Renderer/Camera.cpp
T
atdunbg 26e222a9cf 修复编辑器相机与 game 形态黑屏;中文字体与 Q 选择热键
FPS 式视角合成(yaw 世界轴/pitch 本地轴,永不 roll);
RMB 自由视角后环绕 pivot 失效待重建(修 Focus 后 MMB/滚轮
瞬移锁回);SceneRenderer 默认 kDisplayDirect(game 形态
Blit 空转+清屏黑屏根因);swapchain 优先 Opaque 合成;
ImGui 加载思源黑体(CJK 常用集);Q=纯选择不出杆;
新增 InputMap 动作映射
2026-10-08 17:02:39 +08:00

215 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/22.
//
#include <Engine/Renderer/Camera.h>
#include <Engine/Core/Window.h>
#include <Engine/ECS/Components.h>
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
#include "Engine/Core/Input.h" // 键查询走统一输入门面(不依赖 GLFW)
namespace Engine {
void Camera::Update(ECS::TransformComponent& transform,
const f32 deltaTime, const bool flyRequiresRMB)
{
// ---- 输入模式(UE Scene view 语义):RMB=自由视角(鼠标转向
// + 飞行),MMB=焦点环绕(Shift+MMB=平移),滚轮=推拉。三种
// 光标型交互互斥共用捕获;编辑器模式 flyRequiresRMB=true 时
// 移动键仅 RMB 按住生效(否则与 Gizmo W/E/R 热键冲突)----
const bool wantLook = m_MouseLookEnabled &&
Input::IsMouseButtonDown(Input::MouseButton::Right);
const bool wantOrbit = m_MouseLookEnabled &&
Input::IsMouseButtonDown(Input::MouseButton::Middle);
if (wantLook || wantOrbit)
{
if (!Input::IsCursorCaptured())
{
Input::SetCursorCaptured(true);
}
f32 mouseDX = 0, mouseDY = 0;
Input::GetMouseDelta(mouseDX, mouseDY);
if (mouseDX != 0 || mouseDY != 0)
{
if (wantLook)
{
// ---- FPS 式视角(UE/Unity 编辑器视口语义):
// yaw 绕世界 up 左乘、pitch 绕本地 X 右乘——
// 右轴恒保持水平,水平线永不 roll。此前版本
// yaw 也绕本地轴(6DOF 自由视角),俯视后再
// 平移鼠标会带 roll,up 向量歪斜。pitch 用
// 前向量的仰角钳 ±89°:过顶会倒置视图,编辑
// 器视口不要这种行为。符号约定不变:鼠标右移
// 向右看、鼠标下移低头 ----
const glm::quat dyaw = glm::angleAxis(
glm::radians(-mouseDX * m_MouseSensitivity),
glm::vec3(0, 1, 0));
const glm::vec3 fwd =
transform.GetRotationQuat() * glm::vec3(0, 0, -1);
const float pitch =
std::asin(glm::clamp(fwd.y, -1.0f, 1.0f));
float dPitch = glm::radians(-mouseDY * m_MouseSensitivity);
dPitch = glm::clamp(pitch + dPitch,
glm::radians(-89.0f),
glm::radians(89.0f)) - pitch;
const glm::quat dpitch =
glm::angleAxis(dPitch, glm::vec3(1, 0, 0));
transform.SetRotationQuat(
dyaw * transform.GetRotationQuat() * dpitch);
// 自由视角改写朝向后,旧环绕 pivot 已不在视线上——
// 若保留,下一次 MMB/滚轮会绕老焦点猛拽位置(Focus
// 后飞远再 MMB 的"瞬移锁回":角增量 × 大半径 =
// 米级甩动)。采 Blender/UE 语义:pivot 跟随当前
// 视线,失效待 MMB/滚轮按新视线自动重建
m_HasPivot = false;
}
else if (Input::IsKeyDown(Input::Key::LeftShift))
{
// ---- 屏幕平面平移:内容跟手(拖右 → 相机左移、
// 拖下 → 相机上移),pivot 同步平移保持环绕点的
// 屏幕相对位置 ----
const glm::mat3 r(transform.GetRotationQuat());
const glm::vec3 delta =
(-r * glm::vec3(1, 0, 0)) * mouseDX +
(r * glm::vec3(0, 1, 0)) * mouseDY;
const f32 k = m_OrbitDistance * 0.002f; // m/px
transform.SetPosition(transform.GetPosition() + delta * k);
if (m_HasPivot) m_OrbitPivot += delta * k;
}
else
{
// ---- 焦点环绕:世界系左乘增量(yaw 绕世界 Y、pitch
// 绕相机右轴),roll 保持不变;位置绕 pivot 同
// 角度刚体旋转 → 到 pivot 距离恒定。首次环绕无
// pivot:取当前视线上 m_OrbitDistance 处 ----
if (!m_HasPivot)
{
const glm::mat3 r(transform.GetRotationQuat());
m_OrbitPivot = transform.GetPosition() +
(r * glm::vec3(0, 0, -1)) *
m_OrbitDistance;
m_HasPivot = true;
}
const glm::vec3 offset =
transform.GetPosition() - m_OrbitPivot;
m_OrbitDistance = glm::length(offset);
const glm::vec3 camRight =
glm::mat3(transform.GetRotationQuat()) *
glm::vec3(1, 0, 0);
const glm::quat dq =
glm::angleAxis(
glm::radians(-mouseDX * m_MouseSensitivity),
glm::vec3(0, 1, 0)) *
glm::angleAxis(
glm::radians(-mouseDY * m_MouseSensitivity),
camRight);
// 朝向与位置共用同一 dq:视线始终精确穿过 pivot
transform.SetRotationQuat(dq * transform.GetRotationQuat());
transform.SetPosition(m_OrbitPivot + dq * offset);
}
}
}
else if (Input::IsCursorCaptured())
{
Input::SetCursorCaptured(false);
}
// 基向量由(可能已更新的)实体旋转决定(本地 -Z 为视线方向)
const glm::mat3 r(transform.GetRotationQuat());
const glm::vec3 forward = r * glm::vec3(0, 0, -1);
const glm::vec3 right = r * glm::vec3(1, 0, 0);
// ---- 飞行移动:编辑器模式仅 RMB(wantLook)时生效;游戏模式
// 常开(原行为)----
if (wantLook || !flyRequiresRMB)
{
const f32 speed = m_MoveSpeed * deltaTime;
bool flew = false;
if (Input::IsKeyDown(Input::Key::W)) { transform.SetPosition(transform.GetPosition() + forward * speed); flew = true; }
if (Input::IsKeyDown(Input::Key::S)) { transform.SetPosition(transform.GetPosition() - forward * speed); flew = true; }
if (Input::IsKeyDown(Input::Key::A)) { transform.SetPosition(transform.GetPosition() - right * speed); flew = true; }
if (Input::IsKeyDown(Input::Key::D)) { transform.SetPosition(transform.GetPosition() + right * speed); flew = true; }
if (Input::IsKeyDown(Input::Key::Space)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); flew = true; }
if (Input::IsKeyDown(Input::Key::LeftShift)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); flew = true; }
// 飞行位移同样令环绕 pivot 失效(同自由视角:位移后老
// pivot 与视线脱钩,保留会让 MMB/滚轮绕老焦点拽位置)
if (flew) { m_HasPivot = false; }
}
// ---- 滚轮推拉:有 pivot 时改环绕距离(指数步进,近处精细/
// 远处大步);无 pivot 时沿视线平移 ----
const f32 wheel = Input::ConsumeScrollDelta();
if (m_MouseLookEnabled && wheel != 0.0f)
{
if (m_HasPivot)
{
m_OrbitDistance = glm::clamp(
m_OrbitDistance * std::pow(0.9f, wheel), 0.5f, 500.0f);
transform.SetPosition(m_OrbitPivot - forward * m_OrbitDistance);
}
else
{
transform.SetPosition(transform.GetPosition() +
forward * wheel * m_MoveSpeed * deltaTime);
}
}
}
void Camera::FocusOn(ECS::TransformComponent& transform, const glm::vec3& point)
{
const glm::vec3 fwd =
glm::mat3(transform.GetRotationQuat()) * glm::vec3(0, 0, -1);
m_OrbitPivot = point;
m_HasPivot = true;
transform.SetPosition(point - fwd * m_OrbitDistance);
}
glm::mat4 Camera::GetView(const ECS::TransformComponent& t) const
{
// 相机作为实体:视图 = 世界矩阵的逆(无重复位姿状态)
return glm::inverse(t.World());
}
glm::mat4 Camera::GetProjection(const f32 aspect) const
{
glm::mat4 proj;
if (m_ProjectionMode == ProjectionMode::Perspective)
{
proj = glm::perspective(glm::radians(m_FovDeg), aspect,
m_NearZ, m_FarZ);
proj[1][1] *= -1.0f; // Vulkan Y 翻转
// Vulkan clip 深度 [0,1]:z' = 0.5*z_clip + 0.5*w_clip。
// perspective 的 w_clip = -z_eye,偏移折算进 z 系数
proj[2][2] = proj[2][2] * 0.5f - 0.5f;
proj[3][2] *= 0.5f;
}
else
{
// 正交(Unity orthographic size 语义 = 半垂直高度):全宽由
// 视口 aspect 推导,长宽比跟随视口永不变形
const f32 halfH = m_OrthoSize;
const f32 halfW = halfH * aspect;
proj = glm::ortho(-halfW, halfW, -halfH, halfH, m_NearZ, m_FarZ);
proj[1][1] *= -1.0f; // Vulkan Y 翻转(与透视同款)
// ortho 的 w_clip = 1:偏移直接加进平移分量
//(与 ComputeCascades 光源正交投影同一公式)
proj[2][2] *= 0.5f;
proj[3][2] = proj[3][2] * 0.5f + 0.5f;
}
return proj;
}
} // namespace Engine