An Indexed Cube

来源:互联网 发布:333什么意思网络用语 编辑:程序博客网 时间:2024/04/30 01:46

An Indexed Cube

接下来,通过再创建一个示例结束本章的内容,在这个例子中,使用一个index buffer渲染一个3D立方体。要创建一个CubeDemo类,只需要简单地复制TriangleDemo类的代码,并增加一个ID3D11Buffer指针类型的的成员变量mIndexBuffer。该变量用于存储访问vertex buffer的indices数组。


回顾一下第1章“Introducing DirectX”中所讨论的index buffers相关的内容。Index buffers主要用于消除vertex buffer中重复的vertices。一个cube有6个面,每个面由两个三角形组成。如果不使用index buffer,需要使用36个vertices表示一个cube(6面 * 2个三角形/面 * 3个vertices/三角形)。使用index buffer的情况下,只需要存储cube中每一个唯一的vertices,这样总共就只需要8个vertices。然后就可以创建一个包含36个indices的index buffer。


创建一个index buffer与创建vertex buffer的步骤完全一样:首先配置一个表示buffer的D3D11_BUFFER_DESC结构体对象,然后构建一个D3D11_SUBRESOURCE_DATA结构体对象用于存储初始数据。有了这两个结构体对象之后,就可以调用ID3D11Device::CreateBuffer()函数。创建index buffer与vertex buffer有两处不同的地方:一是D3D11_BUFFER_DESC.BindFlags成员应该设置为D3D_BIND_INDEX_BUFFER;另一个是初始数据是一个无符号整形的数组,不再是vertex结构数组。

列表14.14列出了cube示例中初始化vertex和index buffer的代码。这段代码应该放到CubeDemo::Initialize()函数中。
列表14.14 Creating the Vertex and Index Buffers for a Cube

BasicEffectVertex vertices[] ={BasicEffectVertex(XMFLOAT4(-1.0f, +1.0f, -1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Green))),BasicEffectVertex(XMFLOAT4(+1.0f, +1.0f, -1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Yellow))),BasicEffectVertex(XMFLOAT4(+1.0f, +1.0f, +1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::White))),BasicEffectVertex(XMFLOAT4(-1.0f, +1.0f, +1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::BlueGreen))),BasicEffectVertex(XMFLOAT4(-1.0f, -1.0f, +1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Blue))),BasicEffectVertex(XMFLOAT4(+1.0f, -1.0f, +1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Purple))),BasicEffectVertex(XMFLOAT4(+1.0f, -1.0f, -1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Red))),BasicEffectVertex(XMFLOAT4(-1.0f, -1.0f, -1.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Black)))};D3D11_BUFFER_DESC vertexBufferDesc;ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));vertexBufferDesc.ByteWidth = sizeof(BasicEffectVertex) * ARRAYSIZE(vertices);vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;D3D11_SUBRESOURCE_DATA vertexSubResourceData;ZeroMemory(&vertexSubResourceData, sizeof(vertexSubResourceData));vertexSubResourceData.pSysMem = vertices;if (FAILED(mGame->Direct3DDevice()->CreateBuffer(&vertexBufferDesc, &vertexSubResourceData, &mVertexBuffer))){throw GameException("ID3D11Device::CreateBuffer() failed.");}UINT indices[] ={0, 1, 2,0, 2, 3,4, 5, 6,4, 6, 7,3, 2, 5,3, 5, 4,2, 1, 6,2, 6, 5,1, 7, 6,1, 0, 7,0, 3, 4,0, 4, 7};D3D11_BUFFER_DESC indexBufferDesc;ZeroMemory(&indexBufferDesc, sizeof(indexBufferDesc));indexBufferDesc.ByteWidth = sizeof(UINT) * ARRAYSIZE(indices);indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;D3D11_SUBRESOURCE_DATA indexSubResourceData;ZeroMemory(&indexSubResourceData, sizeof(indexSubResourceData));indexSubResourceData.pSysMem = indices;if (FAILED(mGame->Direct3DDevice()->CreateBuffer(&indexBufferDesc, &indexSubResourceData, &mIndexBuffer))){throw GameException("ID3D11Device::CreateBuffer() failed.");}

在列表14.14的代码中,首先创建了一个由8个vertices组成的vertex buffer,每一个vertex带有一个不同的颜色值,然后创建一个由cube的12个三角形(每个面有2个)组成的index buffer。绘制一个带有index buffer的object与绘制一个不带index buffer的object稍微有点不同。首先需要调用ID3D11DeviceContext::IASetIndexBuffer()函数把index buffer绑定到input-assembler阶段。该函数的原型和参数如下:

void IASetIndexBuffer(ID3D11Buffer *pIndexBuffer,DXGI_FORMAT Format,UINT Offset);

pIndexBuffer:用于绑定的index buffer。
Format:index buffer中的数据格式。只有两种格式可用:分别为表示16位和32位无符号整形数的DXGI_FORMAT_R16_UINT和DXGI_FORMART_R32_UINT。
Offset:index buffer的偏移量,表示在处理indices数据之前需要跳过的数据大小(bytes数量)。


与ID3D11DeviceContext::IASetVertexBuffers()函数不同的是,IASetIndexBuffer()函数只绑定单个index buffer到input-assembler阶段。除了index buffer的绑定不同外,另外一个不同的地方是针对index的特定的绘制操作。通过调用ID3D11DeviceContext::DrawIndexed()函数执行indexed primitives的绘制,该函数的原型与参数如下:

void DrawIndexed(UINT IndexCount,UINT StartIndexLocation,INT BaseVertexLocation);

IndexCount:用于渲染的indices的数量。
StartIndexLocation:第一个处理的index所在的位置。
BaseVertexLocation:使用索引值在vertx buffer中查找一个vertex之前,该index需要增加的基数值。


ID3D11DeviceContext::DrawIndexed()函数中的最后一个参数需要一些额外的说明。在两个objects之间切换vertex和index buffers的开销是非常大的,因此可能会考虑共享同一个vertex buffer。具体实现是指把两个obects的vertex buffers合并到一个vertex buffer中。但是与这两个objects对应的index buffers该怎么处理呢?对于每一个index buffer,只需要指向该object的局部vertex buffer。例如,尽管两个objects具有不同的vertices,但是在一个vertex buffer中的前10个vertices与另一个vertex buffer中的前10个vertices具有同样的indices(都是0到9)。当把两个index buffers合并到一起时,如果不结合BaseVertexLocation参数值就会导致指向错误的vertices。在不使用共享的vertex和index buffers时,该参数值设为0。
列表14.15列出了CubeDemo::Draw()函数的代码,并演示了ID3D11DeviceContext::IASetIndexBuffer()和ID3D11DeviceContext::IASetVertexBuffers()函数的用法。
列表14.15 Drawing an Indexed Cube

void CubeDemo::Draw(const GameTime& gameTime){ID3D11DeviceContext* direct3DDeviceContext = mGame->Direct3DDeviceContext();direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);direct3DDeviceContext->IASetInputLayout(mInputLayout);UINT stride = sizeof(BasicEffectVertex);UINT offset = 0;direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);direct3DDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() * mCamera->ProjectionMatrix();mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));mPass->Apply(0, direct3DDeviceContext);direct3DDeviceContext->DrawIndexed(36, 0, 0);}

使用CubeDemo component时,你可能想要禁用TriangleDemo(实际上,可以同时使用这两个components,但是三角形会被渲染到立方体里面,除非使用平移变换)。另外,如果在示例中使用FpsComponent,运行程序后会看到非常奇怪的输出结果,如图14.2中的左图所示。


图14.2 The cube demo output with the frame rate component (left) and without it(right).


这是第一次出现了深度缓存的问题,因为这是第一次渲染一些互相遮挡的三角形。如果在示例中去掉frame rate component,立方体就会被正确的渲染(如图14.2右图所示)。引起该问题的原因是frame rate component所使用的SpriteBatch系统;SpriteBatch系统会改渲染状态,因此需要自己管理渲染状态。

A Render State Helper Class

回顾一下在编写shaders时,用于表示rasterizer状态和blent状态的对象。这些HLSL对象,具有对应的C++版本ID3D11RaterizerState和ID3D11BlendState,另外还有一个ID3D11DepthStencilState接口。有些情况下,需要自己控制这些渲染状态(比如在上面讨论的cube示例中)。例如,在绘制SpriteBatch类型的2D objects之前,需要先获取这几种状态,然后在sprite渲染完成之后,再恢复这些状态。用于get以及set这三种渲染状态的函数如下:

ID3D11DeviceContext::RSGetState()
ID3D11DeviceContext::OMGetBlendState()
ID3D11DeviceContext::OMGetDepthStencilState()
ID3D11DeviceContext::RSSetState()
ID3D11DeviceContext::OMSetBlendState()
ID3D11DeviceContext::OMSetDepthStencilState()


这些函数都具有类似的功能:获取Direct3D管线的当前状态,保存到对应的接口对象中,然后就可以使用对象更新管线的状态。如果一种状态被设置为NULL,表示更新管线为默认状态。这一次不再是列表这些函数的原型有参数,而是在列表14.6和14.7中列出一个RenderStateHelper类,该支持单个或者分组保存并恢复这些状态。在绘制frame rate component的文字之后,就可以使用RenderStateHelper类恢复渲染状态。


列表14.16 The RenderStateHelper.h File

#pragma once#include "Common.h"namespace Library{    class Game;    class RenderStateHelper    {    public:        RenderStateHelper(Game& game);        ~RenderStateHelper();        static void ResetAll(ID3D11DeviceContext* deviceContext);        ID3D11RasterizerState* RasterizerState();        ID3D11BlendState* BlendState();        ID3D11DepthStencilState* DepthStencilState();        void SaveRasterizerState();        void RestoreRasterizerState() const;        void SaveBlendState();        void RestoreBlendState() const;        void SaveDepthStencilState();        void RestoreDepthStencilState() const;        void SaveAll();        void RestoreAll() const;    private:        RenderStateHelper(const RenderStateHelper& rhs);        RenderStateHelper& operator=(const RenderStateHelper& rhs);        Game& mGame;        ID3D11RasterizerState* mRasterizerState;        ID3D11BlendState* mBlendState;        FLOAT* mBlendFactor;        UINT mSampleMask;        ID3D11DepthStencilState* mDepthStencilState;        UINT mStencilRef;    };}


列表14.17 The RenderStateHelper.cpp File

#include "RenderingGame.h"#include <sstream>#include <SpriteBatch.h>#include <SpriteFont.h>#include "GameException.h"#include "Keyboard.h"#include "Mouse.h"#include "FpsComponent.h"#include "Utility.h"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),   mDirectInput(nullptr), mKeyboard(nullptr), mMouse(nullptr),   mSpriteBatch(nullptr), mSpriteFont(nullptr), mMouseTextPosition(0.0f, 20.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);SetCurrentDirectory(Utility::ExecutableDirectory().c_str());mSpriteBatch = new SpriteBatch(mDirect3DDeviceContext);mSpriteFont = new SpriteFont(mDirect3DDevice, L"Assets\\Fonts\\Arial_14_Regular.spritefont");Game::Initialize();}void RenderingGame::Shutdown(){DeleteObject(mKeyboard);DeleteObject(mMouse);DeleteObject(mFpsComponent);DeleteObject(mSpriteFont);DeleteObject(mSpriteBatch);ReleaseObject(mDirectInput);Game::Shutdown();}void RenderingGame::Update(const GameTime &gameTime){if (mKeyboard->WasKeyPressedThisFrame(DIK_ESCAPE)){Exit();}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() << ", " << mMouse->Y() << "  Mouse Wheel: " << mMouse->Wheel();mSpriteFont->DrawString(mSpriteBatch, mouseLabel.str().c_str(), mMouseTextPosition);mSpriteBatch->End();HRESULT hr = mSwapChain->Present(0, 0);if (FAILED(hr)){throw GameException("IDXGISwapChain::Present() failed.", hr);}}}

可以把RenderStateHelper类直接集成到FspCompnent类中,或者CubeDemo类,或者RenderGame类中。如果使用集成到RenderGame类的方法,需要把FpsComponent从components container中移出,并通过如下的方法手动进行绘制:

mRenderStateHelper->SaveAll();
mFpsComponent->Draw(gameTime);
mRenderStateHelper->RestoreAll();


这种方法保证了在frame rate component的Draw()函数中渲染状态发生改变不会影响其他的components。在本书的配套网站上提供了cube示例的完整代码。

总结

本章完成了第一个完整的3D渲染的应用程序。并运用了相应的API函数,完成了编译并使用effects,创建input layouts,绑定vertex以及index buffers到图表管线的工作。学习了如果绘制indexed和nonindexed objects,以及如何保存并恢复渲染状态。总之,已经完成了Direct3D的完整框架,后面还要继续完善。
下一章,开始学习加载3D模型,并开发一个更复杂的材质系统,用于支持之前开发的shaders。

Exercises

1. Apply transformations to either the triangle or the cube to view them simultaneously (their
untransformed vertices place the triangle inside the cube and, thus, out of view of the camera).
2. Attach the keyboard to the cube to translate or rotate it (for example, you can use the arrow
keys or the number pad).

1.针对三角形或立方体执行变换,使得这两个objects能同时显示出来(未变换的情况下,三角形处理立方体里面,无法被看到)。

2.把keyboard component应用到立方体上,用于平移或转换该立方体(例如,使用键盘的方向键或数字键)。


本章配套学习代码:

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

其中Cube示例中对三角形做了平移变换,可以同时显示出两个objects。并对Cube增加了键盘控制,通过键盘方向键可以平移该cube。

1 0
原创粉丝点击