构建第一人称视角的摄像机与动态索引
在3D游戏和应用程序中,摄像机系统是连接用户与虚拟世界的重要桥梁。第一人称视角(First-Person View)提供了一种身临其境的体验,使用户能够通过主角的视角直接观察和交互虚拟环境。与此同时,动态索引技术使我们能够更高效地管理和渲染大量相似但不完全相同的对象。本章将深入讲解如何在DirectX 12中实现一个功能完善的第一人称摄像机系统,以及如何利用动态索引提升渲染性能。
15.1 视图变换
在3D图形渲染中,视图变换(View Transformation)是将世界空间中的场景转换到摄像机空间的过程。它是构建第一人称视角的基础。
15.1.1 坐标系统回顾
在深入探讨视图变换之前,让我们先回顾一下DirectX中使用的坐标系统:
- 左手坐标系:DirectX默认使用左手坐标系
- X轴:向右为正
- Y轴:向上为正
- Z轴:向前为正
对于视图变换,我们需要创建一个摄像机坐标系,它由三个相互垂直的单位向量定义:
- Right向量(X轴):摄像机的右方向
- Up向量(Y轴):摄像机的上方向
- Look向量(Z轴):摄像机的前进方向
15.1.2 视图矩阵
视图矩阵是将世界坐标系中的点转换到摄像机坐标系的变换矩阵。它本质上是一个刚体变换的逆矩阵,包含旋转和平移两部分:
cpp
// 构建视图矩阵
XMMATRIX BuildViewMatrix(
XMVECTOR eyePosition, // 摄像机位置
XMVECTOR eyeDirection, // 摄像机方向
XMVECTOR upDirection) // 上方向
{
// 规范化方向向量
XMVECTOR L = XMVector3Normalize(eyeDirection);
// 确保上方向和摄像机方向不共线
XMVECTOR U = XMVector3Normalize(upDirection);
// 计算右方向
XMVECTOR R = XMVector3Cross(U, L);
R = XMVector3Normalize(R);
// 重新计算上方向,确保三个轴互相垂直
U = XMVector3Cross(L, R);
// 构建视图矩阵的旋转部分
float x = -XMVectorGetX(XMVector3Dot(R, eyePosition));
float y = -XMVectorGetX(XMVector3Dot(U, eyePosition));
float z = -XMVectorGetX(XMVector3Dot(L, eyePosition));
XMMATRIX view = XMMATRIX(
XMVectorGetX(R), XMVectorGetX(U), XMVectorGetX(L), 0.0f,
XMVectorGetY(R), XMVectorGetY(U), XMVectorGetY(L), 0.0f,
XMVectorGetZ(R), XMVectorGetZ(U), XMVectorGetZ(L), 0.0f,
x, y, z, 1.0f
);
return view;
}
实际上,DirectX提供了封装好的函数来创建视图矩阵:
cpp
// 使用DirectX内置函数创建视图矩阵
XMMATRIX XM_CALLCONV XMMatrixLookToLH(
FXMVECTOR EyePosition, // 摄像机位置
FXMVECTOR EyeDirection, // 观察方向
FXMVECTOR UpDirection); // 上方向
// 或者使用目标点而非方向
XMMATRIX XM_CALLCONV XMMatrixLookAtLH(
FXMVECTOR EyePosition, // 摄像机位置
FXMVECTOR FocusPosition, // 焦点位置
FXMVECTOR UpDirection); // 上方向
15.2 摄像机类
为了方便管理和使用摄像机,我们可以创建一个专门的摄像机类。这个类将封装摄像机的位置、朝向和各种操作方法。
15.2.1 基本摄像机类设计
以下是一个基本的第一人称摄像机类设计:
cpp
class FirstPersonCamera
{
public:
FirstPersonCamera();
~FirstPersonCamera();
// 获取/设置位置
XMVECTOR GetPosition() const;
void SetPosition(float x, float y, float z);
void SetPosition(const XMFLOAT3& position);
// 获取方向向量
XMVECTOR GetRight() const;
XMVECTOR GetUp() const;
XMVECTOR GetLook() const;
// 获取欧拉角
float GetPitch() const;
float GetYaw() const;
float GetRoll() const;
// 设置欧拉角
void SetPitch(float pitch);
void SetYaw(float yaw);
void SetRoll(float roll);
// 获取截锥体属性
float GetNearZ() const;
float GetFarZ() const;
float GetAspect() const;
float GetFovY() const;
// 设置截锥体属性
void SetNearZ(float nearZ);
void SetFarZ(float farZ);
void SetAspect(float aspect);
void SetFovY(float fovY);
// 获取矩阵
XMMATRIX GetView() const;
XMMATRIX GetProj() const;
XMMATRIX GetViewProj() const;
// 移动相关方法
void Walk(float d); // 前后移动
void Strafe(float d); // 左右移动
void Climb(float d); // 上下移动
// 旋转相关方法
void Pitch(float angle); // 上下旋转
void Yaw(float angle); // 左右旋转
void Roll(float angle); // 倾斜旋转
// 更新矩阵
void UpdateViewMatrix();
private:
// 摄像机位置
XMFLOAT3 mPosition;
// 摄像机方向向量
XMFLOAT3 mRight;
XMFLOAT3 mUp;
XMFLOAT3 mLook;
// 欧拉角
float mPitch;
float mYaw;
float mRoll;
// 是否需要更新视图矩阵
bool mViewDirty;
// 视图矩阵
XMFLOAT4X4 mView;
// 截锥体属性
float mNearZ;
float mFarZ;
float mAspect;
float mFovY;
// 投影矩阵
XMFLOAT4X4 mProj;
};
15.2.2 实现移动和旋转
以下是第一人称摄像机移动和旋转方法的实现:
cpp
// 沿观察方向移动
void FirstPersonCamera::Walk(float d)
{
// 更新摄像机位置
XMVECTOR position = XMLoadFloat3(&mPosition);
XMVECTOR look = XMLoadFloat3(&mLook);
position += d * look;
XMStoreFloat3(&mPosition, position);
// 标记视图矩阵需要更新
mViewDirty = true;
}
// 沿右方向移动
void FirstPersonCamera::Strafe(float d)
{
// 更新摄像机位置