本文由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()函數
因為同之前教程中代碼並沒有什么改變,所以不再給出了。
這樣運行程序就能得到之前給出圖片所示的效果了,是不是很簡單呢?