DirectX11 With Windows SDK--03 渲染一个立方体

DirectX11 With Windows SDK完整目录:https://blog.csdn.net/x_jun96/article/details/80293670

一个立方体有8个顶点,然而绘制一个立方体需要画12个三角形,如果按照前面的方法绘制的话,则需要提供36个顶点,而且这里面的顶点数据会重复4次甚至5次。这样的绘制方法会占用大量的内存空间。

接下来会讲另外一种绘制方法,可以只提供立方体的8个顶点数据,然后用一个索引数组来指代使用哪些顶点,按怎样的顺序绘制。

项目源码点此:https://github.com/MKXJun/DirectX11-With-Windows-SDK

目录

索引缓冲区

使用索引缓冲区进行替代指定顺序绘制,可以有效减少顶点缓冲区的占用空间,避免提供大量重复的顶点数据。

在使用索引缓冲区前,先讲初始化顶点数组,如下:

// ******************
// 设置立方体顶点
//    5________ 6
//    /|      /|
//   /_|_____/ |
//  1|4|_ _ 2|_|7
//   | /     | /
//   |/______|/
//  0       3
VertexPosColor vertices[] =
{
    { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
    { XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
    { XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
    { XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
    { XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
    { XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
    { XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};

然后索引缓冲区的创建和使用和之前一样:

// 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_DEFAULT;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.GetAddressOf()));

// 输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosColor);   // 跨越字节数
UINT offset = 0;                        // 起始偏移量

md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);

现在索引数组的初始化如下:

// 索引数组
WORD indices[] = {
    // 正面
    0, 1, 2,
    2, 3, 0,
    // 左面
    4, 5, 1,
    1, 0, 4,
    // 顶面
    1, 5, 6,
    6, 2, 1,
    // 背面
    7, 6, 5,
    5, 4, 7,
    // 右面
    3, 2, 6,
    6, 7, 3,
    // 底面
    4, 0, 3,
    3, 7, 4
};

然后填充缓冲区描述信息并创建索引缓冲区:

// 设置索引缓冲区描述
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_DEFAULT;
ibd.ByteWidth = sizeof indices;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = indices;
HR(md3dDevice->CreateBuffer(&ibd, &InitData, mIndexBuffer.GetAddressOf()));

ID3D11DeviceContext::IASetIndexBuffer方法–渲染管线输入装配阶段设置索引缓冲区

void ID3D11DeviceContext::IASetIndexBuffer( 
    ID3D11Buffer *pIndexBuffer,     // [In]索引缓冲区
    DXGI_FORMAT Format,             // [In]数据格式
    UINT Offset);                   // [In]字节偏移量

于是我们可以这样:

// 输入装配阶段的索引缓冲区设置
md3dImmediateContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);

使用16位的索引有利于节省空间,现阶段绝大多数模型的顶点数目都少于65535个。

常量缓冲区

在HLSL中,常量缓冲区的变量类似于C++这边的全局变量,供着色器代码使用。下面是一个HLSL常量缓冲区示例:

cbuffer ConstantBuffer : register(b0)
{
    row_major matrix World;
    row_major matrix View;  
    row_major matrix Proj;  
}

cbuffer 用于声明一个常量缓冲区

row_major 指定矩阵内数据按行主形式排布,否则默认为列主形式。D3D的矩阵默认为行主形式,如果这里不指定,会导致从D3D传矩阵给HLSL时会发生一次转置。

matrix 等价于 float4x4,同样有vector等价于float4

register(b0) 指的是该常量缓冲区位于寄存器索引为0的缓冲区

而在C++应用层,常量缓冲区的对应结构体可以为:

struct ConstantBuffer
{
    XMMATRIX world;
    XMMATRIX view;
    XMMATRIX proj;
};

下面演示了如何创建一个常量缓冲区:

ComPtr<ID3D11Buffer> mConstantBuffer = nullptr;

D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DEFAULT;
cbd.ByteWidth = sizeof(ConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = 0;
// 新建常量缓冲区,不使用初始数据
md3dDevice->CreateBuffer(&cbd, nullptr, mConstantBuffer.GetAddressOf());

注意:在创建常量缓冲区时,描述参数ByteWidth必须为16的倍数,因为HLSL的常量缓冲区本身以及对它的读写操作需要严格按16字节对齐

ID3D11DeviceContext::UpdateSubresource方法–更新渲染管线的子资源

void ID3D11DeviceContext::UpdateSubresource( 
    ID3D11Resource *pDstResource,   // [In]需要更新的缓冲区
    UINT DstSubresource,            // [In]忽略
    const D3D11_BOX *pDstBox,       // [In]忽略
    const void *pSrcData,           // [In]用于更新的数据源
    UINT SrcRowPitch,               // [In]忽略
    UINT SrcDepthPitch);            // [In]忽略

该方法仅可以用于以D3D11_USAGE_DEFAULTD3D11_USAGE_STAGE方式创建并绑定在渲染管线上的资源,并且不能用于深度模板缓冲区和支持多采样的缓冲区。

注意:如果用于更新着色器的常量缓冲区,不能对其中的数据部分更新,必须完整地进行数据的更新。

现在需要利用mBuffer结构体变量用于更新常量缓冲区:

ConstantBuffer mCBuffer;
mCBuffer.world = XMMatrixIdentity();            
mCBuffer.view = XMMatrixLookAtLH(
    XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
    XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
    XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
);
mCBuffer.proj = XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f);

md3dImmediateContext->UpdateSubresource(mConstantBuffer.Get(), 0, nullptr, &mCBuffer, 0, 0);

ID3D11DeviceContext::VSSetConstantBuffers方法–渲染管线顶点着色阶段设置常量缓冲区

更新了数据后,还需要给顶点着色阶段设置常量缓冲区供使用。

void ID3D11DeviceContext::VSSetConstantBuffers( 
    UINT StartSlot,     // [In]放入缓冲区的起始索引,例如上面指定了b0,则这里应为0
    UINT NumBuffers,    // [In]设置的缓冲区数目
    ID3D11Buffer *const *ppConstantBuffers);    // [In]用于设置的缓冲区数组

在每一帧更新或者绘制的时候就可以这样调用:

md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffer.GetAddressOf());

HLSL代码

该例程所用的HLSL代码如下:

//cube.fx

cbuffer ConstantBuffer : register(b0)
{
    row_major matrix World; 
    row_major matrix View;  
    row_major matrix Proj;  
}


struct VertexIn
{
    float3 pos : POSITION;
    float4 color : COLOR;
};

struct VertexOut
{
    float4 posH : SV_POSITION;
    float4 color : COLOR;
};

VertexOut VS(VertexIn pIn)
{
    VertexOut pOut;
    pOut.posH = mul(float4(pIn.pos, 1.0f), World); 
    pOut.posH = mul(pOut.posH, View);                   
    pOut.posH = mul(pOut.posH, Proj);                   
    pOut.color = pIn.color;       
    return pOut;
}



float4 PS(VertexOut pIn) : SV_Target
{
    return pIn.color;   
}

注意:在HLSL中,矩阵乘法不能用*运算符,该运算符要求两个矩阵行列数相同,运算的结果也是一个同行列数的矩阵,运算过程为:Cij = Aij * Bij。应该使用mul函数进行替代。

然后新建Cube_VS.hlsl和Cube_PS.hlsl,包含上面的Cube.fx,再按之前提到的内容来设置编译信息。

GameApp::UpdateScene方法–逐帧更新数据

绘制之前我们得让立方体转起来,不然就只能看到立方体的正面。在这里我们让魔方同时绕X轴和Y轴旋转,修改世界矩阵即可:

void GameApp::UpdateScene(float dt)
{
    // 更新常量缓冲区,让立方体转起来
    static float phi = 0.0f, theta = 0.0f;
    phi += 0.00003f, theta += 0.00005f;
    mCBuffer.world = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
    md3dImmediateContext->UpdateSubresource(mConstantBuffer.Get(), 0, nullptr, &mCBuffer, 0, 0);
}

GameApp::DrawScene方法

ID3D11DeviceContext::DrawIndexed方法–根据顶点和索引缓冲区进行绘制

在输入装配阶段指定好了顶点缓冲区、索引缓冲区和原始拓补类型后,再绑定常量缓冲区到顶点着色阶段,最后就可以使用ID3D11DeviceContext::DrawIndexed方法来绘制:

void ID3D11DeviceContext::DrawIndexed( 
    UINT IndexCount,            // 索引数目
    UINT StartIndexLocation,    // 起始索引位置
    INT BaseVertexLocation);    // 起始顶点位置

现在我们要绘制12个三角形,构成立方体。GameApp::DrawScene方法实现如下:

void GameApp::DrawScene()
{
    assert(md3dImmediateContext);
    assert(mSwapChain);

    static float blue[4] = { 0.0f, 0.0f, 0.0f, 1.0f };  // RGBA = (0,0,0,255)
    md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&blue));
    md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    // 将更新好的常量缓冲区绑定到顶点着色器
    md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffer.GetAddressOf());

    // 绘制立方体
    md3dImmediateContext->DrawIndexed(36, 0, 0);
    HR(mSwapChain->Present(0, 0));
}

效果如下:
image

下一篇:

DirectX11 With Windows SDK–04 使用DirectX Tool Kit帮助开发:https://blog.csdn.net/x_jun96/article/details/80303408

该文章后续视情况可能还会有所修改。

猜你喜欢

转载自blog.csdn.net/x_jun96/article/details/80301143