最爱午后红茶

剖析几何体演示程序

日期图标
2023-06-14

几何体演示程序:

可以看到,本程序跟立方体和山峰演示程序对比有几个不同的地方:

  • 场景中使用了多个模型
  • 模型使用了线框渲染

本程序也是使用跟 剖析山峰演示程序 一样的方式去创建网格并以网格中心作为世界坐标中心。

合并几何体顶点缓冲区和索引缓冲区

演示程序一共使用了 4 种类型的几何体:长方体、圆柱体、球、网格。因此需要创建这 4 种类型的顶点缓冲区和索引缓冲区。为了提升性能,我们需要把这 4 类几何体的顶点缓冲区合成一个,并且记录他们各自在顶点缓冲区的起始下标。索引缓冲区也需要做类似的合并。

void ShapesApp::BuildGeometryBuffers() {
  GeometryGenerator::MeshData box;
  GeometryGenerator::MeshData grid;
  GeometryGenerator::MeshData sphere;
  GeometryGenerator::MeshData cylinder;

  GeometryGenerator geoGen;
  geoGen.CreateBox(1.0f, 1.0f, 1.0f, box);
  geoGen.CreateGrid(20.0f, 30.0f, 60, 40, grid);
  geoGen.CreateSphere(0.5f, 20, 20, sphere);
  // geoGen.CreateGeosphere(0.5f, 2, sphere);
  geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20, cylinder);

  // Cache the vertex offsets to each object in the concatenated vertex buffer.
  mBoxVertexOffset = 0;
  mGridVertexOffset = box.Vertices.size();
  mSphereVertexOffset = mGridVertexOffset + grid.Vertices.size();
  mCylinderVertexOffset = mSphereVertexOffset + sphere.Vertices.size();

  // Cache the index count of each object.
  mBoxIndexCount = box.Indices.size();
  mGridIndexCount = grid.Indices.size();
  mSphereIndexCount = sphere.Indices.size();
  mCylinderIndexCount = cylinder.Indices.size();

  // Cache the starting index for each object in the concatenated index buffer.
  mBoxIndexOffset = 0;
  mGridIndexOffset = mBoxIndexCount;
  mSphereIndexOffset = mGridIndexOffset + mGridIndexCount;
  mCylinderIndexOffset = mSphereIndexOffset + mSphereIndexCount;

  UINT totalVertexCount = box.Vertices.size() + grid.Vertices.size() +
                          sphere.Vertices.size() + cylinder.Vertices.size();

  UINT totalIndexCount = mBoxIndexCount + mGridIndexCount + mSphereIndexCount +
                         mCylinderIndexCount;

  //
  // Extract the vertex elements we are interested in and pack the
  // vertices of all the meshes into one vertex buffer.
  //

  std::vector<Vertex> vertices(totalVertexCount);

  XMFLOAT4 black(0.0f, 0.0f, 0.0f, 1.0f);

  UINT k = 0;
  for (size_t i = 0; i < box.Vertices.size(); ++i, ++k) {
    vertices[k].Pos = box.Vertices[i].Position;
    vertices[k].Color = black;
  }

  for (size_t i = 0; i < grid.Vertices.size(); ++i, ++k) {
    vertices[k].Pos = grid.Vertices[i].Position;
    vertices[k].Color = black;
  }

  for (size_t i = 0; i < sphere.Vertices.size(); ++i, ++k) {
    vertices[k].Pos = sphere.Vertices[i].Position;
    vertices[k].Color = black;
  }

  for (size_t i = 0; i < cylinder.Vertices.size(); ++i, ++k) {
    vertices[k].Pos = cylinder.Vertices[i].Position;
    vertices[k].Color = black;
  }

  D3D11_BUFFER_DESC vbd;
  vbd.Usage = D3D11_USAGE_IMMUTABLE;
  vbd.ByteWidth = sizeof(Vertex) * totalVertexCount;
  vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
  vbd.CPUAccessFlags = 0;
  vbd.MiscFlags = 0;
  D3D11_SUBRESOURCE_DATA vinitData;
  vinitData.pSysMem = &vertices[0];
  HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &mVB));

  //
  // Pack the indices of all the meshes into one index buffer.
  //

  std::vector<UINT> indices;
  indices.insert(indices.end(), box.Indices.begin(), box.Indices.end());
  indices.insert(indices.end(), grid.Indices.begin(), grid.Indices.end());
  indices.insert(indices.end(), sphere.Indices.begin(), sphere.Indices.end());
  indices.insert(indices.end(), cylinder.Indices.begin(),
                 cylinder.Indices.end());

  D3D11_BUFFER_DESC ibd;
  ibd.Usage = D3D11_USAGE_IMMUTABLE;
  ibd.ByteWidth = sizeof(UINT) * totalIndexCount;
  ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
  ibd.CPUAccessFlags = 0;
  ibd.MiscFlags = 0;
  D3D11_SUBRESOURCE_DATA iinitData;
  iinitData.pSysMem = &indices[0];
  HR(md3dDevice->CreateBuffer(&ibd, &iinitData, &mIB));
}

确定几何体位置

我们知道网格处在 xzx-z 平面,且它的中心在世界坐标系的中心。那我们是怎样把一个长方体摆在网格正中间上的呢?

在前面我们通过 GeometryGenerator::CreateBox 创建了一个 1x1x1 的立方体。通过以下代码,我们为这个立方体创建了一个世界变换矩阵,把它缩放成一个长方体并摆在网格正中间上:

// x 和 z 坐标放大一倍,y 坐标不变
XMMATRIX boxScale = XMMatrixScaling(2.0f, 1.0f, 2.0f);
// 立方体中心初始时在世界坐标系原点,向上平移 0.5 的距离后立方体的底面刚好在网格所在平面
XMMATRIX boxOffset = XMMatrixTranslation(0.0f, 0.5f, 0.0f);
XMStoreFloat4x4(&mBoxWorld, XMMatrixMultiply(boxScale, boxOffset));

因为每个几何体都在世界空间中的不同位置,所以我们需要为每个几何体创建对应的世界变换矩阵,这部分逻辑在 ShapesApp 的构造函数中完成:

ShapesApp::ShapesApp(HINSTANCE hInstance)
    : D3DApp(hInstance), mVB(0), mIB(0), mFX(0), mTech(0), mfxWorldViewProj(0),
      mInputLayout(0), mWireframeRS(0), mTheta(1.5f * MathHelper::Pi),
      mPhi(0.1f * MathHelper::Pi), mRadius(15.0f) {
  mMainWndCaption = L"Shapes Demo";

  mLastMousePos.x = 0;
  mLastMousePos.y = 0;

  XMMATRIX I = XMMatrixIdentity();
  XMStoreFloat4x4(&mGridWorld, I);
  XMStoreFloat4x4(&mView, I);
  XMStoreFloat4x4(&mProj, I);

  // x 和 z 坐标放大一倍,y 坐标不变
  XMMATRIX boxScale = XMMatrixScaling(2.0f, 1.0f, 2.0f);
  // 立方体中心初始时在世界坐标系原点,向上平移 0.5 的距离后立方体的底面刚好在网格所在平面
  XMMATRIX boxOffset = XMMatrixTranslation(0.0f, 0.5f, 0.0f);
  XMStoreFloat4x4(&mBoxWorld, XMMatrixMultiply(boxScale, boxOffset));

  XMMATRIX centerSphereScale = XMMatrixScaling(2.0f, 2.0f, 2.0f);
  XMMATRIX centerSphereOffset = XMMatrixTranslation(0.0f, 2.0f, 0.0f);
  XMStoreFloat4x4(&mCenterSphere,
                  XMMatrixMultiply(centerSphereScale, centerSphereOffset));

  for (int i = 0; i < 5; ++i) {
    XMStoreFloat4x4(&mCylWorld[i * 2 + 0],
                    XMMatrixTranslation(-5.0f, 1.5f, -10.0f + i * 5.0f));
    XMStoreFloat4x4(&mCylWorld[i * 2 + 1],
                    XMMatrixTranslation(+5.0f, 1.5f, -10.0f + i * 5.0f));

    XMStoreFloat4x4(&mSphereWorld[i * 2 + 0],
                    XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i * 5.0f));
    XMStoreFloat4x4(&mSphereWorld[i * 2 + 1],
                    XMMatrixTranslation(+5.0f, 3.5f, -10.0f + i * 5.0f));
  }
}

绘制几何体

程序使用线框模式来绘制几何体,可以通过指定渲染状态中的 D3D11_RASTERIZER_DESC::FillModeD3D11_FILL_WIREFRAME 来达到线框效果(设置成 D3D11_FILL_SOLID 的话就是实心模式)。这部分逻辑在 ShapesApp::Init() 中完成:

bool ShapesApp::Init() {
  if (!D3DApp::Init())
    return false;

  BuildGeometryBuffers();
  BuildFX();
  BuildVertexLayout();

  D3D11_RASTERIZER_DESC wireframeDesc;
  ZeroMemory(&wireframeDesc, sizeof(D3D11_RASTERIZER_DESC));
  wireframeDesc.FillMode = D3D11_FILL_WIREFRAME;
  wireframeDesc.CullMode = D3D11_CULL_BACK;
  wireframeDesc.FrontCounterClockwise = false;
  wireframeDesc.DepthClipEnable = true;

  HR(md3dDevice->CreateRasterizerState(&wireframeDesc, &mWireframeRS));

  return true;
}

几何体顶点的数据是可以重复使用的,因此尽管有些几何体需要在不同位置上绘制多次(比如球和圆柱),我们只需要知道每个几何体的世界变换矩阵以及这类几何体在顶点缓冲区和索引缓冲区的下标,就可以通过 DrawIndexed() 接口调用正确地绘制出来:

void ShapesApp::DrawScene() {
  md3dImmediateContext->ClearRenderTargetView(
      mRenderTargetView,
      reinterpret_cast<const float *>(&Colors::LightSteelBlue));
  md3dImmediateContext->ClearDepthStencilView(
      mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

  md3dImmediateContext->IASetInputLayout(mInputLayout);
  md3dImmediateContext->IASetPrimitiveTopology(
      D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

  md3dImmediateContext->RSSetState(mWireframeRS);

  UINT stride = sizeof(Vertex);
  UINT offset = 0;
  md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB, &stride, &offset);
  md3dImmediateContext->IASetIndexBuffer(mIB, DXGI_FORMAT_R32_UINT, 0);

  // Set constants

  XMMATRIX view = XMLoadFloat4x4(&mView);
  XMMATRIX proj = XMLoadFloat4x4(&mProj);
  XMMATRIX viewProj = view * proj;

  D3DX11_TECHNIQUE_DESC techDesc;
  mTech->GetDesc(&techDesc);
  for (UINT p = 0; p < techDesc.Passes; ++p) {
    // Draw the grid.
    XMMATRIX world = XMLoadFloat4x4(&mGridWorld);
    mfxWorldViewProj->SetMatrix(reinterpret_cast<float *>(&(world * viewProj)));
    mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
    md3dImmediateContext->DrawIndexed(mGridIndexCount, mGridIndexOffset,
                                      mGridVertexOffset);

    // Draw the box.
    world = XMLoadFloat4x4(&mBoxWorld);
    mfxWorldViewProj->SetMatrix(reinterpret_cast<float *>(&(world * viewProj)));
    mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
    md3dImmediateContext->DrawIndexed(mBoxIndexCount, mBoxIndexOffset,
                                      mBoxVertexOffset);

    // Draw center sphere.
    world = XMLoadFloat4x4(&mCenterSphere);
    mfxWorldViewProj->SetMatrix(reinterpret_cast<float *>(&(world * viewProj)));
    mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
    md3dImmediateContext->DrawIndexed(mSphereIndexCount, mSphereIndexOffset,
                                      mSphereVertexOffset);

    // Draw the cylinders.
    for (int i = 0; i < 10; ++i) {
      world = XMLoadFloat4x4(&mCylWorld[i]);
      mfxWorldViewProj->SetMatrix(
          reinterpret_cast<float *>(&(world * viewProj)));
      mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
      md3dImmediateContext->DrawIndexed(
          mCylinderIndexCount, mCylinderIndexOffset, mCylinderVertexOffset);
    }

    // Draw the spheres.
    for (int i = 0; i < 10; ++i) {
      world = XMLoadFloat4x4(&mSphereWorld[i]);
      mfxWorldViewProj->SetMatrix(
          reinterpret_cast<float *>(&(world * viewProj)));
      mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
      md3dImmediateContext->DrawIndexed(mSphereIndexCount, mSphereIndexOffset,
                                        mSphereVertexOffset);
    }
  }

  HR(mSwapChain->Present(0, 0));
}
* 未经同意不得转载。