DirectX 9.0 (11) Terrain Rendering

作者: 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 objects
	float& operator()(int i, int j);

	//For const object
	const 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 file
	ifstream 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 data
	m_pHeightMap = new float[x * y];

	//scale and offset the height value
	for(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 i

	delete []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 i
	avg = 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 i

	memcpy(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 vertices
	for(int i = 0 ; i < raws ; i ++)
	{
		for(int j = 0 ; j < cols ; j ++)
		{
			v.push_back(D3DXVECTOR3(j * dx , 0, -i * dz));
		}
	}

	//Generate the indices
	for(int i = 0 ; i < raws - 1 ; i ++)
	{
		for(int j = 0 ; j < cols - 1 ; j ++)
		{
			//Face 1
			indices.push_back(i * cols + j);
			indices.push_back(i * cols + j + 1);
			indices.push_back((i + 1) * cols + j + 1 );

			//Face 2
			indices.push_back(i * cols + j);
			indices.push_back((i + 1) * cols + j + 1);
			indices.push_back((i + 1) * cols + j);
		}
	}

	//Translate the vertices
	for(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之后的图像:

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

时间: 2024-10-29 11:37:16

DirectX 9.0 (11) Terrain Rendering的相关文章

最详细win7下手动搭建PHP环境:apache2.4.23+php7.0.11

?资源下载(apache24,php7,phpStorm9) ?修改apache24配置文件 ?安装和启动Apache服务 ?修改php7.0.11配置文件 配置前说明:电脑需要有vc运行库环境,否则会提示计算机中丢失 VCRUNTIME140.dll(因为版本需要VC14编译,为避免安装失败,可先安装vc_redist.x86.exe或者vc_redist.x64.exe),如果电脑有装比较高版本的.netFramework的话,就不会有这个问题 地址:64位版下载地址 , 32位版下载地址

0.11之路(四):从实模式到保护模式

(一)关中断并将system移动到内存地址起始位置 0x00000 将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0.这样系统不会再响应中断,直到main函数中能够适应保护模式的中断服务体系重建完毕才会打开,那时候响应中断的服务程序将不再是BIOS提供的中断服务程序,而是系统自身提供的. 就是要完成实模式下的中断向量表和保护模式下的中断描述符表(IDT)的交接工作.借助关中断(cli)和开中断(sti)完成这个过程的创建,即在创建过程中不能去响应中断,否则没有对应的中断程序,系统

HIVE 0.11版本的bug

HIVE 0.11版本的bug 两次{{group by}}的bug  https://issues.apache.org/jira/browse/HIVE-5149 SELECT key, COUNT(*) FROM ( SELECT key, value, COUNT( * ) FROM src GROUP BY key, value ) a GROUP BY key; 特点是两次 group by ,外层字段少于内层,2次集合函数.可以把中间的查询做成临时表回避这个bug HIVE 0.1

Linux 0.11中write实现

看了一下Linux 0.11版本write的实现,首先它在标准头文件unistd.h中有定义 int write(int fildes, const char * buf, off_t count); 接下来看write.c /* * linux/lib/write.c * * (C) 1991 Linus Torvalds */ #define __LIBRARY__ #include <unistd.h> //定义write的实现 _syscall3(int,write,int,fd,co

Kafka 0.11版本新功能介绍 —— 空消费组延时rebalance

在0.11之前的版本中,多个consumer实例加入到一个空消费组将导致多次的rebalance,这是由于每个consumer instance启动的时间不可控,很有可能超出coordinator确定的rebalance timeout(即max.poll.interval.ms),而每次rebalance的代价又相当地大,因为很多状态都需要在rebalance前被持久化,而在rebalance后被重新初始化.曾经有个国外用户,他有100个consumer,每次rebalance的时间甚至要1个

Linux 0.11 中字符设备的使用

Linux 0.11 字符设备的使用 一.概述 本文自顶向下一步步探索字符设备的读写是怎么完成的.通常我们在Linux应用程序中用open.read.write对各种类型的文件进行操作.我们可以从键盘输入,然后命令行窗口会显示你的输入,有输出的话则命令行窗口会显示输出.为什么所有的设备在Linux中都被看成是一个个文件,可以通过统一的read.write直接进行读写?文件句柄与终端设备有什么关联?为什么Linux允许多个控制终端登录?tty又是什么东西?读写时将发生哪些硬件中断,驱动程序是怎么回

0.11之路(五):激活进程0

linux的第一个进程--进程0. Linux 0.11 是一个支持多进程的现代操作系统,所以我们需要进程管理信息数据结构:task_struct.task[64].GDT等的支撑. 我们需要设置物理内存的分布:主内存区.缓冲区和虚拟盘. memory_end -- 系统有效内存末端位置 main_memory_start -- 主内存区起始位置 buffer_memory_end -- 缓冲区末端位置 main_memory_start = buffer_memory_end -- 缓冲区之后

《DirectX 9.0 3D游戏开发编程基础》 第一章 初始化Direct3D 读书笔记

REF设备 参考光栅设备,他能以软件计算方式完全支持Direct3D Api.借助Ref设备,可以在代码中使用那些不为当前硬件所支持的特性,并对这此特性进行测试. D3DDEVTYPE 在程序代码中,HAL设备用值D3DDEVTYPE_HAL来表示.该值是一个枚举变量.REF设备用D3DDEVTYPE_REF来表示.这种类型非常重要,你需要铭记,因为在创建设备的时候,我们必须指定使用哪种设备类型. COM(组件对象模型) 创建COM接口时不可以使用c++关键字new.此外使用完接口,应调用Rel

工商银行 B2C1.0.0.11 PHP ecshop(JAVA) 接口

<?php if (!defined('IN_ECS')){    die('Hacking attempt');} $payment_lang = ROOT_PATH . 'languages/' . $GLOBALS['_CFG']['lang'] . '/payment/icbc.php'; if (file_exists($payment_lang)){    global $_LANG; include_once($payment_lang);} /** * 模块信息 */if (is