DirectX11入门到实践-(4)逻辑篇
先来回顾一下,现在我们已经有窗口、设备、输入、缓冲区、着色器等渲染所需要的一切了,渲染框架已经基本完备,输入顶点后就可以绘制出一些简单的图形。但是,如果我们要渲染更复杂的世界,光有这些是不够的,需要有"逻辑框架",也就是引擎的对象组成部分,下面我们就来做这一步。
对象
一般来说,一个对象应该具有pos、rotation、forward等属性及其操作方法,并包含其渲染数据(vertex、index、constant buffer)。最后,不要忘了保存并及时更新它的worldMatrix变换方式
[Object.h]#include "Vertex.h"#include "VertexBuffer.h"#include "IndexBuffer.h"#include "ConstantBuffer.h"#include <vector>using namespace DirectX;class Object{public:const XMVECTOR & GetPositionVector() const;const XMFLOAT3 & GetPositionFloat3() const;const XMVECTOR & GetRotationVector() const;const XMFLOAT3 & GetRotationFloat3() const;void SetPosition(const XMVECTOR & position) // 设置位置等操作后,不忘更新一下WorldMatrix{XMStoreFloat3(&this->position, position);this->positionVector = position;this->UpdateWorldMatrix();}void SetPosition(const XMFLOAT3 & position);void SetPosition(float x, float y, float z);void AdjustPosition(const XMVECTOR & position);void AdjustPosition(const XMFLOAT3 & position);void AdjustPosition(float x, float y, float z);void SetRotation(const XMVECTOR & rotation);void SetRotation(const XMFLOAT3 & rotation);void SetRotation(float x, float y, float z);void AdjustRotation(const XMVECTOR & rotation);void AdjustRotation(const XMFLOAT3 & rotation);void AdjustRotation(float x, float y, float z);void SetLookAtPos(XMFLOAT3 lookAtPos);const XMVECTOR & GetForwardVector();const XMVECTOR & GetRightVector();const XMVECTOR & GetUpVector();const XMMATRIX& GetWorldMatrix();void XM_CALLCONV SetWorldMatrix(XMMATRIX world);void virtual UpdateWorldMatrix(Object* parent = nullptr) // 更新世界坐标变换矩阵,这是一个细活{this->worldMatrix = XMMatrixRotationRollPitchYaw(this->rotation.x, this->rotation.y, this->rotation.z) * XMMatrixTranslation(this->position.x, this->position.y, this->position.z);if (parent != nullptr)this->worldMatrix *= parent->GetWorldMatrix();// 修正单位方向向量的朝向XMMATRIX vecRotationMatrix = XMMatrixRotationRollPitchYaw(0.0f, this->rotation.y, 0.0f);this->vecForward = XMVector3TransformCoord(this->DEFAULT_FORWARD_VECTOR, vecRotationMatrix);this->vecRight = XMVector3TransformCoord(this->DEFAULT_RIGHT_VECTOR, vecRotationMatrix);this->vecUp = XMVector3TransformCoord(this->DEFAULT_UP_VECTOR, vecRotationMatrix);}protected:ID3D11Device * device = nullptr;ID3D11DeviceContext * deviceContext = nullptr;ConstantBuffer<CB_VS_VertexShader> * cb_vs_vertexshader = nullptr;ID3D11ShaderResourceView * texture = nullptr;VertexBuffer<Vertex> vertexBuffer;IndexBuffer indexBuffer;XMMATRIX worldMatrix = XMMatrixIdentity();XMVECTOR positionVector;XMVECTOR rotationVector;XMFLOAT3 position;XMFLOAT3 rotation;const XMVECTOR DEFAULT_FORWARD_VECTOR = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);const XMVECTOR DEFAULT_RIGHT_VECTOR = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);const XMVECTOR DEFAULT_UP_VECTOR = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);XMVECTOR vecForward;XMVECTOR vecRight;XMVECTOR vecUp;};
建议看原文,首先我们有 W世界矩阵 = R旋转矩阵 * T位移矩阵 (不考虑缩放的情况下)
我们可以把上述变换看作:将物体从世界坐标原点搬移到世界坐标系对应的位置,并按其坐标轴做对应朝向和大小的调整。
现在我们需要做的是从世界坐标系转换到观察空间坐标系,如果把摄像机看做物体的话,实际上观察空间坐标系就是摄像机物体的局部坐标系(右方向为X轴,上方向为Y轴,目视方向为Z轴)。因此我们现在做的是从世界坐标系变换回摄像机的局部坐标系,即世界矩阵的逆变换:(也就是 将相机变换到坐标轴原点的逆矩阵)
V = (RT)^−1 = T^−1 * R^−1 = T^-1 * R^T
Draw的时候,将这个viewProjectionMatrix(由相机给出camera.GetViewMatrix() * camera.GetProjectionMatrix())和worldMatrix 相乘得到wvp矩阵。传给vertexshader,由此shader就可以知道各顶点要绘制的位置了
子物体跟随的原理:
M3子物体世界矩阵 = M1^-1 子物体变换矩阵 * M0 父物体变换矩阵 * M2 父物体世界矩阵
XMMatrixTranslation函数:构建平移矩阵
XMMatrixRotationRollPitchYaw函数:构建旋转矩阵
Mesh-Model
这里我们为了方便load资源文件,用了assimp插件
#include <assimp/Importer.hpp>#include <assimp/postprocess.h>#include <assimp/scene.h>using namespace DirectX;class Mesh{public:// Mesh构造函数:根据mesh的vertices和indices构建顶点和索引缓冲区Mesh(ID3D11Device * device, ID3D11DeviceContext * deviceContext, std::vector<Vertex> & vertices, std::vector<DWORD> & indices){this->deviceContext = deviceContext;HRESULT hr = this->vertexBuffer.Initialize(device, vertices.data(), vertices.size());hr = this->indexBuffer.Initialize(device, indices.data(), indices.size());}Mesh(const Mesh & mesh);// Draw函数就根据mesh已知的这些数据,进行DrawIndexed绘制void Draw(){UINT offset = 0;this->deviceContext->IASetVertexBuffers(0, 1, this->vertexBuffer.GetAddressOf(), this->vertexBuffer.StridePtr(), &offset);this->deviceContext->IASetIndexBuffer(this->indexBuffer.Get(), DXGI_FORMAT::DXGI_FORMAT_R32_UINT, 0);this->deviceContext->DrawIndexed(this->indexBuffer.IndexCount(), 0, 0);}private:VertexBuffer<Vertex> vertexBuffer;IndexBuffer indexBuffer;ID3D11DeviceContext * deviceContext;};class Model : public Object{public:// 从预定义的vertex和index加载的模型bool Initialize(std::vector<Vertex>& vertexVector, std::vector<DWORD>& indexVector, ID3D11Device* device, ID3D11DeviceContext * deviceContext, ID3D11ShaderResourceView * texture, ConstantBuffer<CB_VS_VertexShader> & cb_vs_vertexshader){this->device = device;this->deviceContext = deviceContext;this->texture = texture;this->cb_vs_vertexshader = &cb_vs_vertexshader;meshes.push_back(Mesh(this->device, this->deviceContext, vertexVector, indexVector)); // 借由构建Mesh来构造Model,Model可以包含多个Meshthis->SetPosition(0.0f, 0.0f, 0.0f);this->SetRotation(0.0f, 0.0f, 0.0f);this->UpdateWorldMatrix();return true;}// 从文件中加载的模型bool Initialize(const std::string & filePath, ID3D11Device * device, ID3D11DeviceContext * deviceContext, ConstantBuffer<CB_VS_VertexShader> & cb_vs_vertexshader){...除了mesh部分靠LoadFile,try{if (!this->LoadModel(filePath))return false;}catch (COMException & exception){ErrorLogger::Log(exception);return false;}...其他和mesh的Initialize一样}void SetTexture(ID3D11ShaderResourceView* texture);void virtual Draw(const XMMATRIX & viewProjectionMatrix){// 计算并保存World-View-Projection矩阵this->cb_vs_vertexshader->data.matrix = this->worldMatrix * viewProjectionMatrix;this->cb_vs_vertexshader->data.matrix = XMMatrixTranspose(this->cb_vs_vertexshader->data.matrix);this->cb_vs_vertexshader->ApplyChanges();this->deviceContext->VSSetConstantBuffers(0, 1, this->cb_vs_vertexshader->GetAddressOf());this->deviceContext->PSSetShaderResources(0, 1, &this->texture); // Set Textureif (!meshes.empty()) // 每一个mesh都画{for (int i = 0; i < meshes.size(); i++)meshes[i].Draw();}else{this->deviceContext->IASetIndexBuffer(this->indexBuffer.Get(), DXGI_FORMAT::DXGI_FORMAT_R32_UINT, 0);UINT offset = 0;this->deviceContext->IASetVertexBuffers(0, 1, this->vertexBuffer.GetAddressOf(), this->vertexBuffer.StridePtr(), &offset);this->deviceContext->DrawIndexed(this->indexBuffer.IndexCount(), 0, 0); //Draw}}protected:// LoadModel通过Assimp::Importer来ReadFilebool LoadModel(const std::string & filePath){Assimp::Importer importer;const aiScene* pScene = importer.ReadFile(filePath, aiProcess_Triangulate | aiProcess_ConvertToLeftHanded);if (pScene == nullptr)return false;// ReadFile得到的是一个nodeScene,需要对每一个Node递归处理其meshthis->ProcessNode(pScene->mRootNode, pScene);return true;}// 递归处理,每个结点的mesh挂到Model上void ProcessNode(aiNode * node, const aiScene * scene){for (UINT i = 0; i < node->mNumMeshes; i++) // 处理结点上的每个mesh{aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];meshes.push_back(this->ProcessMesh(mesh, scene)); // Get Vertices & Indices => Mesh}for (UINT i = 0; i < node->mNumChildren; i++) // DFS{this->ProcessNode(node->mChildren[i], scene);}}Mesh ProcessMesh(aiMesh * mesh, const aiScene * scene);std::vector<Mesh> meshes;};
这样封装好一个,我们画一个model,只需要定义+init,然后draw即可
Prefab
为了方便创建各种模型,我们提供了Prefab静态接口
常见模型:Sphere,Box,Cylinder,Plane...
[Prefab.hpp]#include <vector>#include <string>#include <map>#include "Vertex.h"#include "VertexBuffer.h"class Prefab{public:// 网格数据struct MeshData{std::vector<Vertex> vertexVec; // 顶点数组std::vector<DWORD> indexVec; // 索引数组MeshData(){// 需检验索引类型合法性static_assert(sizeof(DWORD) == 2 || sizeof(DWORD) == 4, "The size of IndexType must be 2 bytes or 4 bytes!");static_assert(std::is_unsigned<DWORD>::value, "IndexType must be unsigned integer!");}};// 创建球体网格数据,levels和slices越大,精度越高。static MeshData CreateSphere(float radius = 1.0f, int levels = 20, int slices = 20);// 创建立方体网格数据static MeshData CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f);// 创建圆柱体网格数据,slices越大,精度越高。static MeshData CreateCylinder(float radius = 1.0f, float height = 2.0f, int slices = 20);// 创建只有圆柱体侧面的网格数据,slices越大,精度越高static MeshData CreateCylinderSide(float radius = 1.0f, float height = 2.0f, int slices = 5);// 创建一个平面static MeshData CreatePlane(const DirectX::XMFLOAT3& center, const DirectX::XMFLOAT2& planeSize = { 10.0f, 10.0f }, const DirectX::XMFLOAT2& maxTexCoord = { 1.0f, 1.0f });static MeshData CreatePlane(float centerX = 0.0f, float centerY = 0.0f, float centerZ = 0.0f, float width = 10.0f, float depth = 10.0f, float texU = 1.0f, float texV = 1.0f){using namespace DirectX;MeshData meshData;meshData.vertexVec.resize(4);VertexData vertexData;DWORD vIndex = 0;// 平面就画了一个四边形vertexData = { XMFLOAT3(centerX - width / 2, centerY, centerZ - depth / 2), XMFLOAT2(0.0f, texV) };ConvertToVertexType(meshData.vertexVec[vIndex++], vertexData);vertexData = { XMFLOAT3(centerX - width / 2, centerY, centerZ + depth / 2), XMFLOAT2(0.0f, 0.0f) };ConvertToVertexType(meshData.vertexVec[vIndex++], vertexData);vertexData = { XMFLOAT3(centerX + width / 2, centerY, centerZ + depth / 2), XMFLOAT2(texU, 0.0f) };ConvertToVertexType(meshData.vertexVec[vIndex++], vertexData);vertexData = { XMFLOAT3(centerX + width / 2, centerY, centerZ - depth / 2), XMFLOAT2(texU, texV) };ConvertToVertexType(meshData.vertexVec[vIndex++], vertexData);meshData.indexVec = { 0, 1, 2, 2, 3, 0 }; // 4个顶点两个三角形顺序return meshData;}private:struct VertexData{DirectX::XMFLOAT3 position;DirectX::XMFLOAT2 texCoord;};// 将我们构造的VertexData类型转为Vertex类型static void ConvertToVertexType(Vertex& vertexDst, const VertexData& vertexSrc){vertexDst.position = vertexSrc.position;vertexDst.texCoord = vertexSrc.texCoord;}private:static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap;};
设置vertex, index, init, 最后draw
box和plane最好画,给出每个面的点和连线就行了;cylinder是分为两个圆面和一个弧面画的,弧面按圆面上的点以一定精度连线
sphere的话 需要根据经度和纬度的迭代计算出来
相机
class Camera : public Object // 相机是没有实际模型的,因此只继承Object{public:Camera();enum class Mode{FirstPerson, // 只能WASD控制移动的模式ThirdPerson, // 跟随物体,并可绕物体旋转的模式Free // 自由自动和旋转的模式};Mode mode;void SetFrustum(float fovDegrees, float aspectRatio, float nearZ, float farZ){float fovRadians = (fovDegrees / 360.0f) * XM_2PI;this->projectionMatrix = XMMatrixPerspectiveFovLH(fovRadians, aspectRatio, nearZ, farZ);}void SetTarget(Model* target);void RemoveTarget();void UpdateViewMatrix();const XMMATRIX & GetViewMatrix() const;const XMMATRIX & GetProjectionMatrix() const;float GetDistance() const;float GetRotationX() const;float GetRotationY() const;// 绕物体垂直旋转(限制角度)void RotateX(float rad);// 绕物体水平旋转void RotateY(float rad);// 拉近物体void Approach(float dist);// 设置初始绕X轴的弧度(限制角度)void SetRotationX(float rotX);// 设置初始绕Y轴的弧度void SetRotationY(float rotY);// 设置初始距离void SetDistance(float dist);// 设置最小最大允许距离void SetDistanceMinMax(float minDist, float maxDist);private:Model* target = nullptr;XMMATRIX viewMatrix;XMMATRIX projectionMatrix;XMVECTOR vecLook;float distance;// 最小允许距离,最大允许距离float minDistance, maxDistance;};
void Camera::UpdateViewMatrix(){// 第三人称模式if (target != nullptr && mode == Mode::ThirdPerson){// 球面坐标系计算相机实际位置,效果比XMMatrixLookAtLH计算得到的ViewMatrix更好position.x = target->GetPositionFloat3().x + distance * sinf(rotation.x) * cosf(rotation.y);position.z = target->GetPositionFloat3().z + distance * sinf(rotation.x) * sinf(rotation.y);position.y = target->GetPositionFloat3().y + distance * cosf(rotation.x);positionVector = XMLoadFloat3(&position);vecLook = XMVector3Normalize(XMLoadFloat3(&target->GetPositionFloat3()) - positionVector);}// 其他模式else{XMMATRIX rotationMatrix = XMMatrixRotationRollPitchYaw(this->rotation.x, this->rotation.y, this->rotation.z);vecLook = XMVector3TransformCoord(this->DEFAULT_FORWARD_VECTOR, rotationMatrix);}vecRight = XMVector3Normalize(XMVector3Cross(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), vecLook));vecUp = XMVector3Cross(vecLook, vecRight);XMFLOAT3 look, right, up;// 更新向量XMStoreFloat3(&look, vecLook);XMStoreFloat3(&right, vecRight);XMStoreFloat3(&up, vecUp);// 根据前面计算的结果得到视点矩阵this->viewMatrix = {right.x, up.x, look.x, 0.0f,right.y, up.y, look.y, 0.0f,right.z, up.z, look.z, 0.0f,-XMVectorGetX(XMVector3Dot(positionVector, vecRight)), -XMVectorGetX(XMVector3Dot(positionVector, vecUp)), -XMVectorGetX(XMVector3Dot(positionVector, vecLook)), 1.0f};}void Camera::Approach(float dist){distance += dist;// 限制距离在[minDistance, maxDistance]之间if (distance < minDistance)distance = minDistance;else if (distance > maxDistance)distance = maxDistance;}void Camera::SetRotationX(float rotX){rotation.x = XMScalarModAngle(rotX);// 将上下视野角度限制在[pi/6, pi/2],即余弦值[0, cos(pi/6)]之间if (rotation.x < XM_PI / 6)rotation.x = XM_PI / 6;else if (rotation.x > XM_PIDIV2)rotation.x = XM_PIDIV2;}void Camera::SetRotationY(float rotY){rotation.y = XMScalarModAngle(rotY);}
Skybox
设置天空盒
class Skybox{public:bool Initialize(ID3D11Device * device, ID3D11DeviceContext * deviceContext, ConstantBuffer<CB_VS_VertexShader> & cb_vs_vertexshader, float radius); // 初始化其实和Box的差不多,不过预先要创建好6张贴图void Draw(const Camera& camera, const XMMATRIX & viewProjectionMatrix);void SetTexture(ID3D11ShaderResourceView* texture, int index);private:ID3D11Device* device = nullptr;ID3D11DeviceContext * deviceContext = nullptr;ID3D11ShaderResourceView* texture[6];ConstantBuffer<CB_VS_VertexShader> * cb_vs_vertexshader = nullptr;VertexBuffer<Vertex> vertexBuffer;IndexBuffer indexBuffer;XMMATRIX worldMatrix = XMMatrixIdentity();float boxRadius;// 前后左右上下顺序std::vector<std::wstring> textureFile = {L"Data\\Textures\\1.jpg", L"Data\\Textures\\2.jpg",L"Data\\Textures\\3.jpg", L"Data\\Textures\\4.jpg",L"Data\\Textures\\5.jpg", L"Data\\Textures\\6.jpg", };Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texturePtr[6];};void Skybox::Draw(const Camera& camera, const XMMATRIX & viewProjectionMatrix){// 根据相机移动XMFLOAT3 position = camera.GetPositionFloat3();this->worldMatrix = XMMatrixTranslation(position.x, position.y, position.z);this->cb_vs_vertexshader->data.matrix = this->worldMatrix * viewProjectionMatrix;this->cb_vs_vertexshader->data.matrix = XMMatrixTranspose(this->cb_vs_vertexshader->data.matrix); // 转置this->cb_vs_vertexshader->ApplyChanges();this->deviceContext->VSSetConstantBuffers(0, 1, this->cb_vs_vertexshader->GetAddressOf());UINT indexCount = this->indexBuffer.IndexCount() / 6;this->deviceContext->IASetIndexBuffer(this->indexBuffer.Get(), DXGI_FORMAT::DXGI_FORMAT_R32_UINT, 0);UINT offset = 0;this->deviceContext->IASetVertexBuffers(0, 1, this->vertexBuffer.GetAddressOf(), this->vertexBuffer.StridePtr(), &offset);for (int i = 0; i < 6; ++i){this->deviceContext->PSSetShaderResources(0, 1, &this->texture[i]); //Set Texturethis->deviceContext->DrawIndexed(indexCount, indexCount*i, 0); //Draw}}
原理就是一个内部贴图的巨球(叫盒子只是因为纹理是立方体盒子,实际上直接映射到球体就行了)
我就先画了个1000半径的立方体,for循环6张贴图,DDS不知为啥用不了只能WIC,然后渲染立方体内面,根据固定的顺序……最后记得要把fov设置的比半径大,否则会被bgcolor覆盖背景。
现在推荐的做法为:总是先清空渲染目标和深度/模板缓冲区,天空盒的绘制留到最后。
光照模型
class Light :public Object{public:bool Initialize(ID3D11Device * device, ID3D11DeviceContext * deviceContext);void SetLightProperties(XMFLOAT3 color = XMFLOAT3(1.0f, 1.0f, 1.0f), float strength = 1.0f);private:// 暂时就只实现了一个全局光ConstantBuffer<CB_PS_Light> cb_vs_light;};bool Light::Initialize(ID3D11Device * device, ID3D11DeviceContext * deviceContext){this->device = device;this->deviceContext = deviceContext;HRESULT hr = this->cb_vs_light.Initialize(device, deviceContext);COM_ERROR_IF_FAILED(hr, "Failed to initialize constant buffer.");return true;}void Light::SetLightProperties(XMFLOAT3 color, float strength){this->cb_vs_light.data.ambientLightColor = color;this->cb_vs_light.data.ambientLightStrength = strength;this->cb_vs_light.ApplyChanges();this->deviceContext->PSSetConstantBuffers(0, 1, this->cb_vs_light.GetAddressOf());}
将模型组装成Car
class Car:public Model{public:bool Initialize(ID3D11Device * device, ID3D11DeviceContext * deviceContext, ConstantBuffer<CB_VS_VertexShader> & cb_vs_vertexshader);void Draw(const XMMATRIX& viewProjectionMatrix);void WheelRoll(float deltaTime, bool forward = true);bool dontDraw = false;protected:void UpdateWorldMatrix(Object* parent = nullptr);private:Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> bodyTexture;Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> wheelTexture;float rollSpeed = 0.01f;Model wheels[4];};
这一切逻辑的触发位置:
在我们Graphics文件中,有定义这些绘制对象,并在InitializeScene和RenderFrame中对其进行绘制,除此之外还可加入字体、帧数计数等
bool Graphics::InitializeScene(){try{// Load TextureHRESULT hr = CreateWICTextureFromFile(this->device.Get(), L"Data\\Textures\\plane.jpg", nullptr, planeTexture.GetAddressOf());COM_ERROR_IF_FAILED(hr, "Failed to create wic texture from file.");// Initialize Constant Buffershr = this->cb_vs_vertexshader.Initialize(this->device.Get(), this->deviceContext.Get());COM_ERROR_IF_FAILED(hr, "Failed to initialize constant buffer.");hr = this->cb_ps_pixelshader.Initialize(this->device.Get(), this->deviceContext.Get());COM_ERROR_IF_FAILED(hr, "Failed to initialize constant buffer.");if(!skybox.Initialize(this->device.Get(), this->deviceContext.Get(), this->cb_vs_vertexshader, 1000))return false;if (!light.Initialize(this->device.Get(), this->deviceContext.Get()))return false;if(!car.Initialize(this->device.Get(), this->deviceContext.Get(), this->cb_vs_vertexshader))return false;auto planeMeshData = Prefab::CreatePlane(XMFLOAT3(0, -0.8f, 0), XMFLOAT2(100, 100));if (!plane.Initialize(planeMeshData.vertexVec, planeMeshData.indexVec, this->device.Get(), this->deviceContext.Get(), this->planeTexture.Get(), this->cb_vs_vertexshader))return false;light.SetLightProperties(XMFLOAT3(1, 1, 1), 0.8f);camera.SetFrustum(90.0f, static_cast<float>(windowWidth) / static_cast<float>(windowHeight), 0.1f, 2000.0f);camera.SetTarget(&car);camera.SetDistance(5.0f);camera.SetDistanceMinMax(3.0f, 10.0f);}catch (COMException & exception){ErrorLogger::Log(exception);return false;}return true;}void Graphics::RenderFrame(){float bgcolor[] = { 1.0f, 1.0f, 1.0f, 0.0f };this->deviceContext->ClearRenderTargetView(this->renderTargetView.Get(), bgcolor);this->deviceContext->ClearDepthStencilView(this->depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);this->deviceContext->IASetInputLayout(this->vertexshader.GetInputLayout());this->deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);this->deviceContext->RSSetState(this->rasterizerState.Get());this->deviceContext->OMSetDepthStencilState(this->depthStencilState.Get(), 0);this->deviceContext->OMSetBlendState(NULL, NULL, 0xFFFFFFFF);this->deviceContext->PSSetSamplers(0, 1, this->samplerState.GetAddressOf());this->deviceContext->VSSetShader(vertexshader.GetShader(), NULL, 0);this->deviceContext->PSSetShader(pixelshader.GetShader(), NULL, 0);XMMATRIX matrix = camera.GetViewMatrix() * camera.GetProjectionMatrix();this->plane.Draw(matrix);this->skybox.Draw(camera, matrix);this->car.Draw(matrix);// Draw Textstatic int fpsCounter = 0;static std::string fpsString = "FPS: 0";// 按键切换视角: 1-第一人称 2-第三人称 \nW/S/A/D 前进/后退/左转/右转 \n第三人称下鼠标右键移动控制视野\nstatic std::wstring text = L"Input: 1-FirstPerson 2-ThirdPeron \nW/S/A/D Move \nRight mouse button motion control vision\n";fpsCounter += 1;if (fpsTimer.GetElapsedTime() > 1000.0){fpsString = "FPS: " + std::to_string(fpsCounter);fpsCounter = 0;fpsTimer.Restart();}spriteBatch->Begin();spriteFont->DrawString(spriteBatch.get(), StringConverter::StringToWstring(fpsString).c_str(), DirectX::XMFLOAT2(0, 0), DirectX::Colors::White, 0.0f, DirectX::XMFLOAT2(0.0f, 0.0f), DirectX::XMFLOAT2(1.0f, 1.0f));spriteFont->DrawString(spriteBatch.get(), text.c_str(), DirectX::XMFLOAT2(0, 30), DirectX::Colors::White, 0.0f, DirectX::XMFLOAT2(0.0f, 0.0f), DirectX::XMFLOAT2(1.0f, 1.0f));spriteBatch->End();static int counter = 0;this->swapchain->Present(0, NULL);}