Rendering a Triangle

来源:互联网 发布:旋转矩阵大全 编辑:程序博客网 时间:2024/06/07 02:58

Rendering a Triangle

完成了上面的工作之后,现在可以准备绘制一个简单的三角形了。渲染一组nonindexed primitives(比如,一个三角形不需要带有index buffer)的步骤如下:
1. 设置input-assembler阶段所使用的primitive的topology顺序。
2. 把input layout绑定到input-assembler阶段。
3. 把vertex buffer绑定到input-assembler阶段。
4. 设置所有的shader constants(比如,WorldViewProjection)。
5. 把effect应用于创建的device中。
6. 调用nonindexed绘制函数ID3D11DeviceContext::Draw()进行绘制。
接下来详细讲解每一步操作。


Setting the Primitive Topology

渲染一个三角形的第一步是,对于输入的vertices,指定在input-assembler阶段要使用的topology排序方式。通过调用ID3D11DeviceContext::IASetPrimitiveTopology()函数,并指定一个D3D11_PRIMITIVE_TOPOLOGY类型的参数就可以完成设置,该函数没有返回值。表格4.1列出了最常用的一些topologied排序方式。

表格14.1 Common Primitive Topologies

Binding the Input Layout

下一步,调用ID3D11DeviceContext::IASetInputLayout()函数把input layout绑定到input-assembler阶段。该函数没有返回值,只带一个简单的输入参数:在前面已经创建的ID3D11InputLayout对象。

Binding the Vertex Buffer

接下来,必须调用ID3D11DeviceContext::IASetVertexBuffers()函数,把vertex buffer绑定到input-assembler阶段。该函数在CPU与GPU之间建立了vertex输入数据流,函数原型如下:
void IASetVertexBuffers(UINT StartSlot,UINT NumBuffers,ID3D11Buffer *const *ppVertexBuffers,const UINT *pStrides,const UINT *pOffsets);

StartSlot:用于一组vertex buffers中的第一个input slot。前面已经讨论过,最多有16或32个(根据device的feature level)inut slots用于绑定多个vertex buffers。
NumBuffers:ppVertexBuffers中的vertex buffers数量。
ppVertexBuffers:要绑定的一个vertex buffer数组。
pStrides:一个存储stride values的数组,每一个元素对应于一个vertex buffer。一个stride元素值表示vertex buffer中元素的大小(比如,在三角形示例中即为BasicEffectVertex结构体的大小)。
pOffsets:一个存储offsets值的数组,每一元素表示对应vertex buffer的偏移值。该偏移值用于计算在处理vertex之前需要跳过的bytes数量。主要用于在单个buffer包含多个objects的vertices时,只打算绘制完整vertex buffer中的某一部分。
在当前阶段,三角形示例并不需要使用多个vertex buffers或偏移值,ID3D11DeviceContext::IASetVertexBuffers()函数会让人觉得有一点冗长。但是该函数的设计目的是为了仅使用一个函数就可以支持更高级的使用。列表14.9中演示了调用以上函数设置primitive topology,绑定input layout,以及绑定用于三角形示例中的vertex buffer。
列表14.9 Setting the Primitive Topology and Binding the Input Layout and Vertex Buffer
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);

Setting Shader Constants and Applying the Effect Pass

在调用绘制函数Draw()之前,必须更新shaders中的相关变量(比如,WorldViewProjectiont矩阵)。Effects 11库的ID3DX11EffectPass和ID3DX11EffectVariable接口类型提供了这种功能。例如,ID3DX11EffectVariable::SetRawValue()函数支持把任意数据传递给一个shader变量。该函数的声明如下:
HRESULT SetRawValue(void *pData,UINT Offset,UINT ByteCount);

pData:要设置的数据
Offset:数据起始位置的偏移量(单位为bytes)。
ByteCount:数据的大小(bytes数量)。
调用SetRawValue()可以设置任意类型的变量。但是,使用派生的effect变量类型更容易理解,比如,ID3DX11EffectMatrixVariable接口类型包含一个SetMatirx()函数,该函数仅有一个输入参数:一个浮点型数组。无论使用哪一个接口,所有的“SetXXX”函数都只缓存数据,直到调用了ID3DX11EffectPass:Apply()才会把更新后的数据传递给GPU。这是一种减少性能的方法,因在绘制一个object时通常需要设置多个shader变量。通过缓存更新后的数据,在GPU中可以只进行一次批量修改而不是每调用一次“SetXXX”函数就修改一次。ID3D11EffectPass::Apply()函数包含有两个参数:一个用于表示flags(暂不使用)的无符号整形数,另一个是用于传递数据的ID3D11DeviceContext对象。列表14.10演示了更新WorldViewProjection变量,并把变量应用到effect pass中的方法。
列表14.10 Setting Shader Constants and Applying the Effect Pass
XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() * mCamera->ProjectionMatrix();mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));mPass->Apply(0, direct3DDeviceContext);

列表14.10中前两个语句用于把成员变量mWorldMatrix加载到一个SIMD XMMATRIX类型的对象中,然后通过矩阵乘法构造一个world view projection矩阵。其中,world矩阵必须被初始化为一个单位矩阵,或者包含了有效的平移,旋转以及缩放变换组合。

Executing the Draw Call

最后一步是对Direct3D device context执行nonindexed绘制操作。这是通过调用ID3D11DeviceContext::Draw()函数完成的,该函数的原型和参数如下:
VertexCount:要进行绘制的vertices的数量。
StartVertexLocation:要渲染的第一个vertex的索引。该参数提供了一个vertex buffer内部的偏移量,用于当vertex buffer中包含多个objects,但只渲染某一个object的情形。
列表14.11演示了三角形示例中调用Draw()函数的使用方法。其中,指定了绘制3个vertices,vertex buffer中没有偏移。
列表14.11 Executing the Nonindexed Draw Call
direct3DDeviceContext->Draw(3, 0);

Putting It All Together

现在,已经有了用于渲染一个三角形的所有片段的代码。列表14.12中列出了TriangDemo类的完全实现代码。本章讨论的所有步骤代码都包含在TriangleDemo::Initialize()函数和TriangleDemo::Draw()函数中。需要注意的是,ID3D11DeviceContext::IASetPrimitiveTopology(),IASetInputLayout(),以及IASetVertexBuffers()函数都是在TriangleDemo::Draw()函数(每一帧调用一次)中调用的,而不是在TriangleDemo::Initialize()函数中调用。之所以这么处理是为了强调组件不需要知道在每一帧之间Direct3D管线状态发生的变化。如果不在每一帧重置管线状态,当其他组件修改这些设置(IASet相关的三个函数)中的任何一个,就坏破坏TriangleDemo的渲染状态。
列表14.12 The TriangleDemo.cpp File
#include "TriangleDemo.h"#include "Game.h"#include "GameException.h"#include "MatrixHelper.h"#include "ColorHelper.h"#include "Camera.h"#include "Utility.h"#include "D3DCompiler.h"namespace Rendering{RTTI_DEFINITIONS(TriangleDemo)TriangleDemo::TriangleDemo(Game& game, Camera& camera): DrawableGameComponent(game, camera),  mEffect(nullptr), mTechnique(nullptr), mPass(nullptr), mWvpVariable(nullptr),  mInputLayout(nullptr), mWorldMatrix(MatrixHelper::Identity), mVertexBuffer(nullptr), mAngle(0.0f){}TriangleDemo::~TriangleDemo(){ReleaseObject(mWvpVariable);ReleaseObject(mPass);ReleaseObject(mTechnique);ReleaseObject(mEffect);ReleaseObject(mInputLayout);ReleaseObject(mVertexBuffer);}void TriangleDemo::Initialize(){SetCurrentDirectory(Utility::ExecutableDirectory().c_str());// Compile the shaderUINT shaderFlags = 0;#if defined( DEBUG ) || defined( _DEBUG )shaderFlags |= D3DCOMPILE_DEBUG;shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;#endifID3D10Blob* 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;}// Create an effect object from the compiled shaderhr = D3DX11CreateEffectFromMemory(compiledShader->GetBufferPointer(), compiledShader->GetBufferSize(), 0, mGame->Direct3DDevice(), &mEffect);if (FAILED(hr)){throw GameException("D3DX11CreateEffectFromMemory() failed.", hr);}ReleaseObject(compiledShader);// Look up the technique, pass, and WVP variable from the effectmTechnique = 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.");}// Create the input layoutD3DX11_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);}// Create the vertex bufferBasicEffectVertex 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.");}}void TriangleDemo::Update(const GameTime& gameTime){mAngle += XM_PI * static_cast<float>(gameTime.ElapsedGameTime());XMStoreFloat4x4(&mWorldMatrix, XMMatrixRotationZ(mAngle));}void TriangleDemo::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);XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() * mCamera->ProjectionMatrix();mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));mPass->Apply(0, direct3DDeviceContext);direct3DDeviceContext->Draw(3, 0);}}


图14.1中显示了TriangleDemo组件的输出结果。在该示例中,可以使用鼠标和键盘手动控制camera,并看到三角形的背面(因为在BasicEffect.fx文件中禁用了背面消除选项)。

图14.1 The output of the triangle demo.Spicing Things Up

Spicing Things Up

下面,在该示例中添加一些更有趣的功能,使用三角形绕着z轴旋转。在之前已经完成的框架基础上,实现这个功能是非常简单的。首先,在TriangleDemo类中增加一个命名为mAngle的成员变量,并在构造函数中初始化该变量值为0。然后在类的声明中增加一个成员函数Update(),用于覆盖虚函数DrawableGameComponent::Update(),并使用列表14.13中的代码作为该函数的实现代码。
列表14.13 The TriangleDemo::Update() Method
这段代码用于每一帧(以每秒180度的旋转速度)增加一次mAngle变量值。旋转的angle值用于构造一个围绕z轴旋转的矩阵,并存储到成员变量world matrix中。另外需要记住在
TriangleDemo::Draw()函数中把world矩阵与camera的view和projection矩阵相乘组合成一个world view project矩阵,并传递到GPU中。这就实现旋转功能的全部代码。
现在重新启动应用程序,就能看到一个旋转的三角形了。

0 0
原创粉丝点击