DirectX11入门笔记---创建几何体以及添加BaseObject类

image-20201217210042177

GeometryCreator工具类

为了后续更加方便地添加cube物体,需要一个工具类来生成模型数据,比如顶点属性和索引数组,这里我只需要生成Plane和Cube

#pragma once
#include <DirectXMath.h>
#include "ShaderStruct.h"
using namespace DirectX;

//用于程序化生成常用模型的GeometryCreator工具类
class GeometryCreator
{
public:    
    //创造Plane的顶点数组和索引数组
    static GeoData CreatePlane(float width, float height, UINT gridRow, UINT gridCol);

    //创建Cube的顶点数组和索引数组
    static GeoData CreateCube(float width, float height, float depth);
};

GeoData是一个保存了顶点属性和索引的结构体

//与HLSL对应的C++顶点属性
struct VertexAttribute
{
    XMFLOAT4 vertexPosition;
    XMFLOAT3 normal;
    XMFLOAT2 uv;
    XMFLOAT4 color;

};

//用于保存VertexAttribute数据和索引数据
struct GeoData
{
    std::vector<VertexAttribute> vertexs;
    std::vector<UINT> indexs;
};

Plane

首先我们要指定Plane的宽度和高度,行列数,从左上角一直嵌套for循环处理即可

1356268522_8860

处理索引的方法,一定要顺时针处理索引

1356269070_5100

GeoData GeometryCreator::CreatePlane(float width, float height, UINT gridRow, UINT gridCol)
{
    GeoData geoData;
    //首先获取行列顶点数
    UINT vertexRow_Num = gridRow + 1;
    UINT vertexCol_Num = gridCol + 1;

    //计算起点坐标
    float originPos_X = -width * 0.5f;
    float originPos_Z = height * 0.5f;

    //顶点之间距离
    float dx = width / gridCol;
    float dz = height / gridRow;
    //重新分配容器内存大小
    geoData.vertexs.resize(vertexRow_Num * vertexCol_Num);
    //填充geoData的VertexAttribute
    for (size_t i = 0; i < vertexRow_Num; i++)
    {
        float tempZ = originPos_Z - dz * i;
        for (size_t j = 0; j < vertexCol_Num; j++)
        {
            //计算当前顶点的索引
            UINT index = vertexRow_Num * i + j;
            geoData.vertexs[index].vertexPosition = XMFLOAT4(originPos_X + dx * j, 0.0f, 
                tempZ, 1.0f);
            geoData.vertexs[index].normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
            geoData.vertexs[index].uv = XMFLOAT2(i * (1.0f / gridCol), j * (1.0f / gridRow));
            geoData.vertexs[index].color = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);
        }
    }

    //填充索引,思路:将每个矩形沿对角线拆分成两个三角形
    //重新分配容器内存大小,总共有row*col个格子,一个格子里有6个索引
    geoData.indexs.resize(gridRow * gridCol * 6);

    UINT temp = 0;
    /*
        A___________B
        |            |
        |            |
        |            |
        C___________D

        A - B - C
        C - B - D
    */
    //进行填充
    for (size_t i = 0; i < gridRow; i++)
    {
        for (size_t j = 0; j < gridCol; j++)
        {
            geoData.indexs[temp] = i * vertexCol_Num + j;    //A
            geoData.indexs[temp + 1] = i * vertexCol_Num + j + 1;    //B
            geoData.indexs[temp + 2] = (i + 1) * vertexCol_Num + j;    //C
            geoData.indexs[temp + 3] = (i + 1) * vertexCol_Num + j;    //C
            geoData.indexs[temp + 4] = i * vertexCol_Num + j + 1;    //B
            geoData.indexs[temp + 5] = (i + 1) * vertexCol_Num + j + 1;    //D

            //进行下一个矩形
            temp += 6;
        }
    }

    return geoData;
}

Cube

Cube的话比较简单,主要区分好面的朝向即可,不要手抖写错,主要包含24个顶点,一个顶点要重复3次,因为一个顶点会有3个面相连接,所以有3个法向量。

GeoData GeometryCreator::CreateCube(float width, float height, float depth)
{
    // ******************
    // 设置立方体顶点
    //     ________ 
    //    /|      /|
    //   /_|_____/ |
    //   | |_ _  |_|
    //   | /     | /
    //   |/______|/
    //          


    //包含24个顶点,36个索引
    GeoData geoData;
    //重新分配内存大小
    geoData.vertexs.resize(24);
    float hw = width / 2.0f;
    float hh = height / 2.0f;
    float hd = depth / 2.0f;
    //上
    geoData.vertexs[0].vertexPosition = XMFLOAT4(-hw, hh, -hd, 1.0f);
    geoData.vertexs[1].vertexPosition = XMFLOAT4(-hw, hh, hd, 1.0f);
    geoData.vertexs[2].vertexPosition = XMFLOAT4(hw, hh, hd, 1.0f);
    geoData.vertexs[3].vertexPosition = XMFLOAT4(hw, hh, -hd, 1.0f);
    //下
    geoData.vertexs[4].vertexPosition = XMFLOAT4(-hw, -hh, hd, 1.0f);
    geoData.vertexs[5].vertexPosition = XMFLOAT4(-hw, -hh, -hd, 1.0f);
    geoData.vertexs[6].vertexPosition = XMFLOAT4(hw, -hh, -hd, 1.0f);
    geoData.vertexs[7].vertexPosition = XMFLOAT4(hw, -hh, hd, 1.0f);
    //左
    geoData.vertexs[8].vertexPosition = XMFLOAT4(-hw, -hh, hd, 1.0f);
    geoData.vertexs[9].vertexPosition = XMFLOAT4(-hw, hh, hd, 1.0f);
    geoData.vertexs[10].vertexPosition = XMFLOAT4(-hw, hh, -hd, 1.0f);
    geoData.vertexs[11].vertexPosition = XMFLOAT4(-hw, -hh, -hd, 1.0f);
    //右
    geoData.vertexs[12].vertexPosition = XMFLOAT4(hw, -hh, -hd, 1.0f);
    geoData.vertexs[13].vertexPosition = XMFLOAT4(hw, hh, -hd, 1.0f);
    geoData.vertexs[14].vertexPosition = XMFLOAT4(hw, hh, hd, 1.0f);
    geoData.vertexs[15].vertexPosition = XMFLOAT4(hw, -hh, hd, 1.0f);
    //前
    geoData.vertexs[16].vertexPosition = XMFLOAT4(-hw, -hh, -hd, 1.0f);
    geoData.vertexs[17].vertexPosition = XMFLOAT4(-hw, hh, -hd, 1.0f);
    geoData.vertexs[18].vertexPosition = XMFLOAT4(hw, hh, -hd, 1.0f);
    geoData.vertexs[19].vertexPosition = XMFLOAT4(hw, -hh, -hd, 1.0f);
    //后
    geoData.vertexs[20].vertexPosition = XMFLOAT4(hw, -hh, hd, 1.0f);
    geoData.vertexs[21].vertexPosition = XMFLOAT4(hw, hh, hd, 1.0f);
    geoData.vertexs[22].vertexPosition = XMFLOAT4(-hw, hh, hd, 1.0f);
    geoData.vertexs[23].vertexPosition = XMFLOAT4(-hw, -hh, hd, 1.0f);

    //填充法线和颜色
    for (size_t i = 0; i < 4; i++)
    {
        //上
        geoData.vertexs[i].normal = XMFLOAT3(0.0f,1.0f,0.0f);
        geoData.vertexs[i].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
        //下
        geoData.vertexs[i+4].normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
        geoData.vertexs[i+4].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
        //左
        geoData.vertexs[i+8].normal = XMFLOAT3(-1.0f, 0.0f, 0.0f);
        geoData.vertexs[i+8].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
        //右
        geoData.vertexs[i+12].normal = XMFLOAT3(1.0f, 0.0f, 0.0f);
        geoData.vertexs[i+12].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
        //前
        geoData.vertexs[i+16].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
        geoData.vertexs[i+16].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
        //后
        geoData.vertexs[i+20].normal = XMFLOAT3(0.0f, 0.0f, 1.0f);
        geoData.vertexs[i+20].color = XMFLOAT4(0.0f, 1.0f, 0.3f, 1.0f);
    }

    //计算UV
    for (size_t i = 0; i < 6; i++)
    {
        geoData.vertexs[i * 4].uv = XMFLOAT2(0.0f, 1.0f);
        geoData.vertexs[i * 4+1].uv = XMFLOAT2(0.0f, 0.0f);
        geoData.vertexs[i * 4+2].uv = XMFLOAT2(1.0f, 0.0f);
        geoData.vertexs[i * 4+3].uv = XMFLOAT2(1.0f, 1.0f);
    }

    //填充索引
    geoData.indexs.resize(36);
    geoData.indexs =
    {
        0, 1, 2, 2, 3, 0,        // 上
        4, 5, 6, 6, 7, 4,        // 下
        8, 9, 10, 10, 11, 8,    // 左
        12, 13, 14, 14, 15, 12,    // 右
        16, 17, 18, 18, 19, 16, // 前
        20, 21, 22, 22, 23, 20    // 后

    };

    return geoData;

细分常量缓冲区

我们之前的常量缓冲区为了方便把MVP矩阵都放在一个常量缓冲区里面,但是通常的情况是根据变量的修改的频繁程度来创建不同的常量缓冲,所以我们WVP缓冲区给细分为3个常量缓冲,这是因为每个物体的worldMatrix都会不一样,所以每绘制一个物体都要刷新一遍,然后摄像机的viewMatrix只要每帧刷新一遍即可,projMatrix则根据屏幕的大小有没有改变来刷新。

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

//新
cbuffer cbWorld : register(b0)
{
    matrix WorldMatrix;
}

cbuffer cbView : register(b1)
{
    matrix ViewMatrix;
}

cbuffer cbProj : register(b2)
{
    matrix ProjMatrix;
}

同时我们在OpdaGraphics::InitResource中创建相对应的常量缓冲区,并绑定到顶点着色器

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

    //直接新建常量缓冲区,无需初始化数据
    DirectxDevice->CreateBuffer(&constantBufferDesc, nullptr, constantBuffer[0].GetAddressOf());
    constantBufferDesc.ByteWidth = sizeof(cbView);
    DirectxDevice->CreateBuffer(&constantBufferDesc, nullptr, constantBuffer[1].GetAddressOf());
    constantBufferDesc.ByteWidth = sizeof(cbProj);
    DirectxDevice->CreateBuffer(&constantBufferDesc, nullptr, constantBuffer[2].GetAddressOf());

    //用于更新常量缓冲区
    cbWorld cbW;
    cbW.WorldMatrix = XMMatrixIdentity();//单位矩阵
    //创建view变换矩阵
    cbView cbV;
    XMVECTOR pos = XMVectorSet(0.0f, 1.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);
    cbV.ViewMatrix = XMMatrixTranspose( XMMatrixLookAtLH(pos, target, up));

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

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

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

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

    //将常量缓冲区绑定到渲染管线
    DirectxDeviceContext->VSSetConstantBuffers(0, 1, constantBuffer[0].GetAddressOf());
    DirectxDeviceContext->VSSetConstantBuffers(1, 1, constantBuffer[1].GetAddressOf());
    DirectxDeviceContext->VSSetConstantBuffers(2, 1, constantBuffer[2].GetAddressOf());

BaseObject类

此类是为了更加方便地管理场景中的物体,没必要把代码全塞在OpdaGraphics里面,把物体创建绑定顶点缓冲区和索引缓冲区的操作抽象出来构造BaseObject类。

BaseObject的任务主要有两个:

1.利用GeoData数据创建顶点索引缓冲,进行初始化

2.将顶点索引缓冲区绑定到渲染管线,更新常量缓冲区的数据,进行物体的绘制

class BaseObject
{
public:
    BaseObject();
    //重新设置顶点缓冲区和索引缓冲区
    void CreateVertexIndexBuffer(ID3D11Device* directxdevice, const GeoData& geoData);
    //进行渲染
    void Render(ID3D11DeviceContext* directxDeviceContext);

public:
    cbWorld cbW;

private:
    //顶点缓冲区
    ComPtr<ID3D11Buffer> vertexBuffer;
    //索引缓冲区
    ComPtr<ID3D11Buffer> indexBuffer;
    //索引总数
    UINT totalIndex;
};INT totalIndex;
};

XXGetConstantBuffers—-获得某一着色阶段的常量缓冲区

利用这个方法我们就可以获取顶点着色器绑定的指定常量缓冲区

void ID3D11DeviceContext::VSGetConstantBuffers( 
    UINT StartSlot,     // [In]指定的起始槽索引
    UINT NumBuffers,    // [In]常量缓冲区数目 
    ID3D11Buffer **ppConstantBuffers) = 0;    // [Out]常量固定缓冲区数组
#include "BaseObject.h"

BaseObject::BaseObject()
{
    cbW.WorldMatrix = XMMatrixIdentity();
    totalIndex = 0;
}

void BaseObject::CreateVertexIndexBuffer(ID3D11Device* directxdevice, const GeoData& geoData)
{
    //释放资源
    vertexBuffer.Reset();
    indexBuffer.Reset();


    //创建顶点缓冲区描述
    D3D11_BUFFER_DESC vertexBufferDesc;
    //初始化内存块
    ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
    //因为后续顶点不在改变
    vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    //内存块大小
    vertexBufferDesc.ByteWidth = geoData.vertexs.size() * sizeof(VertexAttribute);
    vertexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    //CPU访问权限
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    //初始化数据
    D3D11_SUBRESOURCE_DATA InitData;
    //初始化内存块
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = geoData.vertexs.data();

    //创建顶点缓冲区
    directxdevice->CreateBuffer(&vertexBufferDesc, &InitData, vertexBuffer.GetAddressOf());

    //获取索引总数
    totalIndex = (UINT)geoData.indexs.size();
    //创建索引缓冲区描述
    D3D11_BUFFER_DESC indexBufferDesc;
    //初始化内存块
    ZeroMemory(&indexBufferDesc, sizeof(indexBufferDesc));
    //后续索引不再改变
    indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    //内存块大小
    indexBufferDesc.ByteWidth = geoData.indexs.size() * sizeof(UINT);
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    //CPU访问权限
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
    indexBufferDesc.StructureByteStride = 0;

    InitData.pSysMem = geoData.indexs.data();
    //创建索引缓冲区
    directxdevice->CreateBuffer(&indexBufferDesc, &InitData, indexBuffer.GetAddressOf());
}

void BaseObject::Render(ID3D11DeviceContext* directxDeviceContext)
{
    //设置顶点索引缓冲区
    UINT pStride = sizeof(VertexAttribute);
    UINT offset = 0;
    //起始插槽    缓冲区数量    缓冲区元素字节大小    字节偏移量
    directxDeviceContext->IASetVertexBuffers(0, 1, vertexBuffer.GetAddressOf(), &pStride, &offset);
    directxDeviceContext->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

    //获取绑定到渲染管线上的常量缓冲区
    ComPtr<ID3D11Buffer> constantBuffer;
    directxDeviceContext->VSGetConstantBuffers(0,1,constantBuffer.GetAddressOf());


    //更新常量缓冲区
    D3D11_MAPPED_SUBRESOURCE mappedData;
    directxDeviceContext->Map(constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData);
    memcpy_s(mappedData.pData, sizeof(cbWorld), &cbW, sizeof(cbWorld));
    directxDeviceContext->Unmap(constantBuffer.Get(), 0);

    //可以绘制了
    directxDeviceContext->DrawIndexed(totalIndex, 0, 0);
}

如何使用

首先我们需要在OpdaGraphics::InitResource中进行顶点索引缓冲区的创建

plane.CreateVertexIndexBuffer(DirectxDevice, GeometryCreator::CreatePlane(10.0f, 10.0f, 1, 1));
    cube.CreateVertexIndexBuffer(DirectxDevice, GeometryCreator::CreateCube(2.0f, 2.0f, 2.0f));

OpdaGraphics::UpdateScene中更新物体的worldMatrix

cube.cbW.WorldMatrix = XMMatrixTranspose(XMMatrixRotationX(RotateX)* XMMatrixRotationY(RotateY));
    plane.cbW.WorldMatrix = XMMatrixTranspose(XMMatrixRotationX(RotateX) * XMMatrixRotationY(RotateY));

最后在OpdaGraphics::RenderScene中调用物体的Render函数

cube.Render(DirectxDeviceContext);
plane.Render(DirectxDeviceContext);
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 Opda
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信