龙书笔记(13)

chap 13 地形绘制基础

主要是创建一个 地形类 (Terrain)


1.高度图
其实是一个数组,每个元素都指定了地形方格中某一顶点的高度值,每个元素只分配了1个字节的存储空间,
当加载到程序时,重新分配 浮点型 或 整型 数据来存储这些高度值,高度图的图形表示有很多,

比如:灰度图,地形中某点海拔越高,相应点在灰度图中的亮度越大


(1)创建高度图
用photoshop完成是不错的选择,一旦完成高度图的创建,将其保存为 8位 的RAW文件
RAW文件:仅连续存储图像中以字节为单位的每个像素的灰度值,他的特点是读取方便,可以理解为一个连续的字节存储块

当photoshop提示是否为RAW文件增加一个文件头时,选择"否"


(2)加载RAW文件
	std::vector<int> _heightmap;				//分配整型数据来存储高度值
	bool Terrain::readRawFile(std::string fileName)
	{
		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], in.size() );	//把RAW文件中的内容读入到BYTE容器in里面
		inFile.close();
		_heightmap.resize( _numVertices );
		for(int i = 0; i<in.size(); i++)
			_heightmap[i] = in[i];			//再把in的内容赋给int容器_heightmap,这样就得到了每个顶点的整型 高度值
		return true;
	}


(3)访问和修改高度值
	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; 
	}


2.创建地形集合信息
每行顶点数 和 单元数,每列顶点数 和 单元数,高度比例因子,总宽度,总深度,总顶点数,三角元数
地形顶点(TerrainVertex) 是 一个嵌套类,他只在地形类(Terrain)中有用

(1)顶点计算
	bool Terrain::computeVertices()
	{
		HRESULT hr = 0;
		hr = _device->CreateVertexBuffer(
			_numVertices * sizeof(TerrainVertex);
			D3DUSAGE_WRITEONLY,
			TerrainVertex::FVF,
			D3DPOOL_MANAGED,
			&_vb,
			0
		);
		if(FAILED(hr))
			return false;
		int startX = -_width/2;
		int startZ =  _depth/2;
		int endX =  _width/2;
		int endZ = -_depth/2;
		float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow;
		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)
			{
				int index = i * _numVertsPerRow + j;
				v[index] = Terrain(
					(float)x,
					(float)_heightmap[index],
					(float)z,
					(float)j*uCoordIncrementSize,
					(float)i*vCoordIncrementSize
				);
				j++;
			}
			i++;
		}
		_vb->Unlock();
		return true;
	}


(2)索引计算
	//为什么要用索引,因为创建的顶点是按行创建的,不是按三角元创建的
	bool Terrain::computeIndices()
	{
		HRESULT hr = 0;
		hr = _device->CreateIndexBuffer(
			_numTriangles * 3 * sizeof(WORD),
			D3DUSAGE_WRITEONLT,
			D3DFMT_INDEX16,
			D3DPOOL_MANAGED,
			&_ib,
			0
		);
		if(FAILED(hr))
			return false;
		WORD *indices = 0;
		_ib->Lock(0,0,(void**)&indices,0);
		int baseIndex = 0;
		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;
				baseIndex += 6;
			}
		}
		_ib->Unlock();
		return true;
	}


3.纹理映射


方案一:
	bool Terrain::LoadTexture(std::string fileName)
	{
		HRESULT hr = 0;
		hr = D3DXCreateTextureFromFile(
			_device,
			fileName.c_str(),
			&_tex
		);
		if(FAILED(hr))
			return false;
		return true;
	}

方案二:
还有另一种纹理映射的方式,他的主要思路是使用一个"空"的纹理,然后基于一些已定义好的参数计算出纹理元的颜色,
例如,可以根据顶点不同的高度给出不同的颜色
(1)D3DXCreateTexture 创建一个空纹理
(2)由于一个纹理对象可有多级渐近纹理,所以要先 锁定 顶层纹理
(3)遍历每个纹理元并对其上色
//第一种方法是 通过文件去创建纹理,可以理解为直接粘贴纹理图
//第二种方法是 创建空纹理,然后再依据某些特定属性去生成的"立即纹理"
			bool Terrain::genTexture(D3DXVECTOR3 * directionToLight)
			{
				HRESULT hr = 0;
				int texWidth  = _numCellsPerRow;
				int texHeight = _numCellsPerCol;
				
				hr = D3DXCreateTexture(
					_device,
					texWidth, texHeight,
					0,			//创建一个完整的多级渐近纹理链
					0,			//Usage
					D3DFMT_X8R8G8B8,
					D3DPOOL_MANAGED,
					&_tex
				);
				if(FAILED(hr))
					return false;
				//确保顶点的格式	
				D3DSURFACE_DESC textureDesc;
				_tex -> GetLevelDesc(0, &textureDesc);
				if(textureDesc.Format != D3DFMT_X8R8G8B8)
					return false;
				
				D3DLOCKED_RECT lockedRect;
				_tex -> LockRect(
					0,
					&lockedRect,
					0,
					0
				);
				DWORD *imageData = (DWORD*)lockedRect.pBits;
				//pitch每行字节数,pBits第一个字节地址
				for(int i = 0; i < texHeight; i++)
				{
					for(int j = 0; j < texWidth; j++)
					{
						D3DXCOLOR c;
						float height = (float)getHeightmapEntry(i,j);
						
						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;
						
						imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
					}
				}
				_tex->UnlockRect(0);
				if(!lightTerrain(directionToLight))				//照亮地形,主要是明暗因子的计算
				{
					::MessageBox(0,"lightTerrain() - FAILED",0,0);
					return false;
				}
				hr = D3DXFilterTexture(
					_tex,
					0,
					0,
					D3DX_DEFAULT
				);
				if(FAILED(hr))
					return false;
				return true;
			}


4.光照


方案一:

自己设置灯


方案二:
关键:计算地形三角元的明暗因子,他是一个0到1之间的浮点数

使用指定 到达光源的方向 来描述平行光 的光向量(L) 是为了更适合 漫射光光照的 计算
每个方格的面法向量设为N
注意:光的方向向量应为单位向量,这是为了求后面的cosine值,因为面的法向量也将被单位化,在对LN进行 点乘 的时候,直接得到明暗因子

LN夹角越大,此方格接受到的光照就越少,当这个角度超过了90度,很明显,光就照不到方格了
		//计算每个面的明暗因子
		float Terrain::ComputeShade(int cellRow, int cellCol, D3DXVECTOR3 *directionToLight)
		{
			float heightA = getHeightEntry(cellRow, cellCol);
			float heightB = getHeightEntry(cellRow, cellCol + 1);
			float heightC = getHeightEntry(cellRow + 1, cellRow);
			
			D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f);
			D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing);
			
			D3DXVECTOR3 n;
			D3DXVec3Cross(&n, &u, &v);
			D3DXVec3Normalize(&n, &n);
			
			float cosine = D3DXVec3Dot(&n, directionToLight);		//计算出法向量和光照方向的cos值,这是一个0~1之间的数
			
			if(cosine<0.0f)
				cosine = 0.0f;
			return cosine;
		}
		//对地形进行着色,也称 明暗处理
		DWORD *imageData = (DWORD*)lockedRect.pBits;
		for(int i = 0; i<textureDesc.Height; i++)
		{
			for(int j = 0; j<textureDesc.Width; j++)
			{
				int index = i * lockedRect.Pitch / 4 + j;
				D3DXCOLOR c(imageData[index]);
				
				c *= ComputeShade(i, j, directionToLight );
				iamgeData[index] = (D3DCOLOR)c;
			}
		}


5.在地形中“行走”


关键点:得到对应x,z坐标的高度值
		float Terrain::getHeight(float x, float z)
		{
			x = ((float)_width / 2.0f) + x;					
			z = ((float)_depth / 2.0f) - z;
			x /= (float)_cellSpacing;
			z /= (float)_cellSpacing;
			float col = ::floorf(x);
			float row = ::floorf(z);						//以上可以求出坐标所在的方格
			float A = getHeightmapEntry(row,   col);
			float B = getHeightmapEntry(row,   col+1);
			float C = getHeightmapEntry(row+1, col);
			float D = getHeightmapEntry(row+1, col+1);		//得此方格的4个顶点
			float dx = x - col;								//把此方格移到起始点
			float dz = z - row;
			float height = 0.0f;
			if(dz < 1.0f - dx) 								//判断此点是位于方格的上三角面还是下三角面
			{
				float uy = B - A;
				float vy = C - A;
				height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz);
			}
			else 
			{
				float uy = C - D; 
				float vy = B - D; 
				height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz);
			}
			return height;									//求得坐标所在点的高度值
		}		


关于龙书13章地形绘制的terrain项目运行出错问题 (注:龙书即:《DirectX9.0 3D游戏开发编程基础》) 在学习该教材时,当我们试着编译并运行13章地形绘制的terrain项目时,发现运行出错,并弹出了一个出错提示窗口,提示我们::访问vector 越界了 提示窗口的内容如下: -------------------------------------------------------------------- Microsoft Visual C++ Debug Library Debug Assertion failed! Program:...cuments and Settings\Administrator\Terrain\Debug\Terrain.exe File: d:\microsoft visual studio 10.0\vc\include\vector Line:932 Expression : vector subscript out of range For information on how your program can cause an assertion Failure , see the Visual C++ documentation on asserts. (Press Retry to debug the application) [终止(A)] [重试(R)] [忽略(I)] ---------------------------------------------------------------------------------- 问题主要出现在terrain.cpp 文件中的几个函数内部传递参数最终作为了vector的索引值,得到的索引值没有经过限定,导致超出了vector的界限-----最终访问越界. 修改的地方在下面这几处: 在terrain.cpp文档里搜索”//修改过!!!” bool Terrain::genTexture(D3DXVECTOR3* directionToLight) float Terrain::computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLight) float Terrain::getHeight(float x, float z) 在terrain.cpp文档里搜索”//注意这里!!!” /int Terrain::getHeightmapEntry(int row, int col) bool Terrain::lightTerrain(D3DXVECTOR3* directionToLight) 在terrainDriver.cpp文档里搜索”//注意这里!!!” float height = TheTerrain->getHeight( pos.x, pos.z ); 我的修改主要是通过在传递索引值的地方,限定索引值的范围,这样就避免了vector访问越界,这也是龙书作者在写该13章的代码时一时所忽略掉的. 只修改过terrain.cpp文件中的内容,其他的都没动.所以可以只将terrain.cpp拷贝到工程中替换掉原来的就可以了.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值