首页 > 代码库 > 高度图地形读取与漫游

高度图地形读取与漫游

地形系统在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];//保存网格平面方程
}

其中的terrainPlanar用于储存平面信息,这些信息将会用于之后的地形漫游方法中.

接着用读取的高度数据构造地形网格:
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;
}


渲染所需要的数据计算完了,接着计算地形漫游.

所谓的地形漫游就是能够取得地形上任意点的位置,也就是输入任意的xz坐标即可求得y坐标.
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方向的缩放大小,最终的结果即使地形上的高度值.

编译运行,结果就是这个样


高度图地形读取与漫游