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()函數

因為同之前教程中代碼並沒有什么改變,所以不再給出了。

 

這樣運行程序就能得到之前給出圖片所示的效果了,是不是很簡單呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM