A First-Person Camera

A First-Person Camera

现在开始扩展Camera基类,实现一个第一人称的camera,这种类型的camera可以通过鼠标和键盘进行控制。通过W和S键可以沿着direction向量分别向前和向后移动camera。按键A和D沿着right向量“扫射”(水平移动camera)。使用鼠标控制camera的俯仰和偏航移动:垂直移动鼠标执行camera的俯仰操作,水平移动鼠标执行camera的偏航操作。其中,俯仰是指绕着camera的right向量进行旋转,而偏航则绕着坐标轴的y轴旋转。这是第一人称camera的惯例方式,但是这种camera并不能用于所有的情况,比如,某个游戏中需要camera能在空间中沿着z轴滚动(纵向旋转)。
列表13.3列出了FirstPersonCamera类的头文件。

列表13.3 The FirstPersonCamera.h Header File

#pragma once

#include "Camera.h"

namespace Library
{
	class Keyboard;
	class Mouse;

	class FirstPersonCamera : public Camera
	{
		RTTI_DECLARATIONS(FirstPersonCamera, Camera)

	public:
		FirstPersonCamera(Game& game);
		FirstPersonCamera(Game& game, float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance);

		virtual ~FirstPersonCamera();

		const Keyboard& GetKeyboard() const;
		void SetKeyboard(Keyboard& keyboard);

		const Mouse& GetMouse() const;
		void SetMouse(Mouse& mouse);

		float& MouseSensitivity();
		float& RotationRate();
		float& MovementRate();		
		
		virtual void Initialize() override;
		virtual void Update(const GameTime& gameTime) override;

		static const float DefaultMouseSensitivity;
		static const float DefaultRotationRate;
		static const float DefaultMovementRate;        

	protected:
		float mMouseSensitivity;
		float mRotationRate;
		float mMovementRate;        

		Keyboard* mKeyboard;
		Mouse* mMouse;

	private:
		FirstPersonCamera(const FirstPersonCamera& rhs);
		FirstPersonCamera& operator=(const FirstPersonCamera& rhs);
	};
}


FirstPersonCamera类中包含了表示鼠标和键盘的成员变量,并提供了访问和修改这些变量的公有成员函数。但是在FirstPersonCamera的Initialize()函数中,试图通过查询service container获取鼠标和键盘。用于修改鼠标和键盘变量的成员函数只是简单地重写这些设置,甚至通过指定NULL值禁用鼠标和键盘控制。
还包含了用于表示camera移动和旋转的速率,以及对鼠标控制的响应敏感度。变量“rate”表示在1秒内camera移动或旋转的单位长度。鼠标敏感度支持增强或抑制对鼠标输入的响应。
列表13.4列出FirstPersonCamera实现中最值得关注的部分代码。本书的配套网站上提供了完成的实现代码。

列表13.4 The FirstPersonCamera Class Implementation (Abbreviated)

#include "FirstPersonCamera.h"
#include "Game.h"
#include "GameTime.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "VectorHelper.h"

namespace Library
{
	RTTI_DEFINITIONS(FirstPersonCamera)

	const float FirstPersonCamera::DefaultRotationRate = XMConvertToRadians(1.0f);
	const float FirstPersonCamera::DefaultMovementRate = 10.0f;
	const float FirstPersonCamera::DefaultMouseSensitivity = 100.0f;

	FirstPersonCamera::FirstPersonCamera(Game& game)
		: Camera(game), mKeyboard(nullptr), mMouse(nullptr), 
		  mMouseSensitivity(DefaultMouseSensitivity), mRotationRate(DefaultRotationRate), mMovementRate(DefaultMovementRate)
	{
	}

	FirstPersonCamera::FirstPersonCamera(Game& game, float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance)
		: Camera(game, fieldOfView, aspectRatio, nearPlaneDistance, farPlaneDistance), mKeyboard(nullptr), mMouse(nullptr),
		  mMouseSensitivity(DefaultMouseSensitivity), mRotationRate(DefaultRotationRate), mMovementRate(DefaultMovementRate)
		  
	{
	}

	FirstPersonCamera::~FirstPersonCamera()
	{
		mKeyboard = nullptr;
		mMouse = nullptr;
	}

	void FirstPersonCamera::Initialize()
	{
		mKeyboard = (Keyboard*)mGame->Services().GetService(Keyboard::TypeIdClass());
		mMouse = (Mouse*)mGame->Services().GetService(Mouse::TypeIdClass());

		Camera::Initialize();
	}

	void FirstPersonCamera::Update(const GameTime& gameTime)
	{
		XMFLOAT2 movementAmount = Vector2Helper::Zero;
		if (mKeyboard != nullptr)
		{
			if (mKeyboard->IsKeyDown(DIK_W))
			{
				movementAmount.y = 1.0f;
			}

			if (mKeyboard->IsKeyDown(DIK_S))
			{
				movementAmount.y = -1.0f;
			}

			if (mKeyboard->IsKeyDown(DIK_A))
			{
				movementAmount.x = -1.0f;
			}

			if (mKeyboard->IsKeyDown(DIK_D))
			{
				movementAmount.x = 1.0f;
			}
		}

		XMFLOAT2 rotationAmount = Vector2Helper::Zero;
		if ((mMouse != nullptr) && (mMouse->IsButtonHeldDown(MouseButtonsLeft)))
		{
			LPDIMOUSESTATE mouseState = mMouse->CurrentState();			
			rotationAmount.x = -mouseState->lX * mMouseSensitivity;
			rotationAmount.y = -mouseState->lY * mMouseSensitivity;
		}

		float elapsedTime = (float)gameTime.ElapsedGameTime();
		XMVECTOR rotationVector = XMLoadFloat2(&rotationAmount) * mRotationRate * elapsedTime;
		XMVECTOR right = XMLoadFloat3(&mRight);

		XMMATRIX pitchMatrix = XMMatrixRotationAxis(right, XMVectorGetY(rotationVector));
		XMMATRIX yawMatrix = XMMatrixRotationY(XMVectorGetX(rotationVector));

		ApplyRotation(XMMatrixMultiply(pitchMatrix, yawMatrix));

		XMVECTOR position = XMLoadFloat3(&mPosition);
		XMVECTOR movement = XMLoadFloat2(&movementAmount) * mMovementRate * elapsedTime;

		XMVECTOR strafe = right * XMVectorGetX(movement);
		position += strafe;

		XMVECTOR forward = XMLoadFloat3(&mDirection) * XMVectorGetY(movement);
		position += forward;
		
		XMStoreFloat3(&mPosition, position);

		Camera::Update(gameTime);
	}
}


在列表13.4中,首先设置了rotation rate,movement rate,以及mouse sensitivity的默认值。其中使用了DiretXMath库的XMConvertToRadians()函数,用于把degress转换成radians。而XMConvertToDegrees()函数则radians转换成degrees。
在Initialize()函数中演示了从service container中获取mouse和keyboard services的方法。需要注意的是这两个services很可能不存在,在这种情况下返回值为NULL,意味着在该component中不需要键盘的鼠标的功能。否则你应该assert在service container中一定存在这些对象。
Update()函数中包含了FirstPersonCamera类实现代码中的主要部分。首先检查W,S,A和D按键是否按下;如果按下了,就保存值1或-1,用于表示移动的方向。键盘是是一种数字形式的,按键要么按下要么没有按下,因此无法表示一个变量,比如游戏手柄上的模拟触发器或者摇杆。因此,成员变量mMovementRate用于定义camera运动的幅度(尽管理论上可以根据按键被按下的时间缩放幅度值)。
接下来,从鼠标移动(如果鼠标左键被按住不放)过程中获取旋转量。这些数量由成员变量mRotationRate与每一帧的elapsed time相乘得到。在执行乘法运算之前,需要把rotationAmount变量加载到一个XMVECTOR对象中,以便利用SIMD运算的优势。使用elapsed time变量是为了与计算帧率相互独立。
有了旋转数量值之后,就可以构建用于camera的偏航和俯仰旋转矩阵。俯仰矩阵通过调用XMMatrixRotationAxis()函数进行创建,该函数可以构建一个绕任意轴旋转的矩阵。对于俯仰矩阵,是绕着camera的right向量旋转。XMMatrixRotationAxis()函数中使用了XMVectorGetY()函数的返回值作为第二个参数。调用XMVectorGetY()函数可以从一个无法访问内部成员的XMVECTOR对象中获取Y分量值。偏航矩阵则通过调用XMMatrixRotationY()函数进行创建,该函数创建了一个绕y轴旋转的矩阵。在DirectXMath库中还有类似的矩阵用于创建围绕x轴和z轴旋转的矩阵。偏航和俯仰矩阵在传送给Camera::ApplyRotation()函数之前,需要先通过乘法运算进行串联。
接下来,使用成员变量mMovementRate和elapsed time对movementAmount进行乘法运算,并把结果存储到一个XMVECTOR类型的movement向量中。然后把camera的right向量与movement向量的X分量进行乘法运算,得到strafe向量。再把该向量值加到camera的position中。对于forward movement,执行同样的操作过程,把最终的position再写回到成员变量Camera::mPosition中。

总结

本章首先创建了一个基类component用于支持一个虚拟camere的通用部分。然后扩展基类创建了一个支持使用鼠标和键盘控制的第一人称camera。在这个过程中,练习了上一章所讨论的component和services模块,并温习了一些3D camera原理和DirectXMath的使用方法。
到目前为止,所有的工作都已经准备就绪。在下一章,就可以正式开始在屏幕上渲染3D场景了。

Exercises

1. Create an orbit camera similar to the one found in NVIDIA FX Composer. Note: Visualizing
the output of your camera will be difficult without any 3D rendering (which the next chapter
covers). You can use the SpriteBatch/SpriteFont system to output your camera’s data or
employ the Grid component on the companion website. This component renders a
customizable reference grid at the world origin.


1.创建一个与NVIDIA FX Composer中类似的orbit camera。注意:在没有任何3D渲染(下一章才会讲到)的情况要显示camera的输出是很困难的。可以使用SpriteBatch/SpriteFont系统输出camera的相关数据,或者使用本书配套网站上提供的Grid component。这个component可以在渲染一个自定义的参考网格。


本章配套学习代码

http://download.csdn.net/detail/chenjinxian_3d/9584778

首先在FirstPersonCamera的基础上,增加了一个OrbitCamera,并添加了一个Grid组件用于演示各种Camera的操作结果。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值