继天空效果之后,这一节简单阐述一点地形生成的基本原理和方法~ 如果哪里说的不对,还望园子的前辈们多多拍砖 ^ ^
首先,我们准备两张图:
grass.dds heightdata.raw
(注:dds为directx支持的特定格式;raw可由photoshop自动生成,在无文件头标准下,其内部仅包含各点颜色值,不再含有其他冗余数据。)
左边这张草地的图我们作基本的地表纹理只用;右边这张怪模怪样的灰度图,其实就是日常大家口中的“高度图”了,我们以其特定点的颜色数据为依据来形成相应顶点的高度,以产生地形的高低错落感~
以下,我们来编写这个基本的地形类——CBaseTerrain:
/-------------------------------------
代码清单:BaseTerrain.h
来自:http://www.cnblogs.com/kenkao
-------------------------------------/
#include"D3DInit.h"
#pragmaonce
classCBaseTerrain
{
public:
CBaseTerrain(void);
~CBaseTerrain(void);
public:
boolCreate(//构建地形
intiSizeX,//原始地形图宽
intiSizeY,//原始地形图高
intiRate,//放大倍率
charszHeightRaw,//原始地形图
charszTexture//地形纹理
);
voidDraw();//绘制地形
voidRelease();//资源释放
floatGetExactHeightAt(floatxCoord,floatzCoord);//获得地形高度
private:
boolLoadHightData(intiSizeX,intiSizeY,charszHeightRaw);//加载地形图
floatGetHeightData(intX,intY){return(float)m_pHeightData[ m_SizeXY+X ];}//获得地形图数据
voidCreateTerrainVertices();//产生顶点缓冲
voidCreateTerrainIndices();//产生索引缓冲
voidGenerateNormals(VertexPosNorColorTexvertices,intindices);//计算全部法线数据
private:
VertexPosNorColorTexm_pVertices;//顶点缓冲区
intm_pIndices;//索引缓冲区
IDirect3DTexture9m_pTexture;//地形纹理指针
unsignedcharm_pHeightData;//地形数据缓冲
intm_SizeX;//地形原始长
intm_SizeY;//地形原始宽
intm_Rate;//地形放大倍率
};BaseTerrain.cpp/-------------------------------------
代码清单:BaseTerrain.cpp
来自:http://www.cnblogs.com/kenkao
-------------------------------------/
#include"StdAfx.h"
#include"BaseTerrain.h"
#include"Ken3DGame.h"
#include"D3DCamera.h"
#include<fstream>
externIDirect3DDevice9g_pD3DDevice;
externCD3DCamerag_pD3DCamera;
CBaseTerrain::CBaseTerrain(void) : m_pVertices(NULL),
m_pIndices(NULL),
m_pTexture(NULL),
m_pHeightData(NULL),
m_SizeX(0),
m_SizeY(0),
m_Rate(1)
{
}
CBaseTerrain::~CBaseTerrain(void)
{
}
voidCBaseTerrain::Release()
{
delete[] m_pHeightData;
ReleaseCOM(m_pTexture);
delete[] m_pVertices;
delete[] m_pIndices;
}
boolCBaseTerrain::Create(intiSizeX,intiSizeY,intiRate,charszHeightRaw,charszTexture)
{
m_Rate=iRate;
//加载高度数据
if(!LoadHightData(iSizeX,iSizeY,szHeightRaw))
returnfalse;
//产生顶点缓冲
CreateTerrainVertices();
//产生索引缓冲
CreateTerrainIndices();
//计算发现数据
GenerateNormals(m_pVertices,m_pIndices);
//产生地形纹理
HRESULT hr=D3DXCreateTextureFromFile(g_pD3DDevice,szTexture,&m_pTexture);
if(FAILED(hr))
returnfalse;
returntrue;
}
voidCBaseTerrain::Draw()
{
//地形绘制
g_pD3DDevice->SetTexture(0,m_pTexture);
g_pD3DDevice->SetFVF(VertexPosNorColorTex::FVF);
g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST,0, m_SizeXm_SizeY, (m_SizeX-1)(m_SizeY-1)2,&m_pIndices[0],
D3DFMT_INDEX32,&m_pVertices[0],sizeof(VertexPosNorColorTex));
}
boolCBaseTerrain::LoadHightData(intiSizeX,intiSizeY,charszHeightRaw)
{
m_SizeX=iSizeX;
m_SizeY=iSizeY;
m_pHeightData=newunsignedchar[iSizeXiSizeY];
memset(m_pHeightData,0, iSizeXiSizeY);
//从.raw地形图读取高度数据
std::ifstream inFile(szHeightRaw, std::ios_base::binary);
if(!inFile)
{
delete[] m_pHeightData;
returnfalse;
}
inFile.read((char)&m_pHeightData[0], iSizeXiSizeY);
inFile.close();
returntrue;
}
voidCBaseTerrain::CreateTerrainVertices()
{
//产生顶点缓冲
//高度图中有多少数据便产生多少顶点,或者你也可以仅仅使用其中的一部分数据,这个很好理解 :)
m_pVertices=newVertexPosNorColorTex[m_SizeXm_SizeY];
inti=0;
for(intz=0; z<m_SizeY; z++)
{
for(intx=0; x<m_SizeX; x++)
{
floaty=GetHeightData(x,z);
m_pVertices[i++]=VertexPosNorColorTex(
xm_Rate, y, zm_Rate,//注意x、z坐标m_Rate倍率的使用,相应的y值即为由地形图获得的高度数据
0,0,0,
D3DXCOLOR_WHITE,
(float)x/30.0f, (float)z/30.0f);
}
}
}
voidCBaseTerrain::CreateTerrainIndices()
{
//产生全部的索引数据
/
------
| / | / |
------
| / | / |
------
索引缓冲包含的元素个数 = 三角形个数 x 3
N 行顶点形成 N-1 行“三角形【对】”,每行三角形对个数为列数 M -1,则总的索引缓冲数 = ( N - 1 ) * ( M - 1 ) * 2 * 3
--------- --------- --- ---
行数 - 1 列数 - 1 三角形对 每个三角形三个顶点
/
m_pIndices=newint[(m_SizeX-1)(m_SizeY-1)6];
inti=0;
for(intz=0; z<m_SizeY-1; z++)
{
for(intx=0; x<m_SizeX-1; x++)
{
//分别为每【对】三角形的顶点索引赋值,注意一下不要被“背面剔除”就可以了 ^ ^
m_pIndices[i++]=zm_SizeX+x;
m_pIndices[i++]=(z+1)m_SizeX+x;
m_pIndices[i++]=zm_SizeX+x+1;
m_pIndices[i++]=(z+1)m_SizeX+x;
m_pIndices[i++]=(z+1)m_SizeX+x+1;
m_pIndices[i++]=zm_SizeX+x+1;
}
}
}
voidCBaseTerrain::GenerateNormals(VertexPosNorColorTexpVertices,intpIndices)
{
//遍历每个三角形
for(inti=0; i<(m_SizeX-1)(m_SizeY-1)2; i++)
{
//获得第一条向量
D3DXVECTOR3 firstVec=D3DXVECTOR3(
pVertices[pIndices[i3+1]]._x-pVertices[pIndices[i3]]._x,
pVertices[pIndices[i3+1]]._y-pVertices[pIndices[i3]]._y,
pVertices[pIndices[i3+1]]._z-pVertices[pIndices[i3]]._z
);
//获得第二条向量
D3DXVECTOR3 secondVec=D3DXVECTOR3(
pVertices[pIndices[i3+2]]._x-pVertices[pIndices[i3]]._x,
pVertices[pIndices[i3+2]]._y-pVertices[pIndices[i3]]._y,
pVertices[pIndices[i3+2]]._z-pVertices[pIndices[i3]]._z
);
//两向量叉乘的该三角形片面的发现数据
D3DXVECTOR3 normal;
D3DXVec3Cross(&normal,&firstVec,&secondVec);
D3DXVec3Normalize(&normal,&normal);
//所得法线数据单位化并累计到组成此片面的三个顶点的法线数据中
D3DXVECTOR3 nfirstVec=D3DXVECTOR3(
pVertices[pIndices[i3]]._nx+=normal.x,
pVertices[pIndices[i3]]._ny+=normal.y,
pVertices[pIndices[i3]]._nz+=normal.z
);
D3DXVECTOR3 nsecondVec=D3DXVECTOR3(
pVertices[pIndices[i3+1]]._nx+=normal.x,
pVertices[pIndices[i3+1]]._ny+=normal.y,
pVertices[pIndices[i3+1]]._nz+=normal.z
);
D3DXVECTOR3 nthirdVec=D3DXVECTOR3(
pVertices[pIndices[i3+2]]._nx+=normal.x,
pVertices[pIndices[i3+2]]._ny+=normal.y,
pVertices[pIndices[i3+2]]._nz+=normal.z
);
}
//遍历全部顶点
for(inti=0;i<m_SizeXm_SizeY;i++)
{
//单位化全部顶点的法线数据
D3DXVECTOR3 nVec;
D3DXVec3Normalize(&nVec,
&D3DXVECTOR3(pVertices[i]._nx, pVertices[i]._ny, pVertices[i]._nz)
);
pVertices[i]._nx=nVec.x;
pVertices[i]._ny=nVec.y;
pVertices[i]._nz=nVec.z;
}
//以上,基本原理即基于所有三角形片面计算全部顶点法线数据而后求平均 :)
}
floatCBaseTerrain::GetExactHeightAt(floatxCoord,floatzCoord)
{
//还原倍率
xCoord/=m_Rate;
zCoord/=m_Rate;
//非法判定
boolinvalid=xCoord<0;
invalid|=zCoord<0;
invalid|=xCoord>m_SizeX-1;
invalid|=zCoord>m_SizeY-1;
if(invalid)
return10;//默认高度10
//获得该点所在三角形【对】四点对应高度
intxLower=(int)xCoord;
intxHigher=xLower+1;
floatxRelative=(xCoord-xLower)/((float)xHigher-(float)xLower);
intzLower=(int)zCoord;
intzHigher=zLower+1;
floatzRelative=(zCoord-zLower)/((float)zHigher-(float)zLower);
floatheightLxLz=GetHeightData(xLower, zLower);
floatheightLxHz=GetHeightData(xLower, zHigher);
floatheightHxLz=GetHeightData(xHigher, zLower);
floatheightHxHz=GetHeightData(xHigher, zHigher);
//判断该点位于三角形对中的哪个,并根据该三角形三点高度进行双线性插值计算最终高度
boolpointAboveLowerTriangle=(xRelative+zRelative<1);
floatfinalHeight;
if(pointAboveLowerTriangle)
{
finalHeight=heightLxLz;
finalHeight+=zRelative(heightLxHz-heightLxLz);
finalHeight+=xRelative(heightHxLz-heightLxLz);
}
else
{
finalHeight=heightHxHz;
finalHeight+=(1.0f-zRelative)(heightHxLz-heightHxHz);
finalHeight+=(1.0f-xRelative)(heightLxHz-heightHxHz);
}
returnfinalHeight;
}
代码看起来挺长,不过其中并没有什么高深的机制 ^ ^
LoadHightData()用于加载高度图数据。
函数中使用的是最基本的C++读取文件二进制数据的方法,意在将raw存储的高度数据导入到内存的unsigned char型缓冲区中,已供生成顶点高度(也就是y坐标)之用~
CreateTerrainVertices()用于产生全部的顶点数据。
很明显,raw中有多少像素,我们这里便产生多少顶点即可。我在代码中提到的放大倍率,其实就是顶点与顶点间的间隔而已~
CreateTerrainIndices()用于产生全部的索引数据。
我在代码中画的那个简陋的图图不知道大家看懂没有,呵呵~ 简单而言就是索引数据永远是三角形个数的3倍,而三角形对数永远是 (行数-1)*(列数-1)。各索引值取得皆有公式,龙书中有详细的阐述,大家看不懂的话可以留言~
GenerateNormals()用于为所有顶点生成法线数据。
要实现地形的光照效果,顶点法线是必不可少的数据成分。单个顶点的法线其实就是其所在三角形面的法线,如果一个顶点为多个三角形共有,则累加之后单位化即可(相当于求平均)~
GetExactHeightAt()用于求取某点的高度。
高度图中仅存有各顶点的高度数据,而地形中并不是每个点都位于顶点的位置。如果某点位于顶点与顶点之间,则我们需要采取双线性差值的方法求得改点合适的高度,其中的原理跟高中学过的相似三角形类似,挺简单~
有关双线性差值的相关理论,可以参看老师为大家翻译的这篇文章:http://shiba.hpe.sh.cn/jiaoyanzu/WULI/showArticle.aspx?articleId=473&classId=4
之后是主体代码:
D3DGame.cpp/-------------------------------------
代码清单:D3DGame.cpp
来自:http://www.cnblogs.com/kenkao
-------------------------------------/
#include"StdAfx.h"
#include"D3DGame.h"
#include"D3DCamera.h"
#include"D3DEffect.h"
#include"CoordCross.h"
#include"SimpleXMesh.h"
#include"Texture2D.h"
#include"D3DSprite.h"
#include"Skybox.h"
#include"SpriteBatch.h"
#include"BaseTerrain.h"
#include<stdio.h>
//---通用全局变量
HINSTANCE g_hInst;
HWND g_hWnd;
D3DXMATRIX g_matProjection;
//---D3D全局变量
IDirect3D9g_pD3D=NULL;
IDirect3DDevice9g_pD3DDevice=NULL;
CMouseInputg_pMouseInput=NULL;
CKeyboardInputg_pKeyboardInput=NULL;
CD3DCamerag_pD3DCamera=NULL;
CCoordCrossg_pCoordCross=NULL;
CSimpleXMeshg_pSimpleXMesh=NULL;
CD3DEffectg_pD3DEffect=NULL;
CD3DSpriteg_pD3DSprite=NULL;
CTexture2Dg_pTexture2D=NULL;
CSpriteBatchg_SpriteBatch=NULL;
CTexture2Dg_pTexture2D2=NULL;
CD3DEffectg_pD3DEffect2=NULL;
CSkyboxg_pSkybox=NULL;
CBaseTerraing_pBaseTerrain=NULL;
//---HLSL全局变量句柄
D3DXHANDLE g_CurrentTechHandle=NULL;
D3DXHANDLE g_matWorldViewProj=NULL;
D3DXHANDLE g_matWorld=NULL;
D3DXHANDLE g_vecEye=NULL;
D3DXHANDLE g_vecLightDir=NULL;
D3DXHANDLE g_vDiffuseColor=NULL;
D3DXHANDLE g_vSpecularColor=NULL;
D3DXHANDLE g_vAmbient=NULL;
D3DXHANDLE g_CurrentTechHandle2=NULL;
D3DXHANDLE g_Scale=NULL;
//HLSL特效参数设置
voidGetParameters();
voidSetParameters();
voidInitialize(HINSTANCE hInst, HWND hWnd)
{
g_hInst=hInst;
g_hWnd=hWnd;
InitD3D(&g_pD3D,&g_pD3DDevice, g_matProjection, hWnd);
g_pMouseInput=newCMouseInput;
g_pMouseInput->Initialize(hInst,hWnd);
g_pKeyboardInput=newCKeyboardInput;
g_pKeyboardInput->Initialize(hInst,hWnd);
g_pD3DCamera=newCD3DCamera;
}
voidLoadContent()
{
g_pCoordCross=newCCoordCross;
g_pD3DCamera->SetCameraPos(D3DXVECTOR3(0.0f,0.0f,0.0f));
g_pSimpleXMesh=newCSimpleXMesh;
g_pSimpleXMesh->LoadXMesh("teapot.X");
g_pD3DEffect=newCD3DEffect;
g_pD3DEffect2=newCD3DEffect;
g_pD3DEffect->LoadEffect("Light.fx");
g_pD3DEffect2->LoadEffect("Thunder.fx");
GetParameters();
g_pD3DSprite=newCD3DSprite(g_pD3DDevice);
g_SpriteBatch=newCSpriteBatch(g_pD3DDevice);
g_pTexture2D=newCTexture2D;
g_pTexture2D->LoadTexture("img.jpg");
g_pTexture2D2=newCTexture2D;
g_pTexture2D2->LoadTexture("img2.jpg");
g_pSkybox=newCSkybox;
g_pSkybox->Create("Skybox_0.JPG","Skybox_1.JPG","Skybox_2.JPG"
,"Skybox_3.JPG","Skybox_4.JPG","Skybox_5.JPG");
g_pBaseTerrain=newCBaseTerrain;
g_pBaseTerrain->Create(128,128,10,"HeightData_128x128.raw","Grass.dds");
}
voidUpdate()
{
g_pMouseInput->GetState();
g_pKeyboardInput->GetState();
//更新摄影机高度
D3DXVECTOR3 CameraPos=g_pD3DCamera->GetCameraPos();
floatroleHeight=25.0f;
floatTy=g_pBaseTerrain->GetExactHeightAt(CameraPos.x,CameraPos.z)+roleHeight;
g_pD3DCamera->SetCameraPos(D3DXVECTOR3(
CameraPos.x,
Ty,
CameraPos.z));
g_pD3DCamera->Update();
//更新g_pD3DEffect中的视点位置
D3DXVECTOR4 vecEye=D3DXVECTOR4(CameraPos.x,Ty,CameraPos.z,0.0f);
g_pD3DEffect->GetEffect()->SetVector(g_vecEye,&vecEye);
}
voidDraw()
{
//参数设定
SetParameters();
g_pD3DDevice->SetTransform(D3DTS_VIEW,&g_pD3DCamera->GetViewMatrix());
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255),1.0f,0);
if(SUCCEEDED(g_pD3DDevice->BeginScene()))
{
//天空处在无限远处,因此必须最先绘制天空盒
g_pSkybox->Draw();
UINT numPasses;
//开启特效
g_pD3DEffect->BeginEffect(numPasses);
for(UINT i=0;i<numPasses;i++)
{
//开启路径
g_pD3DEffect->GetEffect()->BeginPass(i);
//绘制地形
g_pBaseTerrain->Draw();
//路径结束
g_pD3DEffect->GetEffect()->EndPass();
}
//特效结束
g_pD3DEffect->EndEffect();
g_pD3DDevice->EndScene();
}
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
voidUnloadContent()
{
ReleaseCOM(g_pBaseTerrain);
ReleaseCOM(g_pSkybox);
ReleaseCOM(g_pTexture2D2);
ReleaseCOM(g_pTexture2D);
ReleaseCOM(g_SpriteBatch);
ReleaseCOM(g_pD3DSprite);
ReleaseCOM(g_pD3DEffect2);
ReleaseCOM(g_pD3DEffect);
ReleaseCOM(g_pSimpleXMesh);
ReleaseCOM(g_pCoordCross);
}
voidDispose()
{
ReleaseCOM(g_pD3DCamera);
ReleaseCOM(g_pKeyboardInput);
ReleaseCOM(g_pMouseInput);
ReleaseCOM(g_pD3DDevice);
ReleaseCOM(g_pD3D);
}
voidGetParameters()
{
//获得HLSL中各个全局变量句柄
g_CurrentTechHandle=g_pD3DEffect->GetEffect()->GetTechniqueByName("SpecularLight");
g_matWorldViewProj=g_pD3DEffect->GetEffect()->GetParameterByName(0,"matWorldViewProj");
g_matWorld=g_pD3DEffect->GetEffect()->GetParameterByName(0,"matWorld");
g_vecEye=g_pD3DEffect->GetEffect()->GetParameterByName(0,"vecEye");
g_vecLightDir=g_pD3DEffect->GetEffect()->GetParameterByName(0,"vecLightDir");
g_vDiffuseColor=g_pD3DEffect->GetEffect()->GetParameterByName(0,"vDiffuseColor");
g_vSpecularColor=g_pD3DEffect->GetEffect()->GetParameterByName(0,"vSpecularColor");g_vAmbient=g_pD3DEffect->GetEffect()->GetParameterByName(0,"vAmbient");
g_CurrentTechHandle2=g_pD3DEffect2->GetEffect()->GetTechniqueByName("Technique1");
g_Scale=g_pD3DEffect2->GetEffect()->GetParameterByName(0,"Scale");
}
voidSetParameters()
{
//设定当前技术
g_pD3DEffect->GetEffect()->SetTechnique(g_CurrentTechHandle);
//设定HLSL中的各个参数
D3DXMATRIX worldMatrix;
D3DXMatrixTranslation(&worldMatrix,0.0f,0.0f,0.0f);
g_pD3DEffect->GetEffect()->SetMatrix(g_matWorldViewProj,&(worldMatrixg_pD3DCamera->GetViewMatrix()*g_matProjection));
g_pD3DEffect->GetEffect()->SetMatrix(g_matWorld,&worldMatrix);
D3DXVECTOR4 vLightDirection=D3DXVECTOR4(-5.0f,-1.0f,2.0f,1.0f);
g_pD3DEffect->GetEffect()->SetVector(g_vecLightDir,&vLightDirection);
D3DXVECTOR4 vColorDiffuse=D3DXVECTOR4(0.0f,0.0f,0.0f,1.0f);
D3DXVECTOR4 vColorSpecular=D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f);
D3DXVECTOR4 vColorAmbient=D3DXVECTOR4(0.0f,0.0f,0.0f,1.0f);
g_pD3DEffect->GetEffect()->SetVector(g_vDiffuseColor,&vColorDiffuse);
// g_pD3DEffect->GetEffect()->SetVector(g_vSpecularColor,&vColorSpecular); //暂时不加入镜面高光 ^ ^
g_pD3DEffect->GetEffect()->SetVector(g_vAmbient,&vColorAmbient);
g_pD3DEffect2->GetEffect()->SetTechnique(g_CurrentTechHandle2);
g_pD3DEffect2->GetEffect()->SetFloat(g_Scale,0.8f);
}
我们使用light.fx为新生成的地形添加光照效果。这里我在其原有基础上作了改动。
首先添加一个纹理采样器:
sampler2D TextureSampler = sampler_state
{
magfilter=LINEAR;
minfilter=LINEAR;
mipfilter=LINEAR;
};
然后让像素着色器最终的输出结果累计地形纹理原有的颜色:
return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular + tex2D(TextureSampler, vtop.uv); //---最后的颜色 = 环境光 + 漫射光 + 镜面高光 + 本来颜色(纹理)
我们来看看效果:
山地跟沟壑~ 都是高度图的功劳,呵呵~
主体代码中我注释掉了镜面光的颜色赋值,因为按理说草地是不会反射阳光的。我们不妨换一张冷峻的岩石地表,然后再打开镜面光,试试看效果如何~
怎么样,很耀眼吧?呵呵 ^ ^
到此,以上阐述了地形生成方法中的诸多基本原理,但如果要用于生成大型的地形或者无边界地形的话,仅仅靠这样简单的方法是不堪重负的。不过大家无需担心,地形生成的基本原理大致都是相同的,只不过复杂的大地形在此基础上加入了一些特殊的算法和机制,以动态数据的方法减轻了CPU和显卡的负担。如果后续有机会深度研究这些,我会尽我所能,为大家一一道来~
谢谢~ ^ ^
原文链接: https://www.cnblogs.com/kenkao/archive/2011/07/23/2114767.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/29227
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!