最爱午后红茶

剖析山峰演示程序

日期图标
2023-06-14

山峰演示程序:

有了前面 立方体演示程序 的基础,我们很容易可以知道山峰演示程序跟立方体演示程序的区别只是顶点的属性(位置和颜色)不同,其他逻辑比如移动相机位置等都是一样的。

在这里基本就不具体到代码层的剖析了,只是理一下思路就足够了。流程基本分为以下步骤:

  1. xzx-z 平面创建一片网格顶点(此时所有顶点 yy 坐标均为 0)。
  2. 根据顶点创建对应的三角形索引。
  3. 根据高度计算函数(算法)重新计算每个顶点的 yy 坐标,使结果呈现山峰状。
山峰网格
图一:山峰网格

Demo 工程提供了一个工具类 GeometryGenerator,可以创建几类基础几何体的网格顶点和索引:

class GeometryGenerator {
public:
  struct Vertex {
    Vertex() {}
    Vertex(const XMFLOAT3 &p, const XMFLOAT3 &n, const XMFLOAT3 &t,
           const XMFLOAT2 &uv)
        : Position(p), Normal(n), TangentU(t), TexC(uv) {}
    Vertex(float px, float py, float pz, float nx, float ny, float nz, float tx,
           float ty, float tz, float u, float v)
        : Position(px, py, pz), Normal(nx, ny, nz), TangentU(tx, ty, tz),
          TexC(u, v) {}

    XMFLOAT3 Position;
    XMFLOAT3 Normal;
    XMFLOAT3 TangentU;
    XMFLOAT2 TexC;
  };

  struct MeshData {
    std::vector<Vertex> Vertices;
    std::vector<UINT> Indices;
  };

  ///< summary>
  /// Creates a box centered at the origin with the given dimensions.
  ///</summary>
  void CreateBox(float width, float height, float depth, MeshData &meshData);

  ///< summary>
  /// Creates a sphere centered at the origin with the given radius.  The
  /// slices and stacks parameters control the degree of tessellation.
  ///</summary>
  void CreateSphere(float radius, UINT sliceCount, UINT stackCount,
                    MeshData &meshData);

  ///< summary>
  /// Creates a geosphere centered at the origin with the given radius.  The
  /// depth controls the level of tessellation.
  ///</summary>
  void CreateGeosphere(float radius, UINT numSubdivisions, MeshData &meshData);

  ///< summary>
  /// Creates a cylinder parallel to the y-axis, and centered about the origin.
  /// The bottom and top radius can vary to form various cone shapes rather than
  /// true
  // cylinders.  The slices and stacks parameters control the degree of
  // tessellation.
  ///</summary>
  void CreateCylinder(float bottomRadius, float topRadius, float height,
                      UINT sliceCount, UINT stackCount, MeshData &meshData);

  ///< summary>
  /// Creates an mxn grid in the xz-plane with m rows and n columns, centered
  /// at the origin with the specified width and depth.
  ///</summary>
  void CreateGrid(float width, float depth, UINT m, UINT n, MeshData &meshData);

  ///< summary>
  /// Creates a quad covering the screen in NDC coordinates.  This is useful for
  /// postprocessing effects.
  ///</summary>
  void CreateFullscreenQuad(MeshData &meshData);

private:
  void Subdivide(MeshData &meshData);
  void BuildCylinderTopCap(float bottomRadius, float topRadius, float height,
                           UINT sliceCount, UINT stackCount,
                           MeshData &meshData);
  void BuildCylinderBottomCap(float bottomRadius, float topRadius, float height,
                              UINT sliceCount, UINT stackCount,
                              MeshData &meshData);
};

之后使用 GetHeight 函数计算每个顶点的 yy 坐标:

float HillsApp::GetHeight(float x, float z) const {
  return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
}

在设置顶点颜色时,给不同的高度区间设置不同的颜色,就能使各个突起看起来像积雪的山峰。

void HillsApp::BuildGeometryBuffers() {
  GeometryGenerator::MeshData grid;

  GeometryGenerator geoGen;

  geoGen.CreateGrid(160.0f, 160.0f, 50, 50, grid);

  mGridIndexCount = grid.Indices.size();

  //
  // Extract the vertex elements we are interested and apply the height function
  // to each vertex.  In addition, color the vertices based on their height so
  // we have sandy looking beaches, grassy low hills, and snow mountain peaks.
  //

  std::vector<Vertex> vertices(grid.Vertices.size());
  for (size_t i = 0; i < grid.Vertices.size(); ++i) {
    XMFLOAT3 p = grid.Vertices[i].Position;

    p.y = GetHeight(p.x, p.z);

    vertices[i].Pos = p;

    // Color the vertex based on its height.
    if (p.y < -10.0f) {
      // Sandy beach color.
      vertices[i].Color = XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
    } else if (p.y < 5.0f) {
      // Light yellow-green.
      vertices[i].Color = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
    } else if (p.y < 12.0f) {
      // Dark yellow-green.
      vertices[i].Color = XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
    } else if (p.y < 20.0f) {
      // Dark brown.
      vertices[i].Color = XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);
    } else {
      // White snow.
      vertices[i].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    }
  }

  D3D11_BUFFER_DESC vbd;
  vbd.Usage = D3D11_USAGE_IMMUTABLE;
  vbd.ByteWidth = sizeof(Vertex) * grid.Vertices.size();
  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.
  //

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

其余的逻辑基本跟立方体演示程序一致。

* 未经同意不得转载。