D3D地图Terrain类构建思想和实现

地图类的构建思想

其实无论平原,山地,丘陵,沙漠,沙滩都可以用地图网格来表示,巨大的地形网格(网格有顶点向量信息,材质光照,纹理信息)和场景物体,摄像机位置等信息来表达就可以了(2D中需要用45度菱形地图实现3D场景效果,3D中不需要的)。

实现网格信息的步骤:

1.地图高度:从raw灰色图中读取高度图,直接从二进制图片中读取,BYTE转换为int存储因为会放大高度。

2.顶点缓存:在地图网格中构建顶点信息,从传入的像素宽度和格子行列数量和格子大小,还有纹理大小来计算网格的顶点向量和uv信息。

3.索引缓存:在地图网格中构建索引信息,从左到右从上到下的遍历小方格的形式记录三角形的索引,通过假设未知量可以很容易得方格中的三角形顶点和索引的关系。

4.预计算地图中的光照,光照方程来自于L是指光源向量和H是三角形法向量之间夹角来简单决定明暗因子,夹角越小明暗因子越大和光照的镜面反射或者漫反射模型类似,但比他们简单没有计算材质反射值和光泽,观察者方向等信息。 这样SetUp预先计算光照颜色,比实时计算光照性能大幅下降,如果需要切换昼夜可以定时触发计算也比实时计算好。

5.创建一个空纹理,填入材质信息和将纹理通过D3DXFilterTexture得到多级渐进纹理mipMap 或者直接从文件里面D3DXCreateTextureFromFile加载多个纹理,用于地图中指定的网格,当绘制不同地图时候需要切换纹理。或者用纹理随机技术生成随机大小和数量植被地表的地图。

6. 传入当前的摄像机像素位置,挪动坐标算出当前所处的格子位置,由格子位置得到左上角的摄像机高度 。再由向量相加的三角法则,对y方向的向量进行加法得到摄像机当前真实高度(当前所处的格子三角形左上还是右下需要判断,还需要对当前摄像机位置偏移左上角位置或者右下角位置,进行插值计算得到当前高度信息,插值是线性插值a0 + (b-a)t计算即可)。

7.丰富地表表现实现方法:多层纹理的融合的混合地表实现,根据alpha值来计算纹理值,可以得到草地和沙地,岩石和雪贴图根据随机的alpha随机融合的得到满意的地表效果。如果要美术绘制复杂地表,也是可以做到的,但是要改变就比较麻烦。

实现代码及其分析注释

#ifndef __terrainH__
#define __terrainH__

#include "d3dUtility.h"
#include <string>
#include <vector>

class Terrain
{
public:
	Terrain(
		IDirect3DDevice9* device,
		std::string heightmapFileName, 
		int numVertsPerRow,  
		int numVertsPerCol, 
		int cellSpacing,    // space between cells
		float heightScale);   

	~Terrain();

	int  getHeightmapEntry(int row, int col);
	void setHeightmapEntry(int row, int col, int value);

	// 传入当前的摄像机像素位置,挪动坐标算出当前所处的格子位置,由格子位置得到左上角的摄像机高度
	// 再由向量相加的三角法则,对y方向的向量进行加法得到摄像机当前真实高度(当前所处的格子三角形左上还是右下需要判断,
	// 还需要对当前摄像机位置偏移左上角位置或者右下角位置,进行插值计算得到当前高度信息,插值是线性插值a0 + (b-a)t计算即可)。
	float getHeight(float x, float z);

	bool  loadTexture(std::string fileName);
	// 创建一个空纹理,填入材质信息和将纹理通过D3DXFilterTexture得到多级渐进纹理mipMap
	// 或者直接从文件里面D3DXCreateTextureFromFile加载多个纹理,用于地图中指定的网格,当绘制不同地图时候需要切换纹理。
	// 或者用纹理随机技术生成随机大小和数量植被地表的地图。
	bool  genTexture(D3DXVECTOR3* directionToLight);

	bool  draw(D3DXMATRIX* world, bool drawTris);

private:
	IDirect3DDevice9*       _device;
	// 地图包含了一张纹理图片,自己创建的
	IDirect3DTexture9*      _tex;
	// 顶点缓存
	IDirect3DVertexBuffer9* _vb;
	// 索引缓存
	IDirect3DIndexBuffer9*  _ib;

	// 每行顶点数
	int _numVertsPerRow;
	// 每列顶点数
	int _numVertsPerCol;
	// 每个小方格的像素数
	int _cellSpacing;

	// 每行的小方格数是每行顶点数减去1
	int _numCellsPerRow;
	// 每列的小方格数是每列顶点数减去1
	int _numCellsPerCol;
	// x方向像素宽度
	int _width;
	// z方向像素深度
	int _depth;
	// 地图的顶点个数
	int _numVertices;
	// 地图的三角形个数
	int _numTriangles;

	// 实际虚拟空间中对高度图的缩放系数,一般比高度图1大
	float _heightScale;

	// 存放对高度图缩放后的地图高度的数据,对应每个顶点
	std::vector<int> _heightmap;

	// helper methods
	// 1.从raw文件中读取高度图,直接从二进制图片中读取,BYTE转换为int存储因为会放大高度
	bool  readRawFile(std::string fileName);

	// 2.在地图网格中构建顶点信息,从传入的像素宽度和格子数量,还有纹理大小计算顶点向量和uv信息
	bool  computeVertices();

	// 3.在地图网格中构建索引信息,从左到右从上到下的遍历小方格的形式记录三角形的索引,
	// 通过假设未知量可以很容易得方格中的三角形顶点和索引的关系
	bool  computeIndices();

	// 4.预计算地图中的光照,光照方程来自于L是指光源向量和H是三角形法向量之间夹角来简单决定明暗因子,夹角越小明暗因子越大
	// 和光照的镜面反射或者漫反射模型类似,但比他们简单没有计算材质反射值和光泽,观察者方向等信息
	// 这样SetUp预先计算光照颜色,比实时计算光照性能大幅下降,如果需要切换昼夜可以定时触发计算也比实时计算好。
	bool  lightTerrain(D3DXVECTOR3* directionToLight);
	// 传入单元格,和到光源的向量,得到明暗因子
	float computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight);

	struct TerrainVertex
	{
		TerrainVertex(){}
		TerrainVertex(float x, float y, float z, float u, float v)
		{
			_x = x; _y = y; _z = z; _u = u; _v = v;
		}
		float _x, _y, _z;
		float _u, _v;

		static const DWORD FVF;
	};
};

#endif 

#include "terrain.h"
#include <fstream>
#include <cmath>

const DWORD Terrain::TerrainVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX1;

Terrain::Terrain(IDirect3DDevice9* device,
				 std::string heightmapFileName,
				 int numVertsPerRow,
				 int numVertsPerCol,
				 int cellSpacing,
				 float heightScale)
{
	_device         = device;
	_numVertsPerRow = numVertsPerRow;
	_numVertsPerCol = numVertsPerCol;
	_cellSpacing    = cellSpacing;

	_numCellsPerRow = _numVertsPerRow - 1;
	_numCellsPerCol = _numVertsPerCol - 1;

	_width = _numCellsPerRow * _cellSpacing;
	_depth = _numCellsPerCol * _cellSpacing;

	_numVertices  = _numVertsPerRow * _numVertsPerCol;
	_numTriangles = _numCellsPerRow * _numCellsPerCol * 2;

	_heightScale = heightScale;

	// load heightmap
	if( !readRawFile(heightmapFileName) )
	{
		::MessageBox(0, "readRawFile - FAILED", 0, 0);
		::PostQuitMessage(0);
	}

	// scale heights
	// 将高度信息,直接乘以缩放比例得到高度图信息
	for(int i = 0; i < _heightmap.size(); i++)
		_heightmap[i] *= heightScale;

	// compute the vertices
	if( !computeVertices() )
	{
		::MessageBox(0, "computeVertices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}

	// compute the indices
	if( !computeIndices() )
	{
		::MessageBox(0, "computeIndices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}
}

Terrain::~Terrain()
{
	d3d::Release<IDirect3DVertexBuffer9*>(_vb);
	d3d::Release<IDirect3DIndexBuffer9*>(_ib);
	d3d::Release<IDirect3DTexture9*>(_tex);
}

int Terrain::getHeightmapEntry(int row, int col)
{
	return _heightmap[row * _numVertsPerRow + col];
}

void Terrain::setHeightmapEntry(int row, int col, int value)
{
	_heightmap[row * _numVertsPerRow + col] = value;
}

bool Terrain::computeVertices()
{
	HRESULT hr = 0;
	// 地图网格中,直接创建顶点缓存,顶点缓存的个数行顶点数乘以列顶点数
	hr = _device->CreateVertexBuffer(
		_numVertices * sizeof(TerrainVertex),
		D3DUSAGE_WRITEONLY,
		TerrainVertex::FVF,
		D3DPOOL_DEFAULT,
		&_vb,
		0);

	if(FAILED(hr))
		return false;

	// coordinates to start generating vertices at
	// 二维地图中的左上角,开始的像素位置
	int startX = -_width / 2;
	int startZ =  _depth / 2;

	// coordinates to end generating vertices at
	int endX =  _width / 2;
	int endZ = -_depth / 2;

	// compute the increment size of the texture coordinates
	// from one vertex to the next.
	// 纹理坐标的u方向的增长,确保了j等于_numCellsPerRow时候为1
	float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow;
	// 纹理坐标的v方向的增长,确保了i等于_numCellsPerCol时候为1
	float vCoordIncrementSize = 1.0f / (float)_numCellsPerCol;

	TerrainVertex* v = 0;
	_vb->Lock(0, 0, (void**)&v, 0);

	int i = 0;
	for(int z = startZ; z >= endZ; z -= _cellSpacing)
	{
		int j = 0;
		for(int x = startX; x <= endX; x += _cellSpacing)
		{
			// compute the correct index into the vertex buffer and heightmap
			// based on where we are in the nested loop.
			// 获取顶点下标,和填充顶点位置和纹理信息
			int index = i * _numVertsPerRow + j;
			v[index] = TerrainVertex(
				(float)x,
				(float)_heightmap[index],
				(float)z,
				(float)j * uCoordIncrementSize,
				(float)i * vCoordIncrementSize);

			j++; // next column
		}
		i++; // next row
	}

	_vb->Unlock();

	return true;
}

bool Terrain::computeIndices()
{
	HRESULT hr = 0;

	hr = _device->CreateIndexBuffer(
		_numTriangles * 3 * sizeof(WORD), // 3 indices per triangle
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_DEFAULT,
		&_ib,
		0);

	if(FAILED(hr))
		return false;

	WORD* indices = 0;
	_ib->Lock(0, 0, (void**)&indices, 0);

	// index to start of a group of 6 indices that describe the
	// two triangles that make up a quad
	int baseIndex = 0;

	// loop through and compute the triangles of each quad
	// 从左到右,从上到下,得到每个三角形的索引
	// 可以通过很简单的观察得到i   * _numVertsPerRow + j,i   * _numVertsPerRow + j + 1,(i+1) * _numVertsPerRow + j
	// 的规律
	for(int i = 0; i < _numCellsPerCol; i++)
	{
		for(int j = 0; j < _numCellsPerRow; j++)
		{
			indices[baseIndex]     =   i   * _numVertsPerRow + j;
			indices[baseIndex + 1] =   i   * _numVertsPerRow + j + 1;
			indices[baseIndex + 2] = (i+1) * _numVertsPerRow + j;

			indices[baseIndex + 3] = (i+1) * _numVertsPerRow + j;
			indices[baseIndex + 4] =   i   * _numVertsPerRow + j + 1;
			indices[baseIndex + 5] = (i+1) * _numVertsPerRow + j + 1;

			// next quad
			baseIndex += 6;
		}
	}

	_ib->Unlock();

	return true;
}

bool Terrain::loadTexture(std::string fileName)
{
	HRESULT hr = 0;

	hr = D3DXCreateTextureFromFile(
		_device,
		fileName.c_str(),
		&_tex);

	if(FAILED(hr))
		return false;

	return true;
}

bool Terrain::genTexture(D3DXVECTOR3* directionToLight)
{
	// Method fills the top surface of a texture procedurally.  Then
	// lights the top surface.  Finally, it fills the other mipmap
	// surfaces based on the top surface data using D3DXFilterTexture.

	HRESULT hr = 0;

	// texel for each quad cell
	int texWidth  = _numCellsPerRow;
	int texHeight = _numCellsPerCol;

	// create an empty texture
	hr = D3DXCreateTexture(
		_device,
		texWidth, texHeight,
		0, // create a complete mipmap chain
		0, // usage
		D3DFMT_X8R8G8B8,// 32 bit XRGB format
		D3DPOOL_MANAGED, &_tex);

	if(FAILED(hr))
		return false;

	D3DSURFACE_DESC textureDesc; 
	_tex->GetLevelDesc(0 /*level*/, &textureDesc);

	// make sure we got the requested format because our code 
	// that fills the texture is hard coded to a 32 bit pixel depth.
	if( textureDesc.Format != D3DFMT_X8R8G8B8 )
		return false;
		
	D3DLOCKED_RECT lockedRect;
	_tex->LockRect(0/*lock top surface*/, &lockedRect, 
		0 /* lock entire tex*/, 0/*flags*/);         

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for(int i = 0; i < texHeight; i++)
	{
		for(int j = 0; j < texWidth; j++)
		{
			D3DXCOLOR c;

			// get height of upper left vertex of quad.
			float height = (float)getHeightmapEntry(i, j) / _heightScale;

			if( (height) < 42.5f ) 		 c = d3d::BEACH_SAND;
			else if( (height) < 85.0f )	 c = d3d::LIGHT_YELLOW_GREEN;
			else if( (height) < 127.5f ) c = d3d::PUREGREEN;
			else if( (height) < 170.0f ) c = d3d::DARK_YELLOW_GREEN;
			else if( (height) < 212.5f ) c = d3d::DARKBROWN;
			else	                     c = d3d::WHITE;

			// fill locked data, note we divide the pitch by four because the
			// pitch is given in bytes and there are 4 bytes per DWORD.
			// lockedRect.Pitch是上一行中结束位置的字节数,lockedRect.Pitch / 4就是DWORD个数
			imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
		}
	}

	_tex->UnlockRect(0);

	// 这里的光照和纹理颜色,是将纹理颜色和光照的明暗因子进行相乘得到最后颜色的结果,并非融合的方式
	if(!lightTerrain(directionToLight))
	{
		::MessageBox(0, "lightTerrain() - FAILED", 0, 0);
		return false;
	}
	
	// 计算出其它多级纹理层中的纹理元
	// 例如_tex是源纹理是256x256的,那么D3DXFilterTexture可以生成其它大小的纹理,例如128x128的,64x64的
	// 这样自定义填充的纹理,就类似于D3DXCreateTextureFromFile一样拥有mipMap Texture多级纹理的纹理。
	hr = D3DXFilterTexture(
		_tex,
		0, // default palette
		0, // use top level as source level
		D3DX_DEFAULT); // default filter

	if(FAILED(hr))
	{
		::MessageBox(0, "D3DXFilterTexture() - FAILED", 0, 0);
		return false;
	}

	return true;
}

bool Terrain::lightTerrain(D3DXVECTOR3* directionToLight)
{
	HRESULT hr = 0;

	D3DSURFACE_DESC textureDesc; 
	_tex->GetLevelDesc(0 /*level*/, &textureDesc);

	// make sure we got the requested format because our code that fills the
	// texture is hard coded to a 32 bit pixel depth.
	if( textureDesc.Format != D3DFMT_X8R8G8B8 )
		return false;
		
	D3DLOCKED_RECT lockedRect;
	_tex->LockRect(
		0,          // lock top surface level in mipmap chain
		&lockedRect,// pointer to receive locked data
		0,          // lock entire texture image
		0);         // no lock flags specified

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for(int i = 0; i < textureDesc.Height; i++)
	{
		for(int j = 0; j < textureDesc.Width; j++)
		{
			// index into texture, note we use the pitch and divide by 
			// four since the pitch is given in bytes and there are 
			// 4 bytes per DWORD.
			// lockedRect.Pitch是上一行中结束位置的字节数,lockedRect.Pitch / 4就是DWORD个数
			int index = i * lockedRect.Pitch / 4 + j;

			// get current color of quad
			D3DXCOLOR c( imageData[index] );

			// shade current quad
			// 颜色乘以明暗因子,得到光照计算后的颜色
			c *= computeShade(i, j, directionToLight);;

			// save shaded color
			imageData[index] = (D3DCOLOR)c;
		}
	}

	_tex->UnlockRect(0);

	return true;
}

float Terrain::computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight)
{
	// get heights of three vertices on the quad
	float heightA = getHeightmapEntry(cellRow,   cellCol);
	float heightB = getHeightmapEntry(cellRow,   cellCol+1);
	float heightC = getHeightmapEntry(cellRow+1, cellCol);

	// build two vectors on the quad
	// 计算uv,y方向上用向量三角规则来进行计算
	D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f);
	D3DXVECTOR3 v(0.0f,         heightC - heightA, -_cellSpacing);

	// find the normal by taking the cross product of two
	// vectors on the quad.
	// 得到平面法向量
	D3DXVECTOR3 n;
	D3DXVec3Cross(&n, &u, &v);
	D3DXVec3Normalize(&n, &n);

	// L.N得到明暗因子
	float cosine = D3DXVec3Dot(&n, directionToLight);

	if(cosine < 0.0f)
		cosine = 0.0f;

	return cosine;
}

bool Terrain::readRawFile(std::string fileName)
{
	// Restriction: RAW file dimensions must be >= to the
	// dimensions of the terrain.  That is a 128x128 RAW file
	// can only be used with a terrain constructed with at most
	// 128x128 vertices.

	// A height for each vertex
	std::vector<BYTE> in( _numVertices );

	std::ifstream inFile(fileName.c_str(), std::ios_base::binary);

	if( inFile == 0 )
		return false;
	// 直接将二进制数据读取到数组中
	inFile.read(
		(char*)&in[0], // buffer
		in.size());// number of bytes to read into buffer

	inFile.close();

	// copy BYTE vector to int vector
	// 这个拷贝也是因为BYTE类型转换为int类型需要引入的
	_heightmap.resize( _numVertices );
	for(int i = 0; i < in.size(); i++)
		_heightmap[i] = in[i];

	return true;
}

float Terrain::getHeight(float x, float z)
{
	float fWidthLimit = _width * 0.5f;
	if( x < -fWidthLimit || x > fWidthLimit )
	{
		return 0.0f;
	}
	float fDepthLimit = _depth * 0.5f;
	if( z < -fDepthLimit || z > fDepthLimit )
	{
		return 0.0f;
	}
	// Translate on xz-plane by the transformation that takes
	// the terrain START point to the origin.
	// x像素宽度从[-w/2,w/2]到[0,w]
	x = ((float)_width / 2.0f) + x;
	// z像素深度从[d/2,-d/2]到[0,d]
	z = ((float)_depth / 2.0f) - z;

	// Scale down by the transformation that makes the 
	// cellspacing equal to one.  This is given by 
	// 1 / cellspacing since; cellspacing * 1 / cellspacing = 1.
	// x,z从像素深度,除以一个小方格的宽度,得到当前所属于的方格数
	x /= (float)_cellSpacing;
	z /= (float)_cellSpacing;

	// From now on, we will interpret our positive z-axis as
	// going in the 'down' direction, rather than the 'up' direction.
	// This allows to extract the row and column simply by 'flooring'
	// x and z:
	// 得到所属的方格数,去小于等于的整数,也就是左上角的方格数
	float col = ::floorf(x);
	float row = ::floorf(z);

	// get the heights of the quad we're in:
	// 
    //  A   B
    //  *---*
    //  | / |
    //  *---*  
    //  C   D

	float A = getHeightmapEntry(row,   col);
	float B = getHeightmapEntry(row,   col+1);
	float C = getHeightmapEntry(row+1, col);
	float D = getHeightmapEntry(row+1, col+1);

	//
	// Find the triangle we are in:
	//

	// Translate by the transformation that takes the upper-left
	// corner of the cell we are in to the origin.  Recall that our 
	// cellspacing was nomalized to 1.  Thus we have a unit square
	// at the origin of our +x -> 'right' and +z -> 'down' system.
	// 得到x,z相对于左上角方格数的偏差,也就是当前高度需要的dx,dz浮点值
	float dx = x - col;
	float dz = z - row;

	// Note the below compuations of u and v are unneccessary, we really
	// only need the height, but we compute the entire vector to emphasis
	// the books discussion.
	// 选择三角形,在x + y = 1直线的左上部分,还是右下部分
	// dz < 1.0f - dx是左上的三角形
	float height = 0.0f;
	if(dz < 1.0f - dx)  // upper triangle ABC
	{
		float uy = B - A; // A->B
		float vy = C - A; // A->C

		// Linearly interpolate on each vector.  The height is the vertex
		// height the vectors u and v originate from {A}, plus the heights
		// found by interpolating on each vector u and v.
		// a0 + (uy-a0)dx线性插值,这里a0是0,也就是得到uy*dx的值
		// 由三角形法则,因为uy,vy可正可负,且dx,dz是两点间的[0,1]之间的偏差值
		// 三角形法则中因为不需要x,z方向的,只需要y方向的值,所以拿高度进行A + dxuy + dzvy就会得到当前的高度值
		height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz);
	}
	else // lower triangle DCB
	{
		//  A   B
		//  *---*
		//  | / |
		//  *---*  
		//  C   D
		float uy = C - D; // D->C
		float vy = B - D; // D->B

		// Linearly interpolate on each vector.  The height is the vertex
		// height the vectors u and v originate from {D}, plus the heights
		// found by interpolating on each vector u and v.
		// 因为dx是[0,dx,1]之间的一个值,现在从上限处开始算所以是1-dx;1-dz也是指后面部分的值
		// uy,vy来决定正负,1-dx, 1-dy只是个相对量。
		height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz);
	}

	// 得到射线机的高度,就可以在上面行走了
	return height;
}

bool Terrain::draw(D3DXMATRIX* world, bool drawTris)
{
	HRESULT hr = 0;

	if( _device )
	{
		_device->SetTransform(D3DTS_WORLD, world);

		_device->SetStreamSource(0, _vb, 0, sizeof(TerrainVertex));
		_device->SetFVF(TerrainVertex::FVF);
		_device->SetIndices(_ib);
		
		// 设置创建好颜色且填充好光照明暗的多级纹理
		_device->SetTexture(0, _tex);

		// turn off lighting since we're lighting it ourselves
		_device->SetRenderState(D3DRS_LIGHTING, false);

		// 绘制时候直接绘制顶点缓存和索引缓存即可
		hr =_device->DrawIndexedPrimitive(
			D3DPT_TRIANGLELIST,
			0,
			0,
			_numVertices,
			0,
			_numTriangles);

		
		_device->SetRenderState(D3DRS_LIGHTING, true);

		// 绘制三角形网格
		if( drawTris )
		{
			// 填充模式 默认是D3DFILL_SOLID,The default value is D3DFILL_SOLID
			_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
			hr =_device->DrawIndexedPrimitive(
				D3DPT_TRIANGLELIST,
				0,
				0,
				_numVertices,
				0,
				_numTriangles);

			_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
		}

		if(FAILED(hr))
			return false;
	}

	return true;
}

调用端代码:

bool Setup()
{
	//
	// Create the terrain.
	//

	D3DXVECTOR3 lightDirection(0.0f, 1.0f, 0.0f);
	TheTerrain = new Terrain(Device, "coastMountain64.raw", 64, 64, 10, 0.5f);
	TheTerrain->genTexture(&lightDirection);
	//TheTerrain->loadTexture("grass.bmp");

	//
	// Create the font.
	//

	FPS = new FPSCounter(Device);

	//
	// Set texture filters.
	//

	Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

	//
	// Set projection matrix.
	//

	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(
			&proj,
			D3DX_PI * 0.25f, // 45 - degree
			(float)Width / (float)Height,
			1.0f,
			1000.0f);
	Device->SetTransform(D3DTS_PROJECTION, &proj);

	return true;
}

void Cleanup()
{
	d3d::Delete<Terrain*>(TheTerrain);
	d3d::Delete<FPSCounter*>(FPS);
}

bool Display(float timeDelta)
{
	//
	// Update the scene:
	//

	if( Device )
	{
		// walk是沿_look轴的移动,地面摄像机不能绕_look旋转
		if( ::GetAsyncKeyState(VK_UP) & 0x8000f )
			TheCamera.walk(100.0f * timeDelta);

		if( ::GetAsyncKeyState(VK_DOWN) & 0x8000f )
			TheCamera.walk(-100.0f * timeDelta);

		// yaw是绕_up轴偏航,地面摄像机是绕世界y轴旋转;地面摄像机不能沿_up上下移动
		if( ::GetAsyncKeyState(VK_LEFT) & 0x8000f )
			TheCamera.yaw(-1.0f * timeDelta);
		
		if( ::GetAsyncKeyState(VK_RIGHT) & 0x8000f )
			TheCamera.yaw(1.0f * timeDelta);

		// MN是沿_right左右摆动
		if( ::GetAsyncKeyState('N') & 0x8000f )
			TheCamera.strafe(-100.0f * timeDelta);

		if( ::GetAsyncKeyState('M') & 0x8000f )
			TheCamera.strafe(100.0f * timeDelta);

		// pith是绕_right轴的仰俯
		if( ::GetAsyncKeyState('W') & 0x8000f )
			TheCamera.pitch(1.0f * timeDelta);

		if( ::GetAsyncKeyState('S') & 0x8000f )
			TheCamera.pitch(-1.0f * timeDelta);

		D3DXVECTOR3 pos;
		TheCamera.getPosition(&pos);
		float height = TheTerrain->getHeight( pos.x, pos.z );
		if( !d3d::gFltEquip( height, 0.0f) )
		{
			pos.y = height + 5.0f; // add height because we're standing up
		}
		
		TheCamera.setPosition(&pos);

		D3DXMATRIX V;
		TheCamera.getViewMatrix(&V);
		Device->SetTransform(D3DTS_VIEW, &V);

		//
		// Draw the scene:
		//

		Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff000000, 1.0f, 0);
		Device->BeginScene();

		D3DXMATRIX I;
		D3DXMatrixIdentity(&I);

		// 绘制地形,直接使用设置好的纹理和预计算的光照明暗纹理颜色,根据顶点索引绘制顶点缓存即可
		if( TheTerrain )
			TheTerrain->draw(&I, true/*false*/);

		// 绘制FPS文字,因为整个顶点缓存比较影响性能
		if( FPS )
			FPS->render(0xffffffff, timeDelta);

		Device->EndScene();
		Device->Present(0, 0, 0, 0);
	}
	return true;
}


你可能感兴趣的:(D3D地图Terrain类构建思想和实现)