Directx11学习笔记【十三】 实现一个简单地形

本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5510294.html

上一个教程我们实现了渲染一个会旋转的立方体,这次我们来实现一个简单地形。

先来看看最终实现效果吧(蓝色是背景色,地形的不同高度分别渲染了不同颜色)

实现原理其实很简单,我们现在xz平面定义一个二维网格,然后y值可以根据一定的函数得到,比如正余弦函数组成等等,便可以得到一个看似不错的地形

或者水面效果。

1.创建二维网格

首先我们在GeometryGenerator中定义了一个只有XMFLOAT3 Position一个变量的结构Vertex用来描述顶点信息,MeshData中保存了整个grid所有

顶点的顶点信息和索引信息。CreateGrid函数负责创建grid。

 1 #pragma once
 2 
 3 #include<vector>
 4 #include "Dx11DemoBase.h"
 5 
 6 class GeometryGenerator
 7 {
 8 public:
 9     struct  Vertex 
10     {
11         Vertex(){}
12         Vertex(const XMFLOAT3& p)
13             : Position(p){}
14         Vertex(float px, float py, float pz): Position(px, py, pz){}
15         XMFLOAT3 Position;
16     };
17 
18     struct MeshData
19     {
20         std::vector<Vertex> vertices;
21         std::vector<UINT> indices;
22     };
23 
24     void CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData);
25 };

顶点索引的计算关键是推导出一个用于求构成第i行,第j列的顶点处右下方两个三角形的顶点索引的通用公式。

对顶点缓存中的任意一点A,如果该点位于地形中的第i行、第j列的话,那么该点在顶点缓存中所对应的位置应该就是i*m+j(m为每行的顶点数)。如果A点在索引缓存中的位置为k的话,那么A点为起始点构成的三角形ABC中,B、C顶点在顶点缓存中的位置就为(i+1)x m+j和i x m+(j+1)。且B点索引值为k+1,C点索引值为k+2.这样。这样,公式就可以推导为如下:

三角形ABC=【i*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+j】

三角形CBD=【(i+1)*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+(j+1)】

 1 #include "GeometryGenerator.h"
 2 
 3 
 4 void GeometryGenerator::CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData)
 5 {
 6     UINT vertexCount = m*n;
 7     UINT faceCount = (m - 1)*(n - 1) * 2;
 8 
 9     // Create the vertices.
10 
11     float halfWidth = 0.5f*width;
12     float halfDepth = 0.5f*depth;
13 
14     float dx = width / (n - 1);
15     float dz = depth / (m - 1);
16 
17     meshData.vertices.resize(vertexCount);
18     for (UINT i = 0; i < m; ++i)
19     {
20         float z = halfDepth - i*dz;
21         for (UINT j = 0; j < n; ++j)
22         {
23             float x = -halfWidth + j*dx;
24             meshData.vertices[i*n + j].Position = XMFLOAT3(x, 0.0f, z);
25         }
26     }
27 
28     // Create the indices.
29 
30     meshData.indices.resize(faceCount * 3); // 3 indices per face
31 
32     UINT k = 0;
33     for (UINT i = 0; i < m - 1; ++i)
34     {
35         for (UINT j = 0; j < n - 1; ++j)
36         {
37             meshData.indices[k] = i*n + j;
38             meshData.indices[k + 1] = i*n + j + 1;
39             meshData.indices[k + 2] = (i + 1)*n + j;
40 
41             meshData.indices[k + 3] = (i + 1)*n + j;
42             meshData.indices[k + 4] = i*n + j + 1;
43             meshData.indices[k + 5] = (i + 1)*n + j + 1;
44 
45             k += 6; // next quad
46         }
47     }
48 }

2.鼠标拖动控制视角

为了方便观察最后创建出的地形,我们采用鼠标拖动旋转的方式,通过鼠标拖动改变相应的视图矩阵,因此我们Dx11DemoBase中添加了三个函数

//鼠标事件
virtual void OnMouseDown(WPARAM btnState, int x, int y){}
virtual void OnMouseUp(WPARAM btnState, int x, int y){}
virtual void OnMouseMove(WPARAM btnState, int x, int y){}

main函数消息循环中添加

 1 case WM_LBUTTONDOWN:
 2     case WM_MBUTTONDOWN:
 3     case WM_RBUTTONDOWN:
 4         demo->OnMouseDown(wParam, GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
 5         return 0;
 6     case WM_LBUTTONUP:
 7     case WM_MBUTTONUP:
 8     case WM_RBUTTONUP:
 9         demo->OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
10         return 0;
11     case WM_MOUSEMOVE:
12         demo->OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));

具体函数的实现在下面给出

3.顶点缓冲和索引缓冲的创建

 1     GeometryGenerator::MeshData grid;
 2     GeometryGenerator geoGen;
 3     geoGen.CreateGrid(160.0f, 160.0f, 50, 50, grid);
 4     m_gridIndexCount = grid.indices.size();
 5 
 6     std::vector<Vertex> vertices(grid.vertices.size(),Vertex(XMFLOAT3(0,0,0),XMFLOAT4(0,0,0,0)));
 7     for (UINT i = 0; i < grid.vertices.size(); ++i)
 8     {
 9         XMFLOAT3 p = grid.vertices[i].Position;
10         p.y = GetHeight(p.x, p.z);
11 
12         vertices[i].pos = p;
13         
14         //渲染顶点根据高度给出不同颜色
15         if (p.y < -10.0f)
16         {
17             //sandy beach color
18             vertices[i].color = XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
19         }
20         else if (p.y < 5.0f)
21         {
22             //light yellow-green color
23             vertices[i].color = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
24         }
25         else if (p.y < 12.0f)
26         {
27             //dark yellow-green color
28             vertices[i].color = XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
29         }
30         else if (p.y < 20.f)
31         {
32             //dark brown color
33             vertices[i].color = XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);
34         }
35         else
36         {
37             //white snow color
38             vertices[i].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
39         }
40     }
41 
42     D3D11_BUFFER_DESC vertexDesc;
43     ZeroMemory(&vertexDesc, sizeof(vertexDesc));
44     vertexDesc.Usage = D3D11_USAGE_IMMUTABLE;
45     vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
46     vertexDesc.ByteWidth = sizeof(Vertex)* grid.vertices.size();
47     D3D11_SUBRESOURCE_DATA resourceData;
48     ZeroMemory(&resourceData, sizeof(resourceData));
49     resourceData.pSysMem = &vertices[0];
50     result = m_pd3dDevice->CreateBuffer(&vertexDesc, &resourceData, &m_pVertexBuffer);
51     if (FAILED(result))
52     {
53         return false;
54     }
55 
56 
57 
58     D3D11_BUFFER_DESC indexDesc;
59     ZeroMemory(&indexDesc, sizeof(indexDesc));
60     indexDesc.Usage = D3D11_USAGE_IMMUTABLE;
61     indexDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
62     indexDesc.ByteWidth = sizeof(UINT)* m_gridIndexCount;
63 
64     D3D11_SUBRESOURCE_DATA indexData;
65     ZeroMemory(&indexData, sizeof(indexData));
66     indexData.pSysMem = &grid.indices[0];
67     result = m_pd3dDevice->CreateBuffer(&indexDesc, &indexData, &m_pIndexBuffer);
68     if (FAILED(result))
69     {
70         return false;
71     }

加载shader代码与之前相同,故不再给出

GetHeight函数根据xz的值得到y的值

1 float HillsDemo::GetHeight(float x, float z) const
2 {
3     return 0.3f*(z*sinf(0.1f*x) + x*cosf(0.1f*z));
4 }

下面给出鼠标控制的具体做法:

定义4了个变量

1     float m_theta;
2     float m_phi;
3     float m_radius;
4     POINT m_lastMousePos;

其中m_lastMousePos意思明确很容易理解,那么其他三个分别代表什么意思呢?看下面的图就明白了

m_radius为半径,m_phi和m_theta为两个角度。

初始化半径和角度

1 HillsDemo::HillsDemo()//其他变量初始化省略了
2 : m_theta(1.5f*XM_PI), m_phi(0.1f*XM_PI), m_radius(200.0f){}

在Update函数中,给world,view,proj矩阵赋值

 1 void HillsDemo::Update(float dt)
 2 {
 3     float x = m_radius*sinf(m_phi)*cosf(m_theta);
 4     float z = m_radius*sinf(m_phi)*sinf(m_theta);
 5     float y = m_radius*cosf(m_phi);
 6 
 7     XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
 8     XMVECTOR target = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
 9     XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
10 
11     XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
12     XMStoreFloat4x4(&m_view, V);
13     XMMATRIX T = XMMatrixPerspectiveFovLH(XM_PIDIV4, m_width / static_cast<float>(m_height),
14         1.0f, 1000.0f);
15     XMStoreFloat4x4(&m_proj, T);
16     
17 }

拖动鼠标时适当改变半径和角度的值,便可以间接改变view矩阵,从而改变视角

 1 void HillsDemo::OnMouseDown(WPARAM btnState, int x, int y)
 2 {
 3     m_lastMousePos.x = x;
 4     m_lastMousePos.y = y;
 5     SetCapture(m_hWnd);
 6 }
 7 
 8 void HillsDemo::OnMouseUp(WPARAM btnState, int x, int y)
 9 {
10     ReleaseCapture();
11 }
12 
13 //限定数值范围
14 template<typename T>
15 static T Clamp(const T& x, const T& low, const T& high)
16 {
17     return x < low ? low : (x > high ? high : x);
18 }
19 
20 void HillsDemo::OnMouseMove(WPARAM btnState, int x, int y)
21 {
22     if ((btnState & MK_LBUTTON) != 0)
23     {
24         // Make each pixel correspond to a quarter of a degree.
25         float dx = XMConvertToRadians(0.25f*static_cast<float>(x - m_lastMousePos.x));
26         float dy = XMConvertToRadians(0.25f*static_cast<float>(y - m_lastMousePos.y));
27 
28         // Update angles based on input to orbit camera around box.
29         m_theta += dx;
30         m_phi += dy;
31 
32         // Restrict the angle mPhi.
33         m_phi = Clamp(m_phi, 0.1f, XM_PI - 0.1f);
34     }
35     else if ((btnState & MK_RBUTTON) != 0)
36     {
37         // Make each pixel correspond to 0.2 unit in the scene.
38         float dx = 0.2f*static_cast<float>(x - m_lastMousePos.x);
39         float dy = 0.2f*static_cast<float>(y - m_lastMousePos.y);
40 
41         // Update the camera radius based on input.
42         m_radius += dx - dy;
43 
44         // Restrict the radius.
45         m_radius = Clamp(m_radius, 50.0f, 500.0f);
46     }
47 
48     m_lastMousePos.x = x;
49     m_lastMousePos.y = y;
50 }

4.渲染Render()函数

因为同之前教程中代码并没有什么改变,所以不再给出了。

这样运行程序就能得到之前给出图片所示的效果了,是不是很简单呢?

原文地址:https://www.cnblogs.com/zhangbaochong/p/5510294.html