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()函数
因为同之前教程中代码并没有什么改变,所以不再给出了。
这样运行程序就能得到之前给出图片所示的效果了,是不是很简单呢?