第十四章 Hello, Rendering
现在,终于完成了应用程序的基础框架,并开发了足够的代码以用于渲染工作。本章将会实现第一个完整的3D应用程序。并使用大量的Direct3D和Effects 11 API函数。完成这些工作之后,将会更深入的理解Direct3D图形管线。Your First Full Rendering Application
现在正式开始在屏幕上渲染第一个3D object:一个三角形。由于这是第一个完整的Direct3D程序,似乎只适合把该应用程序命名为“Hello, Rendering”。但是这个名称掩盖了程序的复杂性,因为从前面几章的学习中已经认识到,仅仅只是渲染一个简单的三角形也需要大量的基础组件。另外,在本节中添加了大量的代码。远比一个简单的单行输出“Hello, World!”程序要复杂的多。那我们正式开始吧。An Updated Game Project
你可以继续使用之前创建的Game工程,或者创建一个新工程。不需要修改Game工程的设计结构,使用相同的Program.cpp文件,初始化一个RenderingGame对象并调用RenderingGame::Run()函数。RenderingGame类的实现代码与之前一样,除了两点:增加了FirstPersonCamera,以及一个TriangleDemo类型的成员变量。TriangleDemo类继承自DrawableGameComponent类,包含了渲染一个三角形的全部代码。增加了这些新的成员变量之后,RenderingGame类的实现代码如列表14.1所示。列表14.1 The RenderingGame.cpp File
#include "RenderingGame.h"
#include "GameException.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "FpsComponent.h"
#include "ColorHelper.h"
#include "FirstPersonCamera.h"
#include "TriangleDemo.h"
namespace Rendering
{
const XMVECTORF32 RenderingGame::BackgroundColor = ColorHelper::CornflowerBlue;
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),
mDemo(nullptr)
{
mDepthStencilBufferEnabled = true;
mMultiSamplingEnabled = 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);
mCamera = new FirstPersonCamera(*this);
mComponents.push_back(mCamera);
mServices.AddService(Camera::TypeIdClass(), mCamera);
mFpsComponent = new FpsComponent(*this);
mFpsComponent->Initialize();
mDemo = new TriangleDemo(*this, *mCamera);
mComponents.push_back(mDemo);
Game::Initialize();
mCamera->SetPosition(0.0f, 0.0f, 5.0f);
}
void RenderingGame::Shutdown()
{
DeleteObject(mDemo);
DeleteObject(mKeyboard);
DeleteObject(mMouse);
DeleteObject(mFpsComponent);
DeleteObject(mCamera);
ReleaseObject(mDirectInput);
Game::Shutdown();
}
void RenderingGame::Update(const GameTime &gameTime)
{
mFpsComponent->Update(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);
HRESULT hr = mSwapChain->Present(0, 0);
if (FAILED(hr))
{
throw GameException("IDXGISwapChain::Present() failed.", hr);
}
}
}
现在在RenderingGame::Initialize()函数中,初始了成员变量mCamera和mDemo,并把这两个变量添加到components container中。此外,引用了一个ColorHelper类用于初始化背景色。ColorHelper类中包含了一些静态成员用于表示一些常用的颜色值(XMVECTORF32类型),并没有在这里列出来,但是本书的配套网站提供了完整的代码。最后需要注意的是,在Initialize()函数的最后调用了FirstPersonCamera::SetPosition()函数。该函数把camera沿着z轴的正向移动5个单位的距离。由于三角形会创建在坐标原点处,通过调用SetPosition函数移动camera,可以使得在程序启动之后三角形能立即显示出来。这个例子中使用了右手坐标系,所以z轴的正向是朝向屏幕外面。
A TriangleDemo Component
列表14.2列出了TriangleDemo类的声明代码。在该代码中,包含有一个BasicEffectVertex结构体,以及ID3DX11Effect相关的成员变量。前者定义了三角形三个vertices的结构,以及作为shader输入要传递的数据的格式。后者引用了Effects 11库的中数据类型,在第3章“Tools of the Trade”中介绍了该库。如果在工程的external目录中还没有Effects 11库,需要把库解压到该目录中。如果在使用Effects 11库的过程中遇到了问题,可以参考第10章“Project Setup and Window Initialization”中的“Linking Libraries”一节。在Direct3D中,所有渲染都需要使用shaders,因此,无论渲染场景多么复杂,都需要使用ID3DX11Effect相关的成员变量。此外,还有一个ID3DInputLayout类型的成员变量,定义了用于管线的input-assembler阶段的vetex数据结构。以及一个ID3D11Buffer类型的成员变量,用于表示vertex buffer。接下来列出TriangeleDemo的声明,再讨论这些变量。
列表14.2 The TriangleDemo.h Header File
#pragma once
#include "DrawableGameComponent.h"
using namespace Library;
namespace Rendering
{
class TriangleDemo : public DrawableGameComponent
{
RTTI_DECLARATIONS(TriangleDemo, DrawableGameComponent)
public:
TriangleDemo(Game& game, Camera& camera);
~TriangleDemo();
virtual void Initialize() override;
virtual void Update(const GameTime& gameTime) override;
virtual void Draw(const GameTime& gameTime) override;
private:
typedef struct _BasicEffectVertex
{
XMFLOAT4 Position;
XMFLOAT4 Color;
_BasicEffectVertex() { }
_BasicEffectVertex(XMFLOAT4 position, XMFLOAT4 color)
: Position(position), Color(color) { }
} BasicEffectVertex;
TriangleDemo();
TriangleDemo(const TriangleDemo& rhs);
TriangleDemo& operator=(const TriangleDemo& rhs);
ID3DX11Effect* mEffect;
ID3DX11EffectTechnique* mTechnique;
ID3DX11EffectPass* mPass;
ID3DX11EffectMatrixVariable* mWvpVariable;
ID3D11InputLayout* mInputLayout;
ID3D11Buffer* mVertexBuffer;
XMFLOAT4X4 mWorldMatrix;
float mAngle;
};
}
The Effects 11 Library
Effects 11库提供了一组C++接口用于处理effect文件格式(在所有shaders中使用的.fx文件格式)。其中,ID3DX11Effect是顶层接口,包含了effect的变量和techniques。使用接口ID3DX11EffectTechnique可以访问一个technique,而一个technique中又包含了一个或多个passes,passes由接口ID3DX11EffectPass表示。一种effect的所有变量都可以使用接口ID3DX11EffectVariable表示,但是对于一些具体shader constant类型,也可以使用ID3DX11EffectVariable的派生类表示。比如,使用ID3DEffectMatrixVariable类型的成员变量TriangleDemo::mWvpVariable,可以访问effect中对应的WorldViewProjection矩阵。Effects 11库中的所有接口类型都包含在d3dx11Effect.h头文件中。Compiling an Effect
在使用一种effect之前,需要先编译effect文件。可以在编译Visual Studio工程的过程中编译effect,或者在程序运行时编译effect。在下一章将会讲解如何在编译工程的时候编译effects,但是在本节主要使用程序运行时编译effect的方法。调用函数D3DCompileFromFile()可以编译一个effect文件,该函数包含在D3DCompiler.h头文件中,函数原型及参数如下:
HRESULT D3DCompileFromFile(
LPCWSTR pFileName,
D3D_SHADER_MACRO* pDefines,
ID3DInclude* pInclude,
LPCSTR pEntrypoint,
LPCSTR pTarget,
UINT Flags1,
UINT Flags2,
ID3DBlob** ppCode,
ID3DBlob** ppErrorMsgs);
pFileName:包含effect的文件的名称。
pDefines:由D3D_SHADER_MACRO结构体定义的一个shader宏数组。目前的effects不需要使用任何shader宏,可以直接把这个参数设为NULL。
pInclude:一个ID3DInclude类型的指针对象,指明应用程序通过自定义方法打开并关闭 #include 文件。如果该参数设置为NULL,带有#include指令的effect就会编译失败。可以使用D3D_COMPILE_STANDARD_FILE_INCLUDE宏表示默认的include句柄(即默认参数值)。由于该默认句柄表示包含文件位于与当前路径相关联的目录中,因此在使用该参数值编译effects之前,需要把当前工作路径设置为正确的路径。
pEntryPoint:shader中的入口点函数的名称。在编译effect文件时,始终把该参数设为NULL。
pTarget:指定shader编译器的目标版本。在本书所使用的effects中都指定为fx_5_0。
Flags1:由D3DCOMPILE类型的枚举常量按位与运算的结果,用于指定shader的编译方式。比如,常用的编译选项D3DCOMPILE_DEBUG和D3DCOMPILE_SKIP_OPTIMIZATIONS,可以用于调试shaders。要查看完整的选项列表,可以阅读在线文档。
Flags2:由D3DCOMPILE_EFFECT类型的枚举常量按位与运算的结果,用于指定effect文件的编译方式。主要用于一些高级的功能,通常不使用该参数。
ppCode:编译成功的返回值,指向一个ID3DBlob类型的对象。ID3DBlob接口表示一个任意长度的buffer,仅仅包含两个成员函数:ID3DBlob::GetBufferPointer()和ID3DBlob::GetBufferSize(),分别返回一个指向buffer数据的指针以及数据的大小(单位为bytes)。
ppErrorMsgs:用于存储编译返回的错误信息,该参数是可选的。
列表14.3演示了D3DCompileFromFile()函数的使用方法。
14.3 Compiling an Effect
SetCurrentDirectory(Utility::ExecutableDirectory().c_str());
// Compile the shader
UINT shaderFlags = 0;
#if defined( DEBUG ) || defined( _DEBUG )
shaderFlags |= D3DCOMPILE_DEBUG;
shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
ID3D10Blob* compiledShader = nullptr;
ID3D10Blob* errorMessages = nullptr;
HRESULT hr = D3DCompileFromFile(L"Content\\Effects\\BasicEffect.fx", nullptr, nullptr, nullptr, "fx_5_0", shaderFlags, 0, &compiledShader, &errorMessages);
if (FAILED(hr))
{
char* errorMessage = (errorMessages != nullptr ? (char*)errorMessages->GetBufferPointer() : "D3DX11CompileFromFile() failed");
GameException ex(errorMessage, hr);
ReleaseObject(errorMessages);
throw ex;
}
该代码主要用于编译BasicEffect.fx文件,该文件位于应用程序的可执行文件所在目录的Content\Effects\目录下。变量compiledShader用于存储编译返回的effect,返回的任何错误信息都存储在变量errorMessages中。
BasicEffect.fx文件中的代码主要来自第4章中编写的HelloStructs.fx effect文件。在该effect中,使用一个vertex坐标和颜色作为shader的输入,然后把坐标变换到齐次裁剪空间中,最后输出vertex的颜色,对每一个pixel进行插值。列表14.4列出了完整的effect代码。其中使用了关键字technique11,指定使用一种DirectX 11 technique。
列表14.4 The BasicEffect.fx File
/************* Resources *************/
cbuffer CBufferPerObject
{
float4x4 WorldViewProjection : WORLDVIEWPROJECTION;
}
/************* Data Structures *************/
struct VS_INPUT
{
float4 ObjectPosition: POSITION;
float4 Color : COLOR;
};
struct VS_OUTPUT
{
float4 Position: SV_Position;
float4 Color : COLOR;
};
RasterizerState DisableCulling
{
CullMode = NONE;
};
/************* Vertex Shader *************/
VS_OUTPUT vertex_shader(VS_INPUT IN)
{
VS_OUTPUT OUT = (VS_OUTPUT)0;
OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
OUT.Color = IN.Color;
return OUT;
}
/************* Pixel Shader *************/
float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
return IN.Color;
}
/************* Techniques *************/
technique11 main11
{
pass p0
{
SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_5_0, pixel_shader()));
SetRasterizerState(DisableCulling);
}
}
在使用之前,需要把BasicEffect.fx文件存放到Game或者Library的工程目录中。并确保在工程中添加了正确的prebuild和post-build events,用于把content目录中的文件拷贝到应用程序的可执行文件目录中。第12章详细讲述了这些build events。
Creating an Effect Object
编译完effect文件之后,就可以使用返回的ID3DBlob对象创建一个ID3DX11Effect对象。该创建过程通过调用D3DX11CreateEffectFromMemory()函数执行,函数的原型和参数如下:
HRESULT D3DX11CreateEffectFromMemory(
LPCVOID pData,
SIZE_T DataLength,
UINT FXFlags,
ID3D11Device *pDevice,
ID3DX11Effect **ppEffect);
pData:编译effect文件返回的结果数据。
DataLength:结果数据的大小(单位为bytes)。
FXFlags:目前还没有使用effect标志。该参数始终设为0。
pDevice:Direct3D device。
ppEffect:返回创建成功的effect实例。
列表14.5显示一个调用D3DX11CreateEffectFromMemory()函数的示例。其中,在调用D3DX11CreateEffectFromMemory()函数之后立即释放了ID3DBlod类型的对象compiledShader。因为在effect对象创建之后,compilerShader就没用了,因此可以释放对该对象的引用。
列表14.5 Creating an Effect Object
hr = D3DX11CreateEffectFromMemory(compiledShader->GetBufferPointer(), compiledShader->GetBufferSize(), 0, mGame->Direct3DDevice(), &mEffect);
if (FAILED(hr))
{
throw GameException("D3DX11CreateEffectFromMemory() failed.", hr);
}
ReleaseObject(compiledShader);
Technique, Pass, and Variable Lookup
创建完一个effect对象之后,就可以使用该effect的technique,pass以及相应的variables。要得到这些对象,需要调用与之对应命名的函数:ID3DX11Effect::GetTechniqueByName(),ID3DX11Effect::GetPassByName()以及ID3DX11Effect::GetVariableByName()。这三个函数都只包含一个参数,描述要查找的变量对应的字符串;并返回对应的接口,如果指定标识符的变量没有找到,就返回NULL。ID3DX11EffectVariable接口中包含一组以“As”开头的函数,用于把该变量转换为一个接口更具体的对象。例如,ID3DX11EffectVariable::AsMatrix()函数就是试图把ID3DX11EffectVariable对象转换为ID3DX11EffectMatrixVariable类型的对象。在转换之后,必须对转换后的对象调用ID3DX11EffectVariable::IsValid()函数测试是否转换成功。
列表14.6演示了调用相关函数查找BasicEffect.fx文件中的main 11 technique,p0 pass以及WorldViewProjection变量的方法。同时,还包含了把WroldViewProjection变量转换为ID3DX11EffectMatrixVariable对象的函数调用示例。
列表14.6 Retrieving Techniques, Passes, and Variables
mTechnique = mEffect->GetTechniqueByName("main11");
if (mTechnique == nullptr)
{
throw GameException("ID3DX11Effect::GetTechniqueByName() could not find the specified technique.", hr);
}
mPass = mTechnique->GetPassByName("p0");
if (mPass == nullptr)
{
throw GameException("ID3DX11EffectTechnique::GetPassByName() could not find the specified pass.", hr);
}
ID3DX11EffectVariable* variable = mEffect->GetVariableByName("WorldViewProjection");
if (variable == nullptr)
{
throw GameException("ID3DX11Effect::GetVariableByName() could not find the specified variable.", hr);
}
mWvpVariable = variable->AsMatrix();
if (mWvpVariable->IsValid() == false)
{
throw GameException("Invalid effect variable cast.");
}
Creating an Input Layout
下面是BasicEffect.fx文件中的VS_INPUT结构体代码,以及TriangleDemo类中的BasicEffectVertex结构体代码。
struct VS_INPUT
{
float4 ObjectPosition : POSITION;
float4 Color : COLOR;
};
typedef struct _BasicEffectVertex
{
XMFLOAT4 Position;
XMFLOAT4 Color;
_BasicEffectVertex() { }
_BasicEffectVertex(XMFLOAT4 position, XMFLOAT4 color)
: Position(position), Color(color) { }
} BasicEffectVertex;
这两个结构都表示了同样的vertex数据(一个坐标值和一个颜色值),BasicEffectVertex结构是在C++/CPU中定义的,而VS_INPUT结构体则是在HLSL/GPUN中定义的。在Direct3D中需要使用一种input layout把CPU中的vertex data与GPU中vertex data一一对应。在创建一个input layout之前,需要先配置一个D3D11_INPUT_ELEMENT_DESC结构体类型的数组。该结构体定义如下:
typedef struct D3D11_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
SemanticName:与输入数据元素相关联的HLSL semantic(比如,POSITION,COLOR)
SemanticIndex:数据元素的semantic索引值(比如,索引值为1表示semantic为TEXCOORD1,索引值为2表示COLOR2)。除非是把同一个semantic指定给多个输入数据元素,否则不需要索引值。比如,semantic为POSITION等同于POSITION0。
Format:输入元素的数据类型。比如,DXGI_FORMAT_R32G32B32A32_FLOAT表示一种由4个32位浮点数组成的数据通道格式。
InputSlot:根据创建device时所使用的feature level,可以支持16或32个input slots,用于发送vertex data、这些slots包含多个vertex buffer,每一个vertex buffer对应于一个不同的slot。
AlignedByteOffset:两个相邻输入元素之间的偏移量(单位为bytes)。使用D3D11_APPEND_ALIGNED_ELEMENT表示输入元素自动对齐。
InputSlotClass:输入数据的种类,要么是针对每一个vertex,要么是每一个instance。第四部分“Intermediate-Level Rendering Topics”将会讨论hardware instancing。现在,直接使用D3D_INPUT_PER_VERTEX_DATA。
InstanceDataStepRate:该成员与per-instance种类的数据(InputSlotClass成员值为D3D_INPUT_PER_INSTANCE_DATA)同时使用。对于per-vertex种类的数据,该成员总是设为0.
有了D3D11_INPUT_ELEMENT_DESC(input element descriptions)类型的数组之后,就可以调用ID3D11Device::CreateInputLayout()函数,该函数的原型和参数如下:
HRESULT CreateInputLayout(
const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
UINT NumElements,
const void *pShaderBytecodeWithInputSignature,
SIZE_T BytecodeLength,
ID3D11InputLayout **ppInputLayout);
pInputElementDescs:input element descriptions类型的数组。
NumElements:pInputElementDescs数组中的元素个数。
pShaderBytecodeWithInputSignature:编译成功的shader(包含一个输入标记用于验证input element descriptions)。
BytecodeLength:shader的大小(单位为bytes)。
ppInputLayout:返回创建成功的input layout。
列表14.7演示了三角形示例中创建input layout的过程。
列表14.7 Input Layout Creation
D3DX11_PASS_DESC passDesc;
mPass->GetDesc(&passDesc);
D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
if (FAILED(hr = mGame->Direct3DDevice()->CreateInputLayout(inputElementDescriptions, ARRAYSIZE(inputElementDescriptions), passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &mInputLayout)))
{
throw GameException("ID3D11Device::CreateInputLayout() failed.", hr);
}
列表14.7中前面两行代码用于得到一个D3DX11_PASS_DESC结构体类型的对象,该结构体中包含了用于ID3D11Device::CreateInputLayout()函数中的input signature参数。之所以把input signature与一种pass关联起来,是因为一种pass指定了要使用的vertex shader,因此,也就指定了所要使用的shader inputs。接下来创建一个用于表示POSITION和COLOR输入元素的D3D11_INPUT_ELEMENT_DESC数组。最后调用CreateInputLayout()函数创建一个ID3D11InputLayout类型的对象。
Creating a Vertex Buffer
在TriangleDemo类中初始化的最后一步是创建一个vertex buffer。一个vertex buffer中存储了Direct3D图形管线处理的vertices,使用ID3D11Buffer接口类型表示。创建一个vertex buffer之前需要构建一个D3D11_BUFFER_DESC结构体,该结构体定义如下:
typedef struct D3D11_BUFFER_DESC
{
UINT ByteWidth;
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
UINT StructureByteStride;
} D3D11_BUFFER_DESC;
ByteWidth:buffer的大小。
Usage:设置buffer的分别针对CPU和GPU的读写权限。如果希望vertex buffer在创建之后就不再被修改,设置为D3D11_USAGE_IMMUTABLE。对于这种不可变的buffers,只有GPU可以访问(可读但不可写),CPU无法访问(既不能读也不能写)。
BindFlags:buffers的具体类型标志,对于vertex buffer,使用D3D11_BIND_VERTEX_BUFFER。
CPUAccessFlags:指定CPU对texture的读写权限,由相关的枚举常量值按位与运算得到。当CPU不需要访问时把该成员设为0。
MiscFlags:一些不常使用的buffer选项标志。
StructureByteStride:当该buffer是一个structured buffer时,其中每一个元素的大小。一个structured buffer中的元素大小都相同。在本书的所示示例中,该成员都设为0。
接下来构建一个D3D11_SUBRESOURCE_DATA结构对象用于设置vertex buffer的初始数据。对于不可变的buffers(Usage指定为D3D11_USAGE_IMMUTABLE),需要在创建buffer时就指定初始数据。对于其他的buffers,则不是必需的。D3D11_SUBRESOURCE_DATA结构体定义如下:
typedef struct D3D11_SUBRESOURCE_DATA
{
const void *pSysMem;
UINT SysMemPitch;
UINT SysMemSlicePitch;
} D3D11_SUBRESOURCE_DATA;
pSysMem:指向buffer初始数据的指针。
SysmemPitch:texture每一行的数据长度(单位为bytes)。该参数只用于2D和3D textures,与vertex buffers无关。
SysMemSlicePitch:从一个深度级别到下一个深度级别的距离(单位为bytes)。该参数只用于3D textures。
创建一个vertex buffer的最后一步是调用ID3D11Device::CrateBuffer()函数。
HRESULT CreateBuffer(
const D3D11_BUFFER_DESC *pDesc,
const D3D11_SUBRESOURCE_DATA *pInitialData,
ID3D11Buffer **ppBuffer);
pDesc:D3D11_BUFFER_DESC(buffer description)结构体。
pInitialData:提供给buffer的初始数据(根据不同情况,该数据是可选的)。
ppBuffer:返回创建完成的buffer。
列表14.8在三角形示例中创建vertex buffer的代码段。
列表14.8 Vertex Buffer Creation
BasicEffectVertex vertices[] =
{
BasicEffectVertex(XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Red))),
BasicEffectVertex(XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Green))),
BasicEffectVertex(XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Blue)))
};
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.");
}
在该代码中,第一段初始化了一个包括3个vertices的数组,这个3个vertices分别具有坐标(-1, 0, 0), (0, 1, 0), (1, 0, 0)以及对应的红,绿,蓝颜色值。使用构建一个D3D11_BUFFER_DESC结构体类型的对象,该对象包含不可变的vertex buffer。最后,创建一个vertex buffer并存储到成员变量mVertexBuffer中。