大家好,又见面了,我是你们的朋友全栈君。
地形系统在3d程序中是一个重要的部分,这里介绍一下我正在使用的一个简单的地形类.地形数据可以保存在一张灰度图里面,所谓的灰度图就是一张只有黑色和白色的图片,使用颜色深度代表数据大小.我们可以读取出图片上每个像素的颜色值作为地图中某个位置的高度,下面是地形网格投影在平面上的样子
Terrain::Terrain(const char* strName,BYTE* pHeightMap) {
int nSize=MAP_SIZE * MAP_SIZE;//地图大小平方
FILE *pFile = NULL;
pFile = fopen( strName, "rb" );
if ( pFile == NULL )
return;
fread( pHeightMap, 1, nSize, pFile );
int result = ferror( pFile );
if (result)
return;
fclose(pFile);
vertice=0;
normal=1;
texcood=2;
vbos=new GLuint[3];
int nums=6*(MAP_SIZE-STEP_SIZE)*(MAP_SIZE-STEP_SIZE)/(STEP_SIZE*STEP_SIZE);
vertics=new GLfloat[nums*3];
normals=new GLfloat[nums*3];
texCoods=new GLfloat[nums*2];
planarNum=2*(MAP_SIZE-STEP_SIZE)*(MAP_SIZE-STEP_SIZE)/(STEP_SIZE*STEP_SIZE);
terrainPlanar=new Planar*[planarNum];//保存网格平面方程
}
float Terrain::getHeight(BYTE *pHeightMap, int px, int pz) {
int x = px % MAP_SIZE; // Error Check Our x Value
int z = pz % MAP_SIZE; // Error Check Our y Value
if(!pHeightMap)
return 0; // Make Sure Our Data Is Valid
float y=0;
if(px>=0&&pz>=0)
y=pHeightMap[x + (z * MAP_SIZE)];
return y; // Index Into Our Height Array And Return The Height
}
void Terrain::getNormal(GLfloat p1[3],GLfloat p2[3],GLfloat p3[3],GLfloat* result) {
const GLfloat l1[] = {p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]};
const GLfloat l2[] = {p3[0]-p1[0], p3[1]-p1[1], p3[2]-p1[2]};
GLfloat n[] = {
l1[1]*l2[2] - l1[2]*l2[1],
l1[2]*l2[0] - l1[0]*l2[2],
l1[0]*l2[1] - l1[1]*l2[0]
};
GLfloat abs = sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
n[0] /= abs;
n[1] /= abs;
n[2] /= abs;
result[0]=n[0]/2;
result[1]=n[1]/2;
result[2]=n[2]/2;
}
getHeight方法根据网格的xz坐标获取对应高度,getNormal方法根据平面三点求取法线
以下putTexCoord和putVertic方法则是把纹理坐标数据与定点数据分别放入对应的数组,之后就能把数组储存在顶点缓冲区
void Terrain::putTexCoord(int& curTexCoord,GLfloat u,GLfloat v) {
texCoods[curTexCoord]=u;
curTexCoord++;
texCoods[curTexCoord]=v;
curTexCoord++;
}
void Terrain::putVertic(int& curVert,GLfloat x,GLfloat y,GLfloat z) {
vertics[curVert]=x;
curVert++;
vertics[curVert]=y;
curVert++;
vertics[curVert]=z;
curVert++;
}
接着构造地形网格
GLfloat x, y, z;
GLfloat u,v;
int curVert=0;
int curNorm=0;
int curTexCood=0;
if(!pHeightMap)
return;
int curPlanar=0;
for (int i = 0; i < (MAP_SIZE-STEP_SIZE); i += STEP_SIZE ) {
for (int j = 0; j < (MAP_SIZE-STEP_SIZE); j += STEP_SIZE ) {
x = (GLfloat)i;
z = (GLfloat)j;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=0.0f;
v=0.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p1(x,y,z);
x = (GLfloat)i;
z = (GLfloat)(j + STEP_SIZE) ;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=0.0f;
v=1.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p2(x,y,z);
x = (GLfloat)(i + STEP_SIZE);
z = (GLfloat)(j + STEP_SIZE) ;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=1.0f;
v=1.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p3(x,y,z);
x = (GLfloat)i;
z = (GLfloat)j;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=0.0f;
v=0.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p4(x,y,z);
x = (GLfloat)(i + STEP_SIZE);
z = (GLfloat)(j + STEP_SIZE) ;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=1.0f;
v=1.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p5(x,y,z);
x = (GLfloat)(i + STEP_SIZE);
z = (GLfloat)j;
y = (GLfloat)getHeight(pHeightMap, x, z );
u=1.0f;
v=0.0f;
putNormal(curNorm,pHeightMap,x,y,z);
putTexCoord(curTexCood,u,v);
putVertic(curVert, x, y, z);
VECTOR3D p6(x,y,z);
Planar* planar=new Planar(p1,p2,p3);
terrainPlanar[curPlanar]=planar;
curPlanar++;
Planar* planarx=new Planar(p4,p5,p6);
terrainPlanar[curPlanar]=planarx;
curPlanar++;
}
}
此方法用于构造地形网格数据以及网格平面数据,其中的MAP_SIZE是地图的宽度与高度,STEP_SIZE是每个网格在xz平面上的纵向与横向间隔大小.
此方法计算网格平面的法线,注意一个顶点并不是只属于一个平面,而是属于临近的6个平面,因此一个顶点将会计算出6条法线
void Terrain::putNormal(int& curNorm,BYTE* pHeightMap,GLfloat x,GLfloat y,GLfloat z) {
GLfloat v1[]={0,0,0};
GLfloat v2[]={0,0,0};
GLfloat v3[]={0,0,0};
GLfloat v4[]={0,0,0};
GLfloat v5[]={0,0,0};
GLfloat v6[]={0,0,0};
GLfloat nf[]={0,0,0};
GLfloat c1[]={x,y,z};
GLfloat u1[]={x,getHeight(pHeightMap,x,z+STEP_SIZE),z+STEP_SIZE};
GLfloat r1[]={x+STEP_SIZE,getHeight(pHeightMap,x+STEP_SIZE,z),z};
GLfloat d1[]={x,getHeight(pHeightMap,x,z-STEP_SIZE),z-STEP_SIZE};
GLfloat l1[]={x-STEP_SIZE,getHeight(pHeightMap,x-STEP_SIZE,z),z};
GLfloat dl1[]={x-STEP_SIZE,
getHeight(pHeightMap,x-STEP_SIZE,z-STEP_SIZE),z-STEP_SIZE};
GLfloat ur1[]={x+STEP_SIZE,
getHeight(pHeightMap,x+STEP_SIZE,z+STEP_SIZE),z+STEP_SIZE};
getNormal(c1,l1,u1,v1);
getNormal(c1,ur1,r1,v2);
getNormal(c1,r1,d1,v3);
getNormal(c1,d1,dl1,v4);
getNormal(c1,dl1,l1,v5);
getNormal(c1,u1,ur1,v6);
normalize(v1,v2,v3,v4,v5,v6,nf);
normals[curNorm]=nf[0];
curNorm++;
normals[curNorm]=nf[1];
curNorm++;
normals[curNorm]=nf[2];
curNorm++;
}
接着求法线,要
求出6条法线的平均值
void Terrain::normalize(GLfloat* v1,GLfloat* v2,GLfloat* v3,GLfloat* v4,
GLfloat* v5,GLfloat* v6,GLfloat* result) {
GLfloat x=v1[0]+v2[0]+v3[0]+v4[0]+v5[0]+v6[0];
GLfloat y=v1[1]+v2[1]+v3[1]+v4[1]+v5[1]+v6[1];
GLfloat z=v1[2]+v2[2]+v3[2]+v4[2]+v5[2]+v6[2];
GLfloat l=sqrt(x*x+y*y+z*z);
result[0]=x/l;
result[1]=y/l;
result[2]=z/l;
}
float Terrain::calHeight(float cx,float cz,float scale,float scaleY) {
float px=-MAP_SIZE/2+STEP_SIZE/2;
float pz=-MAP_SIZE/2+STEP_SIZE/2;
float cy=0;
float posx=cx-px*scale;
float posz=cz-pz*scale;
int x=posx/(STEP_SIZE*scale);
int z=posz/(STEP_SIZE*scale);
int colNum=(MAP_SIZE-STEP_SIZE)/STEP_SIZE;
int num=x*colNum+z;
num*=2;
float ux=cx/scale-px;
float uz=cz/scale-pz;
if(x>=0&&x<colNum&&z>=0&&z<colNum) {
Planar* target=terrainPlanar[num];
VECTOR3D p(ux,target->getY(ux,uz),uz);
bool isIn=target->pointInPlanar(p);
if(!isIn)
target=terrainPlanar[num+1];
float uy=target->getY(ux,uz);
float py=-MAP_SIZE/10;
float ny=(uy+py)*scaleY;
cy=-ny;
}
return cy;
}
其中cx与cz是输入的xz坐标值,scale是xz方向的缩放大小,scaleY是y方向的缩放大小,最终的结果即使地形上的高度值.
编译运行,结果就是这个样
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141544.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...