Directx11学习笔记【二十二】 用高度图实现地形

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

  在前面我们曾经实现过简单的地形(Directx11学习笔记【十三】 实现一个简单地形),只不过原来使用一个固定的函数获得地形高度,这样跟真实的地形差距比较大。接下来让我们学习使用高度图来进行三维地形模拟。

1.高度图

  高度图其实就是一组连续的数组,这个数组中的元素与地形网格中的顶点一一对应,且每一个元素都指定了地形网格的某个顶点的高度值。高度图最常用的使用灰度图实现,灰度图中亮度越大对应的地形高度越高。下面就是一幅灰度图:

  Directx11学习笔记【二十二】 用高度图实现地形

  灰度图格式通常为.raw,google一下高度图保存图片改为raw格式就可以了。高度图每个元素通常只分配一个字节,即数值在0~255之间。但在实际使用的时候经常要对高度进行比例变换,因此需要将byte转为float,然后通过一个缩放系数进行缩放,这样就不必拘泥于0~255这么一个范围了。

2.读取高度图

  读取高度图很简单,用二进制读取文件就好了。

 //读取高度图信息
bool TerrainDemo::ReadRawFile(std::string filePath)
{
std::ifstream inFile;
//二进制方式打开文件
inFile.open(filePath.c_str(), std::ios::binary);
//文件指针移动到末尾
inFile.seekg(, std::ios::end);
//大小为当前缓冲区大小
std::vector<BYTE> inData(inFile.tellg());
//文件指针移动到开头
inFile.seekg(std::ios::beg);
//读取高度信息
inFile.read((char*)&inData[], inData.size());
inFile.close(); m_heightInfos.resize(inData.size());
for (int i = ; i < inData.size(); ++i)
{
m_heightInfos[i] = inData[i];
} return true;
}

3.顶点和索引的计算

  顶点和索引的计算同Directx11学习笔记【十三】 实现一个简单地形类似,这里就不再详细说明了。不一样的由于采用了纹理光照渲染,所以需要得到法线,在计算索引的同时需要把顶点法线计算出来:

 //计算法线
void TerrainDemo::ComputeNomal(Vertex& v1, Vertex& v2, Vertex& v3, XMFLOAT3& normal)
{
XMFLOAT3 f1(v2.pos.x - v1.pos.x, v2.pos.y - v1.pos.y, v2.pos.z - v1.pos.z);
XMFLOAT3 f2(v3.pos.x - v1.pos.x, v3.pos.y - v1.pos.y, v3.pos.z - v1.pos.z);
XMVECTOR vec1 = XMLoadFloat3(&f1);
XMVECTOR vec2 = XMLoadFloat3(&f2);
XMVECTOR temp = XMVector3Normalize(XMVector3Cross(vec1, vec2));
XMStoreFloat3(&normal, temp);
}

  计算顶点和索引:

 bool TerrainDemo::InitTerrain(float width, float height, UINT m, UINT n,float scale)
{
m_cellsPerRow = m;
m_cellsPerCol = n;
m_verticesPerRow = m + ;
m_verticesPerCol = n + ;
m_numsVertices = m_verticesPerRow*m_verticesPerCol;
m_width = width;
m_height = height;
m_heightScale = scale; //得到缩放后的高度
for (auto& item : m_heightInfos)
{
item *= m_heightScale;
} //起始x z坐标
float oX = -width * 0.5f;
float oZ = height * 0.5f;
//每一格坐标变化
float dx = width / m;
float dz = height / n; m_vertices.resize(m_numsVertices);
//计算顶点
for (UINT i = ; i < m_verticesPerCol; ++i)
{
float tempZ = oZ - dz * i;
for (UINT j = ; j < m_verticesPerRow; ++j)
{
UINT index = m_verticesPerRow * i + j;
m_vertices[index].pos.x = oX + dx * j;
m_vertices[index].pos.y = m_heightInfos[index];
m_vertices[index].pos.z = tempZ; m_vertices[index].tex = XMFLOAT2(dx*i, dx*j);
}
} //计算索引和法线
//总格子数量:m * n
//因此总索引数量: 6 * m * n
UINT nIndices = m * n * ;
m_indices.resize(nIndices);
UINT tmp = ;
for (UINT i = ; i < n; ++i)
{
for (UINT j = ; j < m; ++j)
{
m_indices[tmp] = i * m_verticesPerRow + j;
m_indices[tmp + ] = i * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j; //计算法线
XMFLOAT3 temp;
ComputeNomal(m_vertices[m_indices[tmp]], m_vertices[m_indices[tmp + ]],
m_vertices[m_indices[tmp + ]], temp);
m_vertices[m_indices[tmp]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp; m_indices[tmp + ] = i * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j; ComputeNomal(m_vertices[m_indices[tmp + ]], m_vertices[m_indices[tmp + ]],
m_vertices[m_indices[tmp + ]], temp);
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp; tmp += ;
}
} return true;
}

4.效果截图

Directx11学习笔记【二十二】 用高度图实现地形Directx11学习笔记【二十二】 用高度图实现地形

5.详细源码

TerrainDemo.h

 #pragma once
#include <string>
#include <vector>
#include "Dx11Base.h"
#include "Camera.h"
#include "Input.h"
#include "Utility.h"
#include "LightHelper.h" class TerrainDemo : public Dx11Base
{
public:
TerrainDemo(HINSTANCE hInst, std::wstring title = L"TerrainDemo", int width = , int height = );
~TerrainDemo(); //顶点结构 位置、法线、uv坐标
struct Vertex
{
Vertex() {}
Vertex(const XMFLOAT3 _pos, XMFLOAT3 _normal, XMFLOAT2 _tex) :
pos(_pos), normal(_normal), tex(_tex) {} XMFLOAT3 pos;
XMFLOAT3 normal;
XMFLOAT2 tex;
}; bool Init() override;
void Update(float dt);
void Render(); bool OnResize() override;
private:
bool BuildBuffers();
bool BuildSRVs();
bool BuildInputLayouts();
void UpdateCamera(float dt);
private:
bool ReadRawFile(std::string filePath); //从高度图读取高度信息
bool InitTerrain(float width, float height, UINT m, UINT n, float scale); //初始化地形
void ComputeNomal(Vertex& v1, Vertex& v2, Vertex& v3, XMFLOAT3& normal); //计算法线
private:
std::vector<float> m_heightInfos; //高度图高度信息
int m_cellsPerRow; //每行单元格数
int m_cellsPerCol; //每列单元格数
int m_verticesPerRow; //每行顶点数
int m_verticesPerCol; //每列顶点数
int m_numsVertices; //顶点总数
float m_width; //地形宽度
float m_height; //地形高度
float m_heightScale; //高度缩放系数 std::vector<Vertex> m_vertices; //顶点集合
std::vector<UINT> m_indices; //索引集合 private:
ID3D11Buffer* m_pVertexBuffer;
ID3D11Buffer* m_pIndexBuffer;
ID3D11InputLayout* m_pInputLayout;
ID3D11ShaderResourceView* m_pSRVTerrain; Lights::DirectionalLight m_dirLights[]; //3个平行光源
Lights::Material m_materialTerrain; //材质 Camera m_camera; XMFLOAT4X4 m_world; //世界变换矩阵
XMFLOAT4X4 m_worldViewProj; //世界视角投影矩阵
XMFLOAT4X4 m_worldInvTranspose; //世界逆矩阵的转置,用于法线变换
XMFLOAT4X4 m_texTrans; //纹理坐标变换矩阵 POINT m_lastMousePos;
};

TerrainDemo.cpp

 #include <fstream>
#include <memory>
#include "TerrainDemo.h"
#include "Utility.h"
#include "WICTextureLoader.h"
#include "d3dx11effect.h"
#include "Effects.h" TerrainDemo::TerrainDemo(HINSTANCE hInst, std::wstring title, int width, int height)
:Dx11Base(hInst, title, width, height)
{
//"三点式"照明
//主光源
m_dirLights[].ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
m_dirLights[].diffuse = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_dirLights[].specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_dirLights[].direction = XMFLOAT3(0.57735f, -0.57735f, 0.57735f);
//侧光源
m_dirLights[].ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
m_dirLights[].diffuse = XMFLOAT4(0.20f, 0.20f, 0.20f, 1.0f);
m_dirLights[].specular = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);
m_dirLights[].direction = XMFLOAT3(-0.57735f, -0.57735f, 0.57735f);
//背光源
m_dirLights[].ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
m_dirLights[].diffuse = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
m_dirLights[].specular = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
m_dirLights[].direction = XMFLOAT3(0.0f, -0.707f, -0.707f); //材质
m_materialTerrain.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_materialTerrain.diffuse = XMFLOAT4(.f, .f, .f, 1.0f);
m_materialTerrain.specular = XMFLOAT4(0.3f, 0.3f, 0.3f, 16.0f); //设置相机
m_lastMousePos = { , };
XMVECTOR Eye = XMVectorSet(0.0f, 50.0f, 0.1f, 0.0f);
XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
m_camera.LookAtXM(Eye, At, Up);
//设置投影矩阵
m_camera.SetLens(XM_PIDIV4, AspectRatio(), 0.1f, .f); //初始化世界矩阵 逆转置矩阵 及纹理坐标矩阵
//这些每一帧不改变
XMMATRIX I = XMMatrixIdentity();
XMStoreFloat4x4(&m_world, I);
XMVECTOR det = XMMatrixDeterminant(I);
XMMATRIX worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(&det,I));
XMStoreFloat4x4(&m_worldInvTranspose, worldInvTranspose);
XMStoreFloat4x4(&m_texTrans, I);
} TerrainDemo::~TerrainDemo()
{
SafeRelease(m_pVertexBuffer);
SafeRelease(m_pIndexBuffer);
SafeRelease(m_pInputLayout);
SafeRelease(m_pSRVTerrain);
Effects::ReleaseAll();
} bool TerrainDemo::Init()
{
if (!Dx11Base::Init())
return false;
if (!Effects::InitAll(m_pd3dDevice))
return false;
if (!ReadRawFile("Texture\\heightmap.raw"))
return false;
if (!InitTerrain(, , , , 0.2f))
return false;
if (!BuildBuffers())
return false;
if (!BuildSRVs())
return false;
if (!BuildInputLayouts())
return false;
} void TerrainDemo::Update(float dt)
{
UpdateCamera(dt);
XMMATRIX world = XMLoadFloat4x4(&m_world);
XMMATRIX worldViewProj = world * m_camera.GetViewProj();
XMStoreFloat4x4(&m_worldViewProj, worldViewProj); //设置灯光
Effects::ms_pBasicEffect->m_pFxDirLights->SetRawValue(&m_dirLights, ,
* sizeof(Lights::DirectionalLight));
Effects::ms_pBasicEffect->m_pFxEyePos->SetRawValue(&m_camera.GetPosition(), ,
sizeof(m_camera.GetPosition()));
} void TerrainDemo::Render()
{
m_pImmediateContext->ClearRenderTargetView(m_pRenderTargetView, Colors::Silver);
m_pImmediateContext->ClearDepthStencilView(m_pDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, );
m_pImmediateContext->IASetInputLayout(m_pInputLayout); UINT stride = sizeof(Vertex);
UINT offset = ;
m_pImmediateContext->IASetVertexBuffers(, , &m_pVertexBuffer, &stride, &offset);
m_pImmediateContext->IASetIndexBuffer(m_pIndexBuffer, DXGI_FORMAT_R32_UINT, offset);
m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); D3DX11_TECHNIQUE_DESC desc;
ID3DX11EffectTechnique* tech = Effects::ms_pBasicEffect->m_pFxLight3TexTech;
tech->GetDesc(&desc); for (UINT i = ; i < desc.Passes; ++i)
{
//设置着色器变量
Effects::ms_pBasicEffect->m_pFxWorld->SetMatrix(reinterpret_cast<const float*>(&m_world));
Effects::ms_pBasicEffect->m_pFxWorldViewProj->SetMatrix(reinterpret_cast<const float*>(
&m_worldViewProj));
Effects::ms_pBasicEffect->m_pFxWorldInvTranspose->SetMatrix(reinterpret_cast<const float*>(
&m_worldInvTranspose));
Effects::ms_pBasicEffect->m_pFxTexTrans->SetMatrix(reinterpret_cast<const float*>(
&m_texTrans));
Effects::ms_pBasicEffect->m_pFxMaterial->SetRawValue(&m_materialTerrain, , sizeof(m_materialTerrain));
Effects::ms_pBasicEffect->m_pFxSR->SetResource(m_pSRVTerrain);
tech->GetPassByIndex(i)->Apply(, m_pImmediateContext);
m_pImmediateContext->DrawIndexed(m_indices.size(), , );
} m_pSwapChain->Present(, );
} bool TerrainDemo::OnResize()
{
if (!Dx11Base::OnResize())
return false;
//更新camera参数
m_camera.SetLens(XM_PIDIV4, AspectRatio(), .f, .f); return true;
} bool TerrainDemo::BuildBuffers()
{
//创建顶点缓冲区
D3D11_BUFFER_DESC vertexDesc;
ZeroMemory(&vertexDesc, sizeof(vertexDesc));
vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexDesc.ByteWidth = sizeof(Vertex) * m_numsVertices;
vertexDesc.Usage = D3D11_USAGE_IMMUTABLE; D3D11_SUBRESOURCE_DATA vertexData;
vertexData.pSysMem = &m_vertices[];
vertexData.SysMemPitch = ;
vertexData.SysMemSlicePitch = ;
if (FAILED(m_pd3dDevice->CreateBuffer(&vertexDesc, &vertexData, &m_pVertexBuffer)))
{
MessageBox(nullptr, L"Create Vertex Buffer failed!", L"Error", MB_OK);
return false;
} //创建索引缓冲区
D3D11_BUFFER_DESC indexDesc;
ZeroMemory(&indexDesc, sizeof(indexDesc));
indexDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexDesc.ByteWidth = sizeof(UINT) * m_indices.size();
indexDesc.Usage = D3D11_USAGE_IMMUTABLE; D3D11_SUBRESOURCE_DATA indexData;
indexData.pSysMem = &m_indices[];
indexData.SysMemPitch = ;
indexData.SysMemSlicePitch = ;
if (FAILED(m_pd3dDevice->CreateBuffer(&indexDesc, &indexData, &m_pIndexBuffer)))
{
MessageBox(nullptr, L"Create Index Buffer failed!", L"Error", MB_OK);
return false;
} return true;
} bool TerrainDemo::BuildSRVs()
{
if (FAILED(CreateWICTextureFromFile(m_pd3dDevice, L"Texture\\desert.bmp", nullptr, &m_pSRVTerrain)))
{
MessageBox(nullptr, L"create texture failed!", L"error", MB_OK);
return false;
}
return true;
} bool TerrainDemo::BuildInputLayouts()
{
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", , DXGI_FORMAT_R32G32B32_FLOAT, , , D3D11_INPUT_PER_VERTEX_DATA, },
{ "NORMAL", , DXGI_FORMAT_R32G32B32_FLOAT, , ,D3D11_INPUT_PER_VERTEX_DATA, },
{ "TEXCOORD", , DXGI_FORMAT_R32G32_FLOAT, , ,D3D11_INPUT_PER_VERTEX_DATA, }
}; UINT numLayoutElements = ARRAYSIZE(layout);
D3DX11_PASS_DESC passDesc;
Effects::ms_pBasicEffect->m_pFxLight3TexTech->GetPassByIndex()->GetDesc(&passDesc);
if (FAILED(m_pd3dDevice->CreateInputLayout(layout, numLayoutElements, passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &m_pInputLayout)))
{
MessageBox(nullptr, L"create inputLayout failed!", L"error", MB_OK);
return false;
}
return true;
} void TerrainDemo::UpdateCamera(float dt)
{
//前后左右行走
if (Input::GetInstance()->IsKeyDown('A'))
{
m_camera.Strafe(-.f*dt);
}
else if (Input::GetInstance()->IsKeyDown('D'))
{
m_camera.Strafe(.f*dt);
}
if (Input::GetInstance()->IsKeyDown('W'))
{
m_camera.Walk(.f*dt);
}
else if (Input::GetInstance()->IsKeyDown('S'))
{
m_camera.Walk(-.f*dt);
} if (Input::GetInstance()->IsMouseMove())
{
float mouseX = Input::GetInstance()->GetMouseX();
float mouseY = Input::GetInstance()->GetMouseY();
if (Input::GetInstance()->IsLMouseDown())
{
float dx = XMConvertToRadians(0.25f*(mouseX - m_lastMousePos.x));
float dy = XMConvertToRadians(0.25f*(mouseY - m_lastMousePos.y)); OutputDebugString(L"left btn click");
m_camera.Pitch(dy);
m_camera.RotateY(dx);
}
m_lastMousePos.x = mouseX;
m_lastMousePos.y = mouseY;
} m_camera.UpdateViewMatrix();
} //读取高度图信息
bool TerrainDemo::ReadRawFile(std::string filePath)
{
std::ifstream inFile;
//二进制方式打开文件
inFile.open(filePath.c_str(), std::ios::binary);
//文件指针移动到末尾
inFile.seekg(, std::ios::end);
//大小为当前缓冲区大小
std::vector<BYTE> inData(inFile.tellg());
//文件指针移动到开头
inFile.seekg(std::ios::beg);
//读取高度信息
inFile.read((char*)&inData[], inData.size());
inFile.close(); m_heightInfos.resize(inData.size());
for (int i = ; i < inData.size(); ++i)
{
m_heightInfos[i] = inData[i];
} return true;
} bool TerrainDemo::InitTerrain(float width, float height, UINT m, UINT n,float scale)
{
m_cellsPerRow = m;
m_cellsPerCol = n;
m_verticesPerRow = m + ;
m_verticesPerCol = n + ;
m_numsVertices = m_verticesPerRow*m_verticesPerCol;
m_width = width;
m_height = height;
m_heightScale = scale; //得到缩放后的高度
for (auto& item : m_heightInfos)
{
item *= m_heightScale;
} //起始x z坐标
float oX = -width * 0.5f;
float oZ = height * 0.5f;
//每一格坐标变化
float dx = width / m;
float dz = height / n; m_vertices.resize(m_numsVertices);
//计算顶点
for (UINT i = ; i < m_verticesPerCol; ++i)
{
float tempZ = oZ - dz * i;
for (UINT j = ; j < m_verticesPerRow; ++j)
{
UINT index = m_verticesPerRow * i + j;
m_vertices[index].pos.x = oX + dx * j;
m_vertices[index].pos.y = m_heightInfos[index];
m_vertices[index].pos.z = tempZ; m_vertices[index].tex = XMFLOAT2(dx*i, dx*j);
}
} //计算索引和法线
//总格子数量:m * n
//因此总索引数量: 6 * m * n
UINT nIndices = m * n * ;
m_indices.resize(nIndices);
UINT tmp = ;
for (UINT i = ; i < n; ++i)
{
for (UINT j = ; j < m; ++j)
{
m_indices[tmp] = i * m_verticesPerRow + j;
m_indices[tmp + ] = i * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j; //计算法线
XMFLOAT3 temp;
ComputeNomal(m_vertices[m_indices[tmp]], m_vertices[m_indices[tmp + ]],
m_vertices[m_indices[tmp + ]], temp);
m_vertices[m_indices[tmp]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp; m_indices[tmp + ] = i * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j + ;
m_indices[tmp + ] = (i + ) * m_verticesPerRow + j; ComputeNomal(m_vertices[m_indices[tmp + ]], m_vertices[m_indices[tmp + ]],
m_vertices[m_indices[tmp + ]], temp);
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp;
m_vertices[m_indices[tmp + ]].normal = temp; tmp += ;
}
} return true;
} //计算法线
void TerrainDemo::ComputeNomal(Vertex& v1, Vertex& v2, Vertex& v3, XMFLOAT3& normal)
{
XMFLOAT3 f1(v2.pos.x - v1.pos.x, v2.pos.y - v1.pos.y, v2.pos.z - v1.pos.z);
XMFLOAT3 f2(v3.pos.x - v1.pos.x, v3.pos.y - v1.pos.y, v3.pos.z - v1.pos.z);
XMVECTOR vec1 = XMLoadFloat3(&f1);
XMVECTOR vec2 = XMLoadFloat3(&f2);
XMVECTOR temp = XMVector3Normalize(XMVector3Cross(vec1, vec2));
XMStoreFloat3(&normal, temp);
} int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int cmdShow)
{
std::shared_ptr<Dx11Base> bd(new TerrainDemo(hInstance));
if (!bd->Init())
return -;
return bd->Run();
}
上一篇:Repeated DNA Sequences


下一篇:the5fire博客对接微信公众平台接口 | the5fire的技术博客