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

时间:2023-03-09 07:47:58
Directx11学习笔记【十三】 实现一个简单地形

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

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

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

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

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

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

或者水面效果。

1.创建二维网格

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

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

 #pragma once

 #include<vector>
#include "Dx11DemoBase.h" class GeometryGenerator
{
public:
struct Vertex
{
Vertex(){}
Vertex(const XMFLOAT3& p)
: Position(p){}
Vertex(float px, float py, float pz): Position(px, py, pz){}
XMFLOAT3 Position;
}; struct MeshData
{
std::vector<Vertex> vertices;
std::vector<UINT> indices;
}; void CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData);
};

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

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

对顶点缓存中的任意一点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)】

 #include "GeometryGenerator.h"

 void GeometryGenerator::CreateGrid(float width, float depth, UINT m, UINT n, MeshData& meshData)
{
UINT vertexCount = m*n;
UINT faceCount = (m - )*(n - ) * ; // Create the vertices. float halfWidth = 0.5f*width;
float halfDepth = 0.5f*depth; float dx = width / (n - );
float dz = depth / (m - ); meshData.vertices.resize(vertexCount);
for (UINT i = ; i < m; ++i)
{
float z = halfDepth - i*dz;
for (UINT j = ; j < n; ++j)
{
float x = -halfWidth + j*dx;
meshData.vertices[i*n + j].Position = XMFLOAT3(x, 0.0f, z);
}
} // Create the indices. meshData.indices.resize(faceCount * ); // 3 indices per face UINT k = ;
for (UINT i = ; i < m - ; ++i)
{
for (UINT j = ; j < n - ; ++j)
{
meshData.indices[k] = i*n + j;
meshData.indices[k + ] = i*n + j + ;
meshData.indices[k + ] = (i + )*n + j; meshData.indices[k + ] = (i + )*n + j;
meshData.indices[k + ] = i*n + j + ;
meshData.indices[k + ] = (i + )*n + j + ; k += ; // next quad
}
}
}

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函数消息循环中添加

 case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
demo->OnMouseDown(wParam, GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
return ;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
demo->OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return ;
case WM_MOUSEMOVE:
demo->OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));

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

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

     GeometryGenerator::MeshData grid;
GeometryGenerator geoGen;
geoGen.CreateGrid(160.0f, 160.0f, , , grid);
m_gridIndexCount = grid.indices.size(); std::vector<Vertex> vertices(grid.vertices.size(),Vertex(XMFLOAT3(,,),XMFLOAT4(,,,)));
for (UINT i = ; i < grid.vertices.size(); ++i)
{
XMFLOAT3 p = grid.vertices[i].Position;
p.y = GetHeight(p.x, p.z); vertices[i].pos = p; //渲染顶点根据高度给出不同颜色
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 color
vertices[i].color = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
}
else if (p.y < 12.0f)
{
//dark yellow-green color
vertices[i].color = XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
}
else if (p.y < .f)
{
//dark brown color
vertices[i].color = XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);
}
else
{
//white snow color
vertices[i].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
}
} D3D11_BUFFER_DESC vertexDesc;
ZeroMemory(&vertexDesc, sizeof(vertexDesc));
vertexDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexDesc.ByteWidth = sizeof(Vertex)* grid.vertices.size();
D3D11_SUBRESOURCE_DATA resourceData;
ZeroMemory(&resourceData, sizeof(resourceData));
resourceData.pSysMem = &vertices[];
result = m_pd3dDevice->CreateBuffer(&vertexDesc, &resourceData, &m_pVertexBuffer);
if (FAILED(result))
{
return false;
} D3D11_BUFFER_DESC indexDesc;
ZeroMemory(&indexDesc, sizeof(indexDesc));
indexDesc.Usage = D3D11_USAGE_IMMUTABLE;
indexDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexDesc.ByteWidth = sizeof(UINT)* m_gridIndexCount; D3D11_SUBRESOURCE_DATA indexData;
ZeroMemory(&indexData, sizeof(indexData));
indexData.pSysMem = &grid.indices[];
result = m_pd3dDevice->CreateBuffer(&indexDesc, &indexData, &m_pIndexBuffer);
if (FAILED(result))
{
return false;
}

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

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

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

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

定义4了个变量

     float m_theta;
float m_phi;
float m_radius;
POINT m_lastMousePos;

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

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

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

初始化半径和角度

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

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

 void HillsDemo::Update(float dt)
{
float x = m_radius*sinf(m_phi)*cosf(m_theta);
float z = m_radius*sinf(m_phi)*sinf(m_theta);
float y = m_radius*cosf(m_phi); XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&m_view, V);
XMMATRIX T = XMMatrixPerspectiveFovLH(XM_PIDIV4, m_width / static_cast<float>(m_height),
1.0f, 1000.0f);
XMStoreFloat4x4(&m_proj, T); }

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

 void HillsDemo::OnMouseDown(WPARAM btnState, int x, int y)
{
m_lastMousePos.x = x;
m_lastMousePos.y = y;
SetCapture(m_hWnd);
} void HillsDemo::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
} //限定数值范围
template<typename T>
static T Clamp(const T& x, const T& low, const T& high)
{
return x < low ? low : (x > high ? high : x);
} void HillsDemo::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != )
{
// Make each pixel correspond to a quarter of a degree.
float dx = XMConvertToRadians(0.25f*static_cast<float>(x - m_lastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - m_lastMousePos.y)); // Update angles based on input to orbit camera around box.
m_theta += dx;
m_phi += dy; // Restrict the angle mPhi.
m_phi = Clamp(m_phi, 0.1f, XM_PI - 0.1f);
}
else if ((btnState & MK_RBUTTON) != )
{
// Make each pixel correspond to 0.2 unit in the scene.
float dx = 0.2f*static_cast<float>(x - m_lastMousePos.x);
float dy = 0.2f*static_cast<float>(y - m_lastMousePos.y); // Update the camera radius based on input.
m_radius += dx - dy; // Restrict the radius.
m_radius = Clamp(m_radius, 50.0f, 500.0f);
} m_lastMousePos.x = x;
m_lastMousePos.y = y;
}

4.渲染Render()函数

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

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