FPS 式视角合成(yaw 世界轴/pitch 本地轴,永不 roll); RMB 自由视角后环绕 pivot 失效待重建(修 Focus 后 MMB/滚轮 瞬移锁回);SceneRenderer 默认 kDisplayDirect(game 形态 Blit 空转+清屏黑屏根因);swapchain 优先 Opaque 合成; ImGui 加载思源黑体(CJK 常用集);Q=纯选择不出杆; 新增 InputMap 动作映射
215 lines
10 KiB
C++
215 lines
10 KiB
C++
//
|
||
// 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
|