DirectX 9.0 (11) Terrain Rendering

Source: Internet
Author: User

Author: I _dovelemon

Source: csdn

Date: 24, 2014/9

Keywords: Multi-texturing, Terrian rendering, height Map



Introduction

In many games, what we encounter is not an endless plain, but an uneven terrain. This section describes how to build a terrain.



Height chart

The terrain has different heights. In 3D graphics, we use a height chart to express the ups and downs of a terrain. A height chart is a pixel matrix that stores the height values of each vertex in the terrain mesh. All we need to do is create a height chart of the appropriate size based on the terrain size, and set different height values to describe the terrain. There are many tools that can generate height graphs. For example, the terragen tool and the Bryce 5.5 tool can also create height graphs. After a height map is saved, it is a grayscale image. It is only used as height data, not a texture map for texture ing. We can save the height chart as any format we want to save. For convenience, we use raw files without a file header to save the height chart. In this file, each byte represents the height value of the corresponding vertex. It does not contain RGB values and is only an 8-bit value, which is used to save the height value. To read such a file, we write the following class to read the height map:

<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>

The function of this class is very simple. Read the specified file, parse it, and then convert the height value based on the input scaling value and offset. The above class also provides a series of access functions for accessing internal data.

In the above function, there is a function filter3x3, which needs to be described. We know that if we use only one byte to represent the height, we can only represent 256 height levels. No matter how much you zoom in or out the height value, there are always only 256 types of different height values. That is to say, a byte height value can only represent 256 levels of height changes. Therefore, when we want a high height, for example, 0-256000, if we want to use a height chart to convert 0-256000, even if the two height values are continuous, the two heights also differ by 1000 units. This looks not smooth. Here we use a simple box filter to filter the height value. Perform an average operation around the eight values of the currently sampled height value to obtain an average value indicating the current height value. With this method, we can see a smoother and more gradual process. If you want a smoother effect, you can use the weighted Box Filter to filter the height. Is the difference between using Box Filter and using Box Filter for height Sampling:

It is the image that is filtered using the box filter. It is the image that is not filtered by the box filter:

From the comparison, we can see that after using the box filter, the image looks much smoother and less acute.


Constructing a terrain grid

After we have processed the height chart, we need to construct the terrain grid based on the height chart. The following function is used to construct a terrain grid:

<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>

We first call the gentrigrid () function to build a plane mesh model based on the terrain size we want to create. Gentrigrid is easy to build a grid. We first build a grid on the (x,-z) plane of the xz Z plane, and then translate the vertices in the grid according to the grid size, align the center of the grid with the center of the world coordinates.

When we have a plane mesh, we will change the Y coordinate of the vertices in the mesh based on the data in the height graph, so that we can create a high and low terrain mesh.

The following is my model:

The following figure shows how to use the Phong color model to illuminate the point light source:


Multi-texturing

In texture ing technology, multi-texturing is used to combine different textures to form a texture effect. In this example, we use three different textures: Grass texture, rock texture, and pavement texture. After we have these three textures, we also need to determine the proportions of each texture in the final texture graph? Therefore, there is another texture map blendmap. This texture map only provides a mixture of parameter data, allowing us to determine in pixel shader the proportions of the above three textures respectively. That is to say, when constructing the PIJ pixel, we obtain c0ij, c1ij, and c2ij from the three texture maps respectively. Obtain bij in blendmap. Then we construct the final PIJ based on the following formula:

Pij = W0 * c0ij + W1 * c1ij + W2 * c2ij;

In formula 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 );

In this way, we can map multiple textures to form a wealth of graphic effects. Is the image after multi-texturing:


Now, it's over here. There will be a period later !!!

DirectX 9.0 (11) Terrain Rendering

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.