DirectX 9.0 (11) Terrain Rendering

来源:互联网 发布:java代码反编译 编辑:程序博客网 时间:2024/06/06 10:52

作者: i_dovelemon

来源:CSDN

日期:2014 / 9 / 24

关键字:Multi-texturing, Terrian Rendering, Height Map



引言

              在很多游戏中,我们遇到的并不是一望无际的平原,而是有着高低不平的地形。这一节,我们将要了解,如何建立地形。



高度图

              地形有高低之分,在3D图形中,我们使用高度图来表述一个地形的高低起伏。高度图是一个像素矩阵,它保存了地形网格中每一个顶点的高度值。我们所要做的就是,根据地形的尺寸,创建适合大小的高度图,并且设定不同的高度值,来描述地形。有很多的工具都够产生高度图,比如Terragen这个工具还有Bryce 5.5也能够创建出高度图出来。高度图保存之后,是一个灰度图像,它仅仅是作为高度数据来使用,并不是用来进行纹理映射的纹理图。我们可以将高度图保存为任何我们想要保存的格式。这里为了讲述上的方便,我们使用不带文件头的raw文件来保存高度图。在这个文件中,每一个字节表示相对应的顶点的高度值,它不含有RGB值,仅仅一个8位的值,用来保存高度值而已。为了能够读取这样的文件,我们编写如下的类,用于读取这样的高度图:

<span style="font-family:Microsoft YaHei;">//-------------------------------------------------------------------------------------------// declaration: Copyright (c), by XJ , 2014. All right reserved .// brief: This file will define the Height Map model.// file: HeightMap.h// author: XJ// date: 2014 / 9 / 23// version: 1.0//--------------------------------------------------------------------------------------------#pragma once#include<iostream>using namespace std ;class HeightMap{public:HeightMap();HeightMap(int x, int y);HeightMap(int x, int y, const std::string& fileName, float heightScale, float heightOffset );~HeightMap();void recreate(int x, int y);void loadRaw(int x, int y, const std::string& fileName,float heightScale,float heightOffset);int numRows() const ;int numCols() const ;//For non-const objectsfloat& operator()(int i, int j);//For const objectconst float& operator()(int i, int j)const ;private:bool inBounds(int i, int j);float sampleHeight3x3(int i, int j);void filter3x3();private:std::string m_sFileName ;float       *m_pHeightMap;float       m_fHeightScale;float       m_fHeightOffset;unsigned int m_uiNumCols;unsigned int m_uiNumRows;};// end for class</span>

<span style="font-family:Microsoft YaHei;">#include"HeightMap.h"#include<fstream>using namespace std ;/*** Constructor*/HeightMap::HeightMap(){m_pHeightMap = NULL ;m_fHeightScale = 0 ;m_fHeightOffset = 0 ;m_uiNumRows = 0 ;m_uiNumCols = 0 ;}HeightMap::HeightMap(int x, int y){m_pHeightMap = NULL ;m_fHeightScale = 0 ;m_fHeightOffset = 0 ;m_uiNumRows = x ;m_uiNumCols = y ;recreate(x,y);}HeightMap::HeightMap(int x, int y, const std::string& fileName,float heightScale, float heightOffset){m_sFileName = fileName ;m_pHeightMap = NULL ;m_fHeightScale = heightScale ;m_fHeightOffset = heightOffset ;m_uiNumRows = x ;m_uiNumCols = y ;loadRaw(x,y, m_sFileName, m_fHeightScale, m_fHeightOffset);}/*** Destructor*/HeightMap::~HeightMap(){if(m_pHeightMap)delete[]m_pHeightMap;m_pHeightMap = NULL ;}/*** Create an m*n heightmap with heights initializa zero*/void HeightMap::recreate(int x, int y){m_pHeightMap = new float[x * y];memset(m_pHeightMap, 0, sizeof(float) * (x * y));}// end for recreate/*** Load the heightmap from the .raw file which does not have the file header*/void HeightMap::loadRaw(int x, int y, const std::string& fileName,float heightScale,float heightOffset){//open the fileifstream input;input.open(fileName,ios_base::binary);if(input.fail())return ;unsigned char * buffer = new unsigned char[x * y];input.read((char*)&buffer[0], (streamsize)(x * y) * sizeof(unsigned char));input.close();//allocate the memory the map datam_pHeightMap = new float[x * y];//scale and offset the height valuefor(int i = 0 ; i < y ; i ++){for(int j = 0 ; j < x ;j ++){m_pHeightMap[i * m_uiNumCols + j] = (float)buffer[i * m_uiNumCols + j] * m_fHeightScale + m_fHeightOffset;}// end for j}// end for idelete []buffer ;//filter3x3();}// end for loadRaw/*** Sample the specific height value according the box filter*/float HeightMap::sampleHeight3x3(int x,int y){float sum = 0 , avg = 0 ;unsigned int num = 0 ;for( int i = x -1 ; i <= x + 1 ; i ++){for( int j = y - 1 ; j <= y + 1 ; j ++){if(inBounds(i,j)){sum += m_pHeightMap[i * m_uiNumCols + j];num ++ ;}}// end for j}// end for iavg = sum / num ;return avg ;}// end for sampleHeight3x3/*** Fileter*/void HeightMap::filter3x3(){float *buffer = new float[m_uiNumCols * m_uiNumRows];memset(buffer, 0, sizeof(float)*(m_uiNumCols * m_uiNumRows));for(int i = 0 ; i < m_uiNumRows ; i ++){for(int j = 0 ; j < m_uiNumCols ; j ++){buffer[i * m_uiNumCols + j] = sampleHeight3x3(i,j);}// end for j}// end for imemcpy(m_pHeightMap, buffer, sizeof(float) * (m_uiNumCols * m_uiNumRows));}// end for filter3x3/*** Check if the coordinate is in the range*/bool HeightMap::inBounds(int i, int j){if( i < 0 || i > m_uiNumCols) return false ;if( j < 0 || j > m_uiNumRows) return false ;return true ;}// end for inBounds/*** Return the num of raws*/int HeightMap::numRows() const{return m_uiNumRows ;}// end for numRows/*** Return the num of cols*/int HeightMap::numCols() const{return m_uiNumCols ;}// end for numCols/*** Operator*/float& HeightMap::operator()(int i, int j){return m_pHeightMap[j * m_uiNumCols + i] ;}// end for ()const float& HeightMap::operator()(int i, int j) const{return m_pHeightMap[j * m_uiNumCols + i];}// end for ()</span>

                 这个类的功能很简单,读取指定的文件,进行解析,然后根据输入的缩放值和偏移量,来对高度值进行变换。上面的类还提供了一系列的访问函数,用于访问内部数据。

                 在上面的函数中,有一个函数filter3x3,需要说明下。我们知道,如果只用一个字节来表示高度的话,那么就是说我们只能够表示256个高度级别。因为你不管对高度值进行怎样的放大缩小,不同高度值的种类总是只有256种,也就是说一个字节的高度值,只能够表示256个级别的高度变化。所以,当我们想要的高度很高的时候,比如说0-256000的时候,那么将0-256000使用高度图进行变换的话,即使两个高度值是连续的,它们两个高度之间也相差1000个单位。这样看上去很不平滑。我们这里使用简单的box filter来对高度值进行滤波处理。使用围绕当前采样的高度值所在的8个值,进行平均运算,得到一个平均值来表示当前的高度值。使用这样的方法,就能够出现较平滑的高度渐进了。如果想要更加平滑的效果,那么可以使用加权的box filter,进行高度值的滤波处理。下图是不使用box filter与使用box filter进行高度采样的区别:

                  上图是使用box filter进行滤波处理之后的图,下图是没有进行box filter滤波处理之后的图:

                  从上图的比较可以看出,使用box filter之后,图像显得平滑了很多,不那么尖锐。


构建地形网格

                   在我们对高度图进行了处理之后,我们就需要根据高度图来进行地形网格的构造了。下面的函数是进行地形网格构造的方法:

<span style="font-family:Microsoft YaHei;">void CubeDemo::genCube(){    //Create the height map    m_pMap = new HeightMap(129, 129, "heightmap17_129.raw", 0.25f, 0.0f);    //Build the grid geometry    std::vector<D3DXVECTOR3> verts ;    std::vector<WORD>     indices ;    int vertRows = 129 ;    int vertCols = 129 ;    float dx = 1.0f , dz = 1.0f ;    genTriGrid(vertRows, vertCols, dx, dz, verts, indices);    //Calculate the number of vertices    int numVerts = vertRows * vertCols ;        //Calculate the number of faces    int numTris = (vertRows - 1) * (vertCols - 1) * 2 ;    //Create the mesh    D3DVERTEXELEMENT9    elems[MAX_FVF_DECL_SIZE];    UINT numElements = 0 ;    HR(VertexPNT::_vertexDecl->GetDeclaration(elems, &numElements));    HR(D3DXCreateMesh(numTris, numVerts, D3DXMESH_MANAGED,elems,        m_pDevice,&m_TerrianMesh));    //Lock the vertex buffer    VertexPNT* v = NULL ;    HR(m_TerrianMesh->LockVertexBuffer(0, (void**)&v));    //Calculate the width and depth    float w = (vertCols - 1) * dx ;    float d = (vertRows - 1) * dz ;        //Write the vertex    for(int i = 0 ; i < vertRows ; i ++)    {        for(int j = 0 ; j < vertCols ; j ++)        {            DWORD index = i * vertCols + j ;            v[index]._pos = verts[index];            v[index]._pos.y = (float)(*m_pMap)(j, i) ;            v[index]._normal = D3DXVECTOR3(0.0f, 1.0f, 0.0f);            v[index]._tex.x = (v[index]._pos.x + 0.5f * w) / w ;            v[index]._tex.y = (v[index]._pos.z - 0.5f * d) / (-d) ;        }    }    //Unlock the vertex buffer    HR(m_TerrianMesh->UnlockVertexBuffer());    //Write the indices and attribute buffer    WORD* k = 0 ;    HR(m_TerrianMesh->LockIndexBuffer(0, (void**)&k));    DWORD * attr = 0 ;    HR(m_TerrianMesh->LockAttributeBuffer(0, (DWORD**)&attr));    //Compute the index buffer for the grid    for(int i = 0 ; i < numTris ; i ++)    {        k[i * 3 + 0] = (WORD)indices[i * 3 + 0];        k[i * 3 + 1] = (WORD)indices[i * 3 + 1];        k[i * 3 + 2] = (WORD)indices[i * 3 + 2];        attr[i] = 0 ;  //Always subset 0    }    //Unlock the index buffer    HR(m_TerrianMesh->UnlockIndexBuffer());    HR(m_TerrianMesh->UnlockAttributeBuffer());    //Generate normals and then opimize the mesh    HR(D3DXComputeNormals(m_TerrianMesh,0));    DWORD* adj = new DWORD[m_TerrianMesh->GetNumFaces() * 3];    HR(m_TerrianMesh->GenerateAdjacency(1.0f, adj));    HR(m_TerrianMesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE|        D3DXMESHOPT_ATTRSORT, adj, 0, 0, 0));    delete[]adj;}</span>

<span style="font-family:Microsoft YaHei;">void CubeDemo::genTriGrid(int raws, int cols, float dx, float dz, std::vector<D3DXVECTOR3>& v,std::vector<WORD>& indices){//Generate the verticesfor(int i = 0 ; i < raws ; i ++){for(int j = 0 ; j < cols ; j ++){v.push_back(D3DXVECTOR3(j * dx , 0, -i * dz));}}//Generate the indicesfor(int i = 0 ; i < raws - 1 ; i ++){for(int j = 0 ; j < cols - 1 ; j ++){//Face 1indices.push_back(i * cols + j);indices.push_back(i * cols + j + 1);indices.push_back((i + 1) * cols + j + 1 );//Face 2indices.push_back(i * cols + j);indices.push_back((i + 1) * cols + j + 1);indices.push_back((i + 1) * cols + j);}}//Translate the verticesfor(int i = 0 ; i < raws * cols ; i ++){v[i].x -= (cols - 1) * dx * 0.5f;v[i].z += (raws - 1) * dz * 0.5f;}}// end for genTriGrid</span>

                  我们先根据想要创建的地形尺寸的大小,调用genTriGrid()函数,来构建一个平面的网格模型。genTriGrid进行网格构建的方法很简单。我们先在XZ平面的(X,-Z)平面上构建好网格,然后根据网格的尺寸,对网格中的顶点进行平移,让网格的中心和世界坐标的中心对齐。

                  当我们有了平面的网格之后,我们就根据高度图中的数据,对网格中的顶点的y坐标进行改变,这样就能够创建一个高低起伏的地形网格出来了。

                   下面是我的模型的截图:

                                在使用Phong式着色模型进行点光源的照射,如下所示:


Multi-Texturing

                  在纹理映射技术中,Multi-Texturing用于融合不同的纹理,从而构成一个纹理效果。在本次实例中,我们使用三种不同的纹理,分别是草地纹理,岩石纹理,和路面纹理。在有了这个三个纹理图之后,我们还需要确定,最终形成的纹理图中各个纹理所占的比例分别是多少了?所以,还有另外一个纹理图blendmap。这个纹理图,仅仅是提供一个混合的参数数据,让我们能够在Pixel Shader中判断,上面三个纹理图分别所占的比例。也就是说,在构建第Pij个像素的时候,我们分别从三个纹理图中获取C0ij, C1ij, C2ij。同时获取Blendmap里面的Bij。然后我们根据如下的公式来构建最后的Pij:

                              Pij = w0 * C0ij + w1 * C1ij + w2 * C2ij ;

式中w0 = Bij.R/(Bij.R + Bij.G + Bij.B)  ;

        w1 = Bij.G/(Bij.R + Bij.G + Bij.B)  ;

        w2ij = Bij.B/(Bij.R + Bij.G + Bij.B)  ;

这样,我们就能够进行多纹理映射,从而形成比较丰富的图形效果。下图是进行Multi-Texturing之后的图像:


好了,今天就到这里结束了。后会有期!!!

0 0