DirectX11入门笔记---绘制立方体

image-20201217210042177

效果展示

GIF 2020-12-22 16-19-43

绘制立方体

首先我们初始化顶点数据

    // ******************
    // 设置立方体顶点
    //    5________ 6
    //    /|      /|
    //   /_|_____/ |
    //  1|4|_ _ 2|_|7
    //   | /     | /
    //   |/______|/
    //  0       3

    //设置立方体顶点,顶点应按顺时针排布
    VertexPosColor vertices[] =
    {
        { XMFLOAT4(-1.0f, -1.0f, -1.0f,1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
        { XMFLOAT4(-1.0f, 1.0f, -1.0f,1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT4(1.0f, 1.0f, -1.0f,1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT4(1.0f, -1.0f, -1.0f,1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT4(-1.0f, -1.0f, 1.0f,1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
        { XMFLOAT4(-1.0f, 1.0f, 1.0f,1.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
        { XMFLOAT4(1.0f, 1.0f, 1.0f,1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT4(1.0f, -1.0f, 1.0f,1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
    };

索引缓冲区

索引缓冲区的作用是用来避免复制大量重复的顶点数据,毕竟占用的存储空间会非常大,如果复制的是索引那占用空间没什么所谓了

初始化索引数组

    //初始化索引数组
    DWORD indexs[] =
    {
        //正面
        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 indexBufferDesc;
    //将指定内存区域都设置为0
    ZeroMemory(&indexBufferDesc, sizeof(indexBufferDesc));
    //目前索引缓冲区在创建后通常不会修改,所以选择D3D11_USAGE_IMMUTABLE
    indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    indexBufferDesc.ByteWidth = sizeof(indexs);
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;

    //同样需要指定初始化数据
    InitData.pSysMem = indexs;
    //创建索引缓冲区
    DirectxDevice->CreateBuffer(&indexBufferDesc, &InitData, indexBuffer.GetAddressOf());

ID3D11DeviceContext::IASetIndexBuffer与渲染管线进行绑定

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

Format:指定每个索引所占的字节

DXGI_FORMAT 字节数 索引范围
DXGI_FORMAT_R8_UINT 1 0-255
DXGI_FORMAT_R16_UINT 2 0-65535
DXGI_FORMAT_R32_UINT 4 0-2147483647
    //将索引缓冲区绑定到渲染管线
    DirectxDeviceContext->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

常量缓冲区

常量缓冲区相当于C++的全局变量,供着色器使用,首先我们要在hlsl文件中定义常量缓冲区

//常量缓冲区
cbuffer ConstantBuffer : register(b0)
{
    matrix WorldMatrix;
    matrix ViewMatrix;
    matrix ProjMatrix;
}

定义了一个ConstantBuffer的cbuffer对象,cbuffer就是常量缓冲,里面存储了各自独立的MVP矩阵

matrix相当于float4x4,directx中矩阵默认是行主矩阵,但HLSL矩阵是列主矩阵,这个要进行转置,否则会出错

register指该常量缓冲区处于寄存器索引为0的缓冲区

我们在C++中也应该有相对应的数据结构

//常量缓冲区对应的数据结构
struct ConstantBufferStruct
{
    XMMATRIX worldMatrix;
    XMMATRIX viewMatrix;
    XMMATRIX projMatrix;
};

更新方式

常量缓冲区有两种运行时更新方式:

  1. 缓冲区描述Usage指定为D3D11_USAGE_DEFAULT,可以允许常量缓冲区从GPU写入,需要用ID3D11DeviceContext::UpdateSubresource方法更新,这种方法适合更新不频繁的数据
  2. 在创建资源的时候指定UsageD3D11_USAGE_DYNAMICCPUAccessFlagsD3D11_CPU_ACCESS_WRITE,允许常量缓冲区从CPU写入,首先通过ID3D11DeviceContext::Map方法获取内存映射,然后再更新到映射好的内存区域,最后通过ID3D11DeviceContext::Unmap方法解除占用,这种方法适合频繁更新的数据

ID3D11DeviceContext::Map—获取指向缓冲区中数据的指针并拒绝GPU对该缓冲区的访问

HRESULT ID3D11DeviceContext::Map(
    ID3D11Resource           *pResource,          // [In]包含ID3D11Resource接口的资源对象
    UINT                     Subresource,         // [In]包含在资源中的子资源的索引,由于缓冲区不包含子资源,所以设置为0
    D3D11_MAP                MapType,             // [In]D3D11_MAP枚举值,指定读写相关操作
    UINT                     MapFlags,            // [In]填0,CPU需要等待GPU使用完毕当前缓冲区
    D3D11_MAPPED_SUBRESOURCE *pMappedResource     // [Out]获取到的已经映射到缓冲区的内存
);

image-20201222155452623

获取到缓冲区内存之后,我们使用memcpy_s将我们计算好的矩阵数据复制到pMappedResource.pData里去。

ID3D11DeviceContext::Unmap函数—让指向资源的指针无效并重新启用GPU对该资源的访问权限

当你完成缓冲区的更新操作之后,必须调用 ID3D11Buffer::Unmap 函数。

void ID3D11DeviceContext::Unmap(
    ID3D11Resource *pResource,      // [In]包含ID3D11Resource接口的资源对象
    UINT           Subresource      // [In]缓冲区资源填0
);

创建常量缓冲区

同样的操作,只不过不需要初始化数据

    //设置常量缓冲区描述
    D3D11_BUFFER_DESC constantBufferDesc;
    ZeroMemory(&constantBufferDesc, sizeof(constantBufferDesc));
    //由于常量缓冲区大多数需要频繁更新,所以需要设置为
    constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    constantBufferDesc.ByteWidth = sizeof(ConstantBufferStruct);
    constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    //需要CPU写入矩阵数据
    constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    //直接新建常量缓冲区,无需初始化数据
    DirectxDevice->CreateBuffer(&constantBufferDesc, nullptr, constantBuffer.GetAddressOf());

计算矩阵并且更新常量缓冲区

//用于更新常量缓冲区
    cbStruct.worldMatrix = XMMatrixIdentity();//单位矩阵
    //创建view变换矩阵
    XMVECTOR pos = XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f);
    XMVECTOR target = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
    XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
    cbStruct.viewMatrix = XMMatrixTranspose( XMMatrixLookAtLH(pos, target, up));

    //创建Proj变换矩阵
    cbStruct.projMatrix = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, 640.0f / 480.0f, 1.0f, 1000.0f));

    //用于存储map函数获取到缓冲区的内存
    D3D11_MAPPED_SUBRESOURCE mappedData;
    DirectxDeviceContext->Map(constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData);
    //安全内存复制,将cbstruct复制到pData
    memcpy_s(mappedData.pData, sizeof(cbStruct), &cbStruct, sizeof(cbStruct));
    //关闭常量缓冲区的访问权限
    DirectxDeviceContext->Unmap(constantBuffer.Get(), 0);

绑定至渲染管线顶点着色阶段

ID3D11DeviceContext::*SSetConstantBuffers—渲染管线某一着色阶段设置常量缓冲区

void ID3D11DeviceContext::VSSetConstantBuffers( 
    UINT StartSlot,     // [In]放入缓冲区的起始索引,例如上面指定了b0,则这里应为0
    UINT NumBuffers,    // [In]设置的缓冲区数目
    ID3D11Buffer *const *ppConstantBuffers);    // [In]用于设置的缓冲区数组
    //将常量缓冲区绑定到渲染管线
    DirectxDeviceContext->VSSetConstantBuffers(0, 1, constantBuffer.GetAddressOf());

UpdateScene旋转立方体

因为要旋转立方体,所以需要每帧更新WorldMatrix,所以要在Update中进行操作

void OpdaGraphics::UpateScene(float deltaTime)
{
    static float rotateY = 0.0f;
    rotateY += 0.00015f;
    cbStruct.worldMatrix = XMMatrixTranspose(XMMatrixRotationY(rotateY));
    // 更新常量缓冲区,让立方体转起来
    D3D11_MAPPED_SUBRESOURCE mappedData;
    DirectxDeviceContext->Map(constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData);
    memcpy_s(mappedData.pData, sizeof(cbStruct), &cbStruct, sizeof(cbStruct));
    DirectxDeviceContext->Unmap(constantBuffer.Get(), 0);
}

最后在DrawScene进行修改

void OpdaGraphics::RenderScene()
{
    XMVECTORF32 color = { 0.0f,0.0f,0.0f,1.0f };
    DirectxDeviceContext->ClearRenderTargetView(renderTargetView, reinterpret_cast<float*>(&color));
    DirectxDeviceContext->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    //绘制三角形
    //DirectxDeviceContext->Draw(3, 0);
    //绘制立方体
    DirectxDeviceContext->DrawIndexed(36, 0, 0);
    dxgiSwapChain->Present(0, 0);
}
void ID3D11DeviceContext::DrawIndexed( 
    UINT IndexCount,            // 索引数目
    UINT StartIndexLocation,    // 起始索引位置
    INT BaseVertexLocation);    // 起始顶点位置

HLSL文件


//常量缓冲区
cbuffer ConstantBuffer : register(b0)
{
    matrix WorldMatrix;
    matrix ViewMatrix;
    matrix ProjMatrix;
}


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

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

//顶点着色器
VertexOut VS(VertexIn vIn)
{
    VertexOut vOut;
    //变换到裁剪空间,MVP矩阵
    vOut.pos = mul(mul(mul(vIn.pos, WorldMatrix), ViewMatrix),ProjMatrix);
    vOut.color = vIn.color;
    return vOut;
}

//像素着色器
float4 PS(VertexOut pIn):SV_Target
{
    return pIn.color;
}
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 Opda
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信