Directx11学习笔记【十三】 实现一个简单地形
本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5510294.html
上一个教程我们实现了渲染一个会旋转的立方体,这次我们来实现一个简单地形。
先来看看最终实现效果吧(蓝色是背景色,地形的不同高度分别渲染了不同颜色)


实现原理其实很简单,我们现在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列的顶点处右下方两个三角形的顶点索引的通用公式。

对顶点缓存中的任意一点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意思明确很容易理解,那么其他三个分别代表什么意思呢?看下面的图就明白了

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()函数
因为同之前教程中代码并没有什么改变,所以不再给出了。
这样运行程序就能得到之前给出图片所示的效果了,是不是很简单呢?
Directx11学习笔记【十三】 实现一个简单地形的更多相关文章
- Linux系统学习笔记之 1 一个简单的shell程序
不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步: ...
- 【opencv学习笔记五】一个简单程序:图像读取与显示
今天我们来学习一个最简单的程序,即从文件读取图像并且创建窗口显示该图像. 目录 [imread]图像读取 [namedWindow]创建window窗口 [imshow]图像显示 [imwrite]图 ...
- Django 学习笔记之六 建立一个简单的博客应用程序
最近在学习django时建立了一个简单的博客应用程序,现在把简单的步骤说一下.本人的用的版本是python 2.7.3和django 1.10.3,Windows10系统 1.首先通过命令建立项目和a ...
- [原创]java WEB学习笔记12:一个简单的serlet连接数据库实验
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- UNP学习笔记2——从一个简单的ECHO程序分析TCP客户/服务器之间的通信
1 概述 编写一个简单的ECHO(回复)程序来分析TCP客户和服务器之间的通信流程,要求如下: 客户从标准输入读入一行文本,并发送给服务器 服务器从网络输入读取这个文本,并回复给客户 客户从网络输入读 ...
- Ruby学习笔记2 : 一个简单的Ruby网站,搭建ruby环境
Ruby on Rails website 的基础是 请求-返回 循环. 首先是浏览器请求服务器, 第二步,Second, in our Rails application, the route ta ...
- 【Python学习笔记三】一个简单的python爬虫
这里写爬虫用的requests插件 1.一般那3.x版本的python安装后都带有相应的安装文件,目录在python安装目录的Scripts中,如下: 2.将scripts的目录配置到环境变量pa ...
- DirectX11 学习笔记3 - 创建一个立方体 和 轴
该方案将在进一步的程序 面向对象. 独立的模型类.更像是一个框架. 其中以超过遇到了一个非常有趣的问题,.获得一晚.我读了好几遍,以找到其他的列子.必须放在某些功能Render里面实时更新,而不是仅仅 ...
- DuiLib学习笔记2——写一个简单的程序
我们要独立出来自己创建一个项目,在我们自己的项目上加皮肤这才是初衷.我的新建项目名为:duilibTest 在duilib根目录下面有个 Duilib入门文档.doc 我们就按这个教程开始入门 首先新 ...
- avalonjs学习笔记之实现一个简单的查询页
官网地址:http://avalonjs.coding.me/ 因为是为了学习js,所以对样式没什么要求,先放效果图: 步骤为:初始页面-------条件查询-------编辑员工1-------保存 ...
随机推荐
- make工具与Makefile文件
make工具与Makefile文件 阅读目录 1. make工具 2. Makefile文件 3. Makefile的简单示例 4. 伪目标 5. Makefile 自动化变量 6. 编译生成多个可执 ...
- HDU1754_I Hate It(线段树/单点更新)
解题报告 题意: 略 思路: 单点替换,区间最值 #include <iostream> #include <cstring> #include <cstdio> ...
- 更改Oracle实例的字符集
(1).数据库服务器字符集select * from nls_database_parameters 来源于props$,是表示数据库的字符集. (2).服务端字符集环境select * from n ...
- 番外:android模拟器连不上网
1.删除你PC端得备用DNS,只留一个即可.确保能够上网. 注意:这个虽然不是必须的,出错点也不一定在他,但是我建议这样做,因为我们不确定到底模拟器和我们的PC是否使用的是一个DNS,不是的话,就会造 ...
- Codeforces Round #248 (Div. 1)——Ryouko's Memory Note
题目连接 题意: 给n和m,一行m个1<=x<=n的数.记c=.如今仅仅能选择一个数x变成y,序列中全部等于x的值都变成y,求最小的c 分析: 对于一个数x,把与他相邻的所有的非x的数所有 ...
- iOS ,呼叫捕获抛出勉未知方法的障碍
iOS 捕获未知方法的调用,避勉抛出异常 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 ...
- shell 脚本之if、for、while语句
(1)if语句 root@ubuntu:/mnt/shared/shellbox/shellif# cat shellif.sh #!/bin/bash #推断字符串 if [ "$1&qu ...
- Java UML描述
开发Java应用程序时,开发者要想有效地利用统一建模语言(UML),必须全面理解UML元素以及这些元素如何映射到Java.本文重点讨论UML类图中的元素. 类图是最常用的UML图,它用于描述系统的 ...
- Knockout应用开发指南 第十章:更多信息(完结篇)
原文:Knockout应用开发指南 第十章:更多信息(完结篇) 1 浏览器支持 Knockout在如下浏览器通过测试: Mozilla Firefox 2.0+(最新测试版本:3.6.8) Goo ...
- Oracle连接池
原由:许多用户可能在查询相同的数据库以获取相同的数据.在这些情况下,可以通过使应用程序共享到数据源的连接来提高应用程序的性能.否则,让每个用户打开和关闭单独的连接的开销会对应用程序性能产生不利影响.这 ...