Mecanim 发表于 2022-12-5 08:20

DX12游戏开发实战习题 第七章

习题的仓库链接如下:
oursuccess/D3D12 (github.com)
下面是每道习题的思路解答:
1. 以CreateGeoSphere方法替代例程中的原始CreateSphere方法

思路:
简单按照题目要求进行修改即可
代码:
//在BuildShapesGeometry方法中
#pramga region Quiz0701
      //GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 20, 20);
      GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 1); //第二个参数可以替换为其他值
#pragma endregion
2. 以根常量代替描述符表来设置世界矩阵

思路:
将根描述符表直接替换为根常量即可。 需要注意,由于每个根常量均为1DWORD,因此对于4X4的世界矩阵,我们需要声明一个大小为16的数组。
代码:
//直接移除掉UpdateObjectCBs方法的声明、定义和调用。 不再给出示例(定义中关于world的更新可以保留,我们会将其转移到DrawRenderItems中)

//因为我们不再使用单个物体的常量缓冲区,因此,PassCB现在不再需要偏移。我们可以修改PassCB的Offset,也可以如下简单注释一行:
//在BuildDDescriptorHeaps方法中
#pragma region Quiz0702
      //由于现在只有逐帧的常量缓冲区,因此passCB现在不再需要偏移了。mPassCbvOffset默认为0
      //mPassCbvOffset = objCount * gNumFrameResources;
#pragma endregion

//因为不再需要单个物体的常量缓冲区,因此我们可以在BuildConstantBufferViews方法中注释掉如下代码:
#pragma region Quiz0702
      //由于世界矩阵不再通过描述符表设置,因此单物体的常量缓冲区也不再需要了
      /*
      //计算该常量缓冲区的大小
      UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

      //计算物体总数
      UINT objCount = (UINT)mOpaqueRitems.size();

      //每个物体在每个帧资源中都需要一个常量缓冲区
      for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
      {
                auto objectCB = mFrameResources->ObjectCB->Resource();
                for (UINT i = 0; i < objCount; ++i)
                {
                        //获取GPU的虚拟地址
                        D3D12_GPU_VIRTUAL_ADDRESS cbAddress = objectCB->GetGPUVirtualAddress();
                        cbAddress += i * objCBByteSize;

                        //不同的渲染对象理论上来说需要不同的地址偏移
                        int heapIndex = frameIndex * objCount + i;
                        auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
                        handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);

                        //创建常量缓冲区描述符,并指定其大小和位置
                        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
                        cbvDesc.BufferLocation = cbAddress;
                        cbvDesc.SizeInBytes = objCBByteSize;

                        //根据常量缓冲区描述符创建实际的常量缓冲区
                        md3dDevice->CreateConstantBufferView(&cbvDesc, handle);

                }
      }
      */
#pragma endregion

//在创建根签名时,我们将第一个描述符表修改为根常量
//BuildRootSignature方法中
//创建根签名
      //创建一个有一个元素的根参数
      CD3DX12_ROOT_PARAMETER slotRootParameter;

#pragma region Quiz0702
      //我们使用根常量代替了描述符表
      /*
      //创建一个有1个元素的描述符表
      CD3DX12_DESCRIPTOR_RANGE cbvTable0;
      cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
      //使用该描述符表初始化根参数
      slotRootParameter.InitAsDescriptorTable(1, &cbvTable0);
      */
      //创建一个有16个常量的根常量
      slotRootParameter.InitAsConstants(16, 0);
#pragma endregion

//最后,我们修改DrawRenderItems中的方法,现在不再需要设置描述符表的偏移,而是将常量直接绑定到根签名
#pragma region Quiz0702
                //因为物体的世界矩阵并不是通过常量缓冲区传入的,因此我们不再需要这里的偏移了
                /*
                //calc offset
                UINT cbvIndex = mCurrFrameResourceIndex * (UINT)mOpaqueRitems.size() + ri->ObjCBIndex;
                auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
                cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);

                cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);
                */

                //取而代之的是,我们直接将缓冲区与根常量绑定
                XMMATRIX world = XMLoadFloat4x4(&ri->World);
                ObjectConstants objConstants;
                XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));
                cmdList->SetGraphicsRoot32BitConstants(0, 16, &objConstants, 0);
#pragma endregion3. 导入骷髅头

思路:
在mAllRitems中添加一个骷髅头的数据即可。我们需要自己读入一下骷髅头的数据。
代码:
//添加一个BuildSkullGeometry方法,并在Initialize时调用
      void BuildQuiz03Geometry();
#pragma region Quiz0703
      //添加一个添加骷髅头的方法
      void BuildSkullGeometry();
#pragma endregion

//BuildSkullGeometry的实现:
#pragma region Quiz0703
//添加一下骷髅头 里面的实现和顶点数据的构建是相同的
void Quiz03::BuildSkullGeometry()
{
      std::ifstream fin("Models/skull.txt");
      if (!fin)
      {
                MessageBox(0, L"Models/skull.txt not found!", 0, 0);
                return;
      }
      //读取顶点数量和三角形数量
      UINT vcount = 0, tcount = 0;
      std::string ignore;      //我们将无需的内容读到ignore中
      fin >> ignore >> vcount;      //fin会逐单词读取。在这里忽略了VertexCount:,将31076读入了vcount
      fin >> ignore >> tcount;
      fin >> ignore >> ignore >> ignore >> ignore; //将VetexList (pos, normal)与下一行的{ 忽略

      //这里的Vertex添加了Normal数据
      std::vector<Vertex> vertices(vcount);
      for (UINT i = 0; i < vcount; ++i)
      {
                fin >> vertices.Pos.x >> vertices.Pos.y >> vertices.Pos.z;
                fin >> vertices.Normal.x >> vertices.Normal.y >> vertices.Normal.z;
      }

      //准备读取indices 同样要跳过无用的数据
      fin >> ignore >> ignore >> ignore;
      std::vector<std::int32_t> indices(3 * tcount);
      for (UINT i = 0; i < tcount; ++i)
      {
                //一个三角形有三个顶点
                fin >> indices >> indices >> indices;
      }
      //读取完毕 关闭文件
      fin.close();

      //将indices和vertices打包到常量缓冲区
      const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
      const UINT ibByteSize = (UINT)indices.size() * sizeof(std::int32_t);

      auto geo = std::make_unique<MeshGeometry>();
      geo->Name = "skullGeo";

      ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
      CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
      ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
      CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
      
      geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
      geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);

      geo->VertexByteStride = sizeof(Vertex);
      geo->VertexBufferByteSize = vbByteSize;
      geo->IndexFormat = DXGI_FORMAT_R32_UINT;
      geo->IndexBufferByteSize = ibByteSize;

      SubmeshGeometry skullMesh;
      skullMesh.IndexCount = (UINT)indices.size();
      skullMesh.StartIndexLocation = 0;
      skullMesh.BaseVertexLocation = 0;

      geo->DrawArgs["skull"] = skullMesh;
      mGeometries = std::move(geo);
}
#pragma endregion

//在BuildRenderItems方法中,将该骷髅头加入到mAllRitems中(这里附带了一些上下文,方便定位)
    gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
    gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
      mAllRitems.push_back(std::move(gridRitem));

#pragma region Quiz0703
      //将骷髅头填充到这里
      auto skullRitem = std::make_unique<RenderItem>();
      skullRitem->World = MathHelper::Identity4x4();
      skullRitem->ObjCBIndex = 2;
      const auto skullGeo = mGeometries["skullGeo"].get();
      const auto skull = skullGeo->DrawArgs["skull"];
      skullRitem->Geo = skullGeo;
      skullRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
      skullRitem->IndexCount = skull.IndexCount;
      skullRitem->StartIndexLocation = skull.StartIndexLocation;
      skullRitem->BaseVertexLocation = skull.BaseVertexLocation;
      mAllRitems.push_back(std::move(skullRitem));

      //这里的2被改为3 因为我们在上面添加了一个骷髅头
      UINT objCBIndex = 3;
#pragma endregion如果往后看几章,我们可以发现,书中有骷髅头读取的实现...
页: [1]
查看完整版本: DX12游戏开发实战习题 第七章