圖形引擎(四):創建自定義的Camera

圖形引擎(四):創建自定義的Camera

上一篇文章中,已經討論瞭如何在應用程序中添加組件,並完成了對輸入設備如鍵盤和鼠標的響應操作,本篇文章中將講解如何使用鼠標和鍵盤控制場景。任何一個3D場景中都需要使用一個虛擬Camera用於顯示,根據Camera的座標位置和觀察方向的不同,可以觀察場景的不同角度。爲此,我們將會創建一個基礎的Camera類,包含了Camera的基礎功能屬性,然後在此基礎上完成一個第一人稱的First-Person Camera類。作爲練習,在創建一個類似於NVIDIA FX Composer中的Orbit Camera。
關於Camera基類,以及FirstPersonCamera類的詳細講解,請查看另一篇博客。在這裏,重點講解如何創建一個Orbit Camera。

Orbit Camera

Orbit Camera是指Camera圍繞某個座標軸,沿着對應的軌道運行,並保持觀察方向指向世界座標空間的原點。具體地說,就是在世界座標空間中,以原點爲中心,Camera與中心的距離作爲半徑,然後圍繞與camera的Right或Up方向平行的軸進行旋轉,旋轉所經過的路徑就是運行的軌道。
在第一人稱Camera中,已經計算了俯仰和偏航,詳見http://blog.csdn.net/chenjinxian_3d/article/details/51946202。俯仰是指Camera圍繞Right方向軸旋轉一定的角度,而偏航是圍繞Up方向軸旋轉一定的角度。在些基礎上,可以把俯仰的角度作爲Orbit Camera圍繞世界座標空間與Camera的Right方向平行的軸的旋轉角度,把偏航的角度作爲Orbit Camera圍繞世界座標空間與Camera的Up方向平行的軸的旋轉角度。因此Orbit Camera圍繞軌道運行一定角度後的座標位置就可以通過把Camera的Postion與旋轉矩陣相乘得到。由於Camera的座標位置改變後,Camera的觀察方向需要保持指向世界座標空間的原點,因此在第一人稱Camera中執行的旋轉操作在Orbit Camera仍然需要。
Orbit Camera的代碼如下
#pragma once

#include "Camera.h"

namespace Library
{
	class Keyboard;
	class Mouse;

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

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

		virtual ~OrbitCamera();

		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:
		OrbitCamera(const OrbitCamera& rhs);
		OrbitCamera& operator=(const OrbitCamera& rhs);
	};
}


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

namespace Library
{
	RTTI_DEFINITIONS(OrbitCamera)

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

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

	OrbitCamera::OrbitCamera(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)
		  
	{
	}

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

	const Keyboard& OrbitCamera::GetKeyboard() const
	{
		return *mKeyboard;
	}

	void OrbitCamera::SetKeyboard(Keyboard& keyboard)
	{
		mKeyboard = &keyboard;
	}

	const Mouse& OrbitCamera::GetMouse() const
	{
		return *mMouse;
	}

	void OrbitCamera::SetMouse(Mouse& mouse)
	{
		mMouse = &mouse;
	}

	float&OrbitCamera:: MouseSensitivity()
	{
		return mMouseSensitivity;
	}


	float& OrbitCamera::RotationRate()
	{
		return mRotationRate;
	}

	float& OrbitCamera::MovementRate()
	{
		return mMovementRate;
	}

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

		Camera::Initialize();
	}

	void OrbitCamera::Update(const GameTime& gameTime)
	{
		XMFLOAT2 movementAmount = Vector2Helper::Zero;
		XMFLOAT2 rotationAmount = Vector2Helper::Zero;
		if (mMouse != nullptr && mKeyboard != nullptr)
		{
			LPDIMOUSESTATE mouseState = mMouse->CurrentState();
			if (mMouse->IsButtonHeldDown(MouseButtonsLeft) && mKeyboard->IsKeyHeldDown(DIK_LMENU))
			{
				rotationAmount.x = -mouseState->lX * mMouseSensitivity;
				rotationAmount.y = -mouseState->lY * mMouseSensitivity;
			}
			else
			{
				movementAmount.y = static_cast<float>(mouseState->lZ);
			}
		}

		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));

		XMMATRIX transform = XMMatrixMultiply(pitchMatrix, yawMatrix);
		ApplyRotation(transform);

		XMVECTOR position = XMLoadFloat3(&mPosition);
		position = XMVector3TransformNormal(position, transform);

		XMVECTOR movement = XMLoadFloat2(&movementAmount) * mMovementRate * elapsedTime;
		XMVECTOR forward = XMLoadFloat3(&mDirection) * XMVectorGetY(movement);
		position += forward;

		XMStoreFloat3(&mPosition, position);

		Camera::Update(gameTime);
	}
}


其中,除了Update函數其他的與FirstPersonCamera基本一樣,在Update函數中只是對Camera的Position進行了旋轉變換。

Camera示例

創建了Orbit Camera之後,就可以在RenderingGame中使用了。在下面的示例程序中,爲了更好的演示兩種Camera,我們在代碼中同時添加兩種Camera,並通過鍵盤控制使用哪一種Camera。更新後的RenderingGame類的聲明和實現代碼如下:
#pragma once

#include "Common.h"
#include "Game.h"

using namespace Library;

namespace DirectX
{
	class SpriteBatch;
	class SpriteFont;
}

namespace Library
{
	class FpsComponent;
	class Keyboard;
	class Mouse;
	class FirstPersonCamera;
	class OrbitCamera;
	class Grid;
}

namespace Rendering
{
	class RenderingGame : public Game
	{
	public:
		RenderingGame(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand);
		~RenderingGame();

		virtual void Initialize() override;		
		virtual void Update(const GameTime& gameTime) override;
		virtual void Draw(const GameTime& gameTime) override;

	protected:
		virtual void Shutdown() override;

	private:
		static const XMVECTORF32 BackgroundColor;

		FpsComponent* mFpsComponent;
		FirstPersonCamera * mFirstPersonCamera;
		OrbitCamera * mOrbitCamera;
		Grid* mGrid;
		LPDIRECTINPUT8 mDirectInput;
		Keyboard* mKeyboard;
		Mouse* mMouse;

		SpriteBatch* mSpriteBatch;
		SpriteFont* mSpriteFont;
		XMFLOAT2 mMouseTextPosition;
		XMFLOAT2 mCameraTextPosition;
		XMFLOAT2 mTipTextPosition;
	};
}


#include "RenderingGame.h"
#include <SpriteBatch.h>
#include <SpriteFont.h>
#include "GameException.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "FpsComponent.h"
#include "FirstPersonCamera.h"
#include "OrbitCamera.h"
#include "Grid.h"
#include "Utility.h"
#include <sstream>
#include <string>
#include <iomanip>

namespace Rendering
{
	const XMVECTORF32 RenderingGame::BackgroundColor = { 0.392f, 0.584f, 0.929f, 1.0f };

	RenderingGame::RenderingGame(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand)
		: Game(instance, windowClass, windowTitle, showCommand),
		mFpsComponent(nullptr), mFirstPersonCamera(nullptr), mOrbitCamera(nullptr), mGrid(nullptr),
		mDirectInput(nullptr), mKeyboard(nullptr), mMouse(nullptr),
		mSpriteBatch(nullptr), mSpriteFont(nullptr),
		mMouseTextPosition(0.0f, 20.0f), mCameraTextPosition(0.0f, mScreenHeight - 40.0f), mTipTextPosition(0.0f, mScreenHeight - 80.0f)
	{
		mDepthStencilBufferEnabled = true;
		mMultiSamplingEnabled = true;
// 		mIsFullScreen = true;
	}

	RenderingGame::~RenderingGame()
	{
	}

	void RenderingGame::Initialize()
	{
		if (FAILED(DirectInput8Create(mInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&mDirectInput, nullptr)))
		{
			throw GameException("DirectInput8Create() failed");
		}

		mKeyboard = new Keyboard(*this, mDirectInput);
		mComponents.push_back(mKeyboard);
		mServices.AddService(Keyboard::TypeIdClass(), mKeyboard);

		mMouse = new Mouse(*this, mDirectInput);
		mComponents.push_back(mMouse);
		mServices.AddService(Mouse::TypeIdClass(), mMouse);

		mFpsComponent = new FpsComponent(*this);
		mComponents.push_back(mFpsComponent);

		mFirstPersonCamera = new FirstPersonCamera(*this);
		mComponents.push_back(mFirstPersonCamera);
		mServices.AddService(Camera::TypeIdClass(), mFirstPersonCamera);

		mOrbitCamera = new OrbitCamera(*this);
		mComponents.push_back(mOrbitCamera);
		mServices.AddService(Camera::TypeIdClass(), mOrbitCamera);

		mGrid = new Grid(*this, *mFirstPersonCamera);
		mComponents.push_back(mGrid);

		SetCurrentDirectory(Utility::ExecutableDirectory().c_str());

		mSpriteBatch = new SpriteBatch(mDirect3DDeviceContext);
		mSpriteFont = new SpriteFont(mDirect3DDevice, L"Assets\\Fonts\\Arial_14_Regular.spritefont");

		Game::Initialize();

		if (mIsFullScreen)
		{
			mCameraTextPosition = XMFLOAT2(0.0f, mScreenHeight - 40.0f);
			mTipTextPosition = XMFLOAT2(0.0f, mScreenHeight - 80.0f);
		}
	}

	void RenderingGame::Shutdown()
	{
		DeleteObject(mKeyboard);
		DeleteObject(mMouse);
		DeleteObject(mFpsComponent);
		DeleteObject(mFirstPersonCamera);
		DeleteObject(mOrbitCamera);
		DeleteObject(mGrid);
		DeleteObject(mSpriteFont);
		DeleteObject(mSpriteBatch);

		ReleaseObject(mDirectInput);

		Game::Shutdown();
	}

	void RenderingGame::Update(const GameTime &gameTime)
	{
		if (mKeyboard->WasKeyPressedThisFrame(DIK_ESCAPE))
		{
			Exit();
		}

		if (mKeyboard->WasKeyPressedThisFrame(DIK_F))
		{
			mOrbitCamera->SetEnabled(false);
			mFirstPersonCamera->SetEnabled(true);
			mGrid->SetCamera(mFirstPersonCamera);
		}
		else if (mKeyboard->WasKeyPressedThisFrame(DIK_O))
		{
			mFirstPersonCamera->SetEnabled(false);
			mOrbitCamera->SetEnabled(true);
			mGrid->SetCamera(mOrbitCamera);
		}

		Game::Update(gameTime);
	}

	void RenderingGame::Draw(const GameTime &gameTime)
	{
		mDirect3DDeviceContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&BackgroundColor));
		mDirect3DDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

		Game::Draw(gameTime);

		mSpriteBatch->Begin();

		std::wostringstream mouseLabel;
		mouseLabel << L"Mouse Position: " << mMouse->X() << L", " << mMouse->Y() << L"  Mouse Wheel: " << mMouse->Wheel();
		mSpriteFont->DrawString(mSpriteBatch, mouseLabel.str().c_str(), mMouseTextPosition);

		std::wostringstream cameraLabel;
		cameraLabel << std::setprecision(4) <<
			L"Camera Position: " << mFirstPersonCamera->Position().x << L", " << mFirstPersonCamera->Position().y << L", " << mFirstPersonCamera->Position().z <<
			L"  Camera Direction: " << mFirstPersonCamera->Direction().x << L", " << mFirstPersonCamera->Direction().y << L", " << mFirstPersonCamera->Direction().z;
		mSpriteFont->DrawString(mSpriteBatch, cameraLabel.str().c_str(), mCameraTextPosition);

		std::wstring tipLabel = L"Switch Camera: (F)Use first person camera (O)Use orbit camera";
		mSpriteFont->DrawString(mSpriteBatch, tipLabel.c_str(), mTipTextPosition);

		mSpriteBatch->End();

		HRESULT hr = mSwapChain->Present(0, 0);
		if (FAILED(hr))
		{
			throw GameException("IDXGISwapChain::Present() failed.", hr);
		}
	}
}


其中有幾點需要注意:
1.除了兩種Camera之外,還增加一個Grid,Grid組件主要是在場景中添加了一個參考表格,用於用於更好的顯示Camera。關於Grid類使用的相關知識,後面在Effect章節會詳細討論。
2.增加了用於顯示切換Camera的提示語,以及Camera的相關信息的文字輸出。
3.通常情況下每一種Camera都是相關獨立的,具有獨立的座標和方向,在該示例了,由於需要在兩種Camera之間進行切換,因此需要使用同一個座標和方向。爲此,修改Camera基類,把座標和方向相關的變量設爲靜態成員變量:

static XMFLOAT3 mPosition;
static XMFLOAT3 mDirection;
static XMFLOAT3 mUp;
static XMFLOAT3 mRight;

這樣,在兩種Camera中都使用同樣的變量值,切換Camera時就可以保持觀察座標和方向保持不變。



圖形引擎第四步的完整工程代碼:
發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章