HeightMap

HeightMap
=========
focus on 3d Terrain一书介绍了两种自动生成HeightMap的方法。作者Trent参考了编程精粹中提供的算法,并做了应用。

Trent的实现:

CTerrain类维护着一个结构体SHEIGHI_DATA
struct SHEIGHT_DATA
{
 unsigned char* m_ucpData;//保存高度值
 int m_iSize;  // (must be a power of 2)  其实并不是必需的!m_iSize表示构成地形宽或高的顶点数
};


高度程的2种实现:
bool CTERRAIN::MakeTerrainFault( int iSize, int iIterations, int iMinDelta, int iMaxDelta, float fFilter )

bool CTERRAIN::MakeTerrainPlasma( int iSize, float fRoughness )
算法通用处理方法:
第一步,为m_ucpData申请内存:new char[m_iSize*m_iSize],用来保存高度值。
        为fTempBuffer申请内存 :new float [m_iSize*m_iSize],引入是为了提高精度。
第二步,采用相应的算法生成高度值,通过过滤处理,使得到地形效果符合自然地形走势。

最后,把处理后的高度值要映射到区间[0-255] (可指定),然后保存到m_ucpData中。类CTerrain实例在得到高程图数据后,就可以
进行渲染工作了。

------------目标:理解算法过程--------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第一部分:Fault Formation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~     
算法思想:
  for( iCurrentIteration=0; iCurrentIteration<iIterations; iCurrentIteration++ )
{

  首先,fTempBuffer[m_iSize*m_iSize]初始化为0。对应的渲染效果图,就是一个格子状的矩形。
  
  接着,构成矩形的顶点中,随机取2个不同的顶点。(注意到:它们都在XZ平面上)

    然后,连接这两个顶点坐标并做延长,为这条延长线其中一侧的顶点赋于高度值iHeight(迭代得到),另一侧则维持原值。

iHeight= iMaxDelta - ( ( iMaxDelta-iMinDelta )*iCurrentIteration )/iIterations;

其中iMaxDelta,iMinDelta为指定的最大高度极值。显然,一开始取到的是最大值。

    最后,对整个数组中高度值做衰减处理,目的为了各顶点高度值过渡更平滑。

 }  
===
补充说明:
   1.迭代过程,简单地说,就是对地形画一条随机线(由两个随机得到的不同顶点连线所得),然后对线一侧(原程序设定的是线的右侧)赋于高度值。 如果不做衰减处理的话,这时地形渲染后有强烈明暗效果,一边是黑色位置低,一边是白色,表示位置高.  迭代多次后,那么每个区域高度值过渡相当平和很多。

    2.衰减处理
      fTempBuffer[m_iSize*m_iSize]渲染后就是一个平面格子,构成格子的顶点的高度值决定了地形的平缓程度。
    公式:后续顶点高度值(更新后)=因子*前一个顶点高度值+(1-因子)*后续顶点高度值
对迭代后得到的高度值做衰减处理过程:从左到右,从右到左,从上到下,从下到上。

一个例子:
地形fTempBuffer[4X4] 
如果将下面顶点(字母符号)连接起来,就是渲染后的地形图(9个格子)。
m n o p
i j k l
e f g h
a b c d
其中fTempBuffer[0]表示的是最初顶点a所对应的高度值,fTempBuffer[0*4+3]对应的是顶点d.


按衰减处理过程:

->表示"衰减公式的影响"

从左到右:
a->b->c->d
e->f->g->h
i->j->k->l
m->n->o->p

从右到左:
d->c->b->a
h->g->f->e
l->k->j->i
p->o->n->m

从上到下:略
从下到上:略

     3.如何确定落在分割线的一侧。
       原程序说明:
       随机生成的顶点A(x1,z1),B(x2,z2) ,其连线就是分割线。其它的待判定的点记C(x,z)
        
    向量AB:(Dx1=x2-x1,Dz1=z2-z1)
    向量AC:(Dx2=x-x1, Dz2=z-z1)
    判定条件是:if( Dx2*Dz1-Dx1*Dz2)>0)  ftempBuffer[x+z*m_iSize]=迭代得到高度值。

如何理解? (Dx2*Dz1-Dx1*Dz2)>0.

这个问题可转化为原生问题:如何判定顶点落在AB连线的哪一侧。
向量运算时,做点积是最直观的。画几条向量,可以知道,只要把向量AC和 {垂直AB的向量}做点积,就可以达到要求了。

   求出垂直AB的向量(回想高中时代学过的复数知识):
   向量AB逆时针转90度,
         (Dx1,Dz1)*(cos90,sin90)=(Dx1*cos90-Dz1*sin90,Dx1*sin90+Dz1*cos90)
                                =(-Dz1,Dx1)

   把(Dx2*Dz1-Dx1*Dz2)拆成点积形式(Dx2,Dz2).(Dz1,-Dx1)
比较可知:(Dz1,-Dx1)是向量AB顺时针转90得到的。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第二部分 midpoint displacement
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这种地形分形方式有好多称谓:Midpoint displacement ,plasma fractal ,diamond-square 算法。
顶点数据个数:m_iSize*m_iSize,地形实际大小为(m_iSize-1)*(m_iSize-1),其中m_iSize必须为2的n次幂。这个限制是针对顶点个数来说的!!!


注意:
屏幕坐标系下作者做的图示,和我们通常所用笛卡尔坐标系下所作的图示,其实是没有区别的,因为最终写到缓冲区的数据都是一致的。
例子:3x3的地形,(3x3 指得是顶点实际个数比)
ABC
DEF
GHI 
比较
GHI
DEF
ABC
在保存数据都是采用行序,ABCDEFGHI依次存储.


算法思想:求正方形中点,求菱形中点,反复。

fTempBuffer[0]= 0.0f;

while( iRectSize>0 )
 {
/*Diamond step


a.....b
.     . 
.  e  .
.     .
c.....d  

e  = (a+b+c+d)/4 + random

In the code below:
a = (i,j)
b = (ni,j)
c = (i,nj)
d = (ni,nj)
e = (mi,mj)   */

for( i=0; i<m_iSize; i+=iRectSize )
{
for( j=0; j<m_iSize; j+=iRectSize )
{
ni= ( i+iRectSize )%m_iSize;
nj= ( j+iRectSize )%m_iSize;

mi= ( i+iRectSize/2 );
mj= ( j+iRectSize/2 );

fTempBuffer[mi+mj*m_iSize]= ( float )( ( fTempBuffer[i+j*m_iSize] + fTempBuffer[ni+j*m_iSize] +
   fTempBuffer[i+nj*m_iSize] + fTempBuffer[ni+nj*m_iSize] )/4 + RangedRandom( -                                  fHeight/2, fHeight/2 ) );    
   }   
   }

/*Square step -
.......
 .     .
 .     .
.  d  .
 .     .
.     .
......a..g..b
.     .     .
.     .     .
.  e  h  f  .
.     .     .
.     .     .
......c......

g = (d+f+a+b)/4 + random
h = (a+c+e+f)/4 + random
  
In the code below:
a = (i,j)
b = (ni,j)
c = (i,nj)
d = (mi,pmj)
ef = (mi,mj)
g = (mi,j)
h = (i,mj)*/
for( i=0; i<m_iSize; i+=iRectSize )
{
 for( j=0; j<m_iSize; j+=iRectSize )
  {

  ni= (i+iRectSize)%m_iSize;
  nj= (j+iRectSize)%m_iSize;

  mi= (i+iRectSize/2);
  mj= (j+iRectSize/2);

  pmi= (i-iRectSize/2+m_iSize)%m_iSize;
  pmj= (j-iRectSize/2+m_iSize)%m_iSize;
    
 //Calculate the square value for the top side of the rectangle
    fTempBuffer[mi+j*m_iSize]= ( float )( ( fTempBuffer[i+j*m_iSize]+fTempBuffer[ni+j*m_iSize] +       fTempBuffer[mi+pmj*m_iSize]+fTempBuffer[mi+mj*m_iSize] )/4+
                                        RangedRandom( -fHeight/2, fHeight/2 ) );

 //Calculate the square value for the left side of the rectangle
 fTempBuffer[i+mj*m_iSize]= ( float )( ( fTempBuffer[i+j*m_iSize]+fTempBuffer[i+nj*m_iSize]                                               +fTempBuffer[pmi+mj*m_iSize]+fTempBuffer[mi+mj*m_iSize] )/4+
               RangedRandom( -fHeight/2, fHeight/2 ) );
   }
 }

 //reduce the rectangle size by two to prepare for the next
 //displacement stage
 iRectSize/= 2;

 //reduce the height by the height reducer
    fHeight*= fHeightReducer;
 }

 ~~~~~~~
说明:
运行过程:以m_iSize=4为例。
=============
iRectSize=4时
=============
0XXXY
XXXXY
XX1XY
XXXXY
YYYYY 
这一步,各跨度下构成的方格,为其中心赋高度值,Y是虚拟点,我们并不需要为它分配内存,所以申请内存
只要new  float[4X4]即可,而不是new float[5X5]。这是通过取余%来实现的。

------------
0X2XY
XXXXY
3X1XY
XXXXY
YYYYY
这一步,构造菱形的中心,部分边缘处的点在对周围4个点采样时(这4个点构成菱形),个别点是不可见的,同样要
作取余%处理。
=============
iRectSize=2时
=============
0X2XY
X4X6Y
3X1XY
X5X7Y
YYYYY
------
0a2eY
b4f6Y
3c1gY
d5h7Y
YYYYY
------------
=============
iRectSize=1时
=============
作用对应于 Fault算法中的衰减处理。
这里的过程在diamand步是由左往右,由上往下,分别对顶点周围的4个点进行采样,

square步
同一顶点采样2次,接着由左往右,由上往下。

_________________________________________________________________________________________
                                   ~Fin~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值