目前,各种高性能计算机正以其强大的运算能力被广泛应用于各种领域,其中对自然界的物理现象和自然规律进行仿真是主要应用之一。由于许多专业书籍对此类仿真技术讳莫如深,使不少程序设计人员对此类程序的设计问题感到无从下手。本文以对真实水波的产生、扩散、衰减以及多个水波的交迭过程的计算机模拟为例,介绍此类程序的设计思路与解决方法。在程序的实现过程中,为了使仿真的效果更加逼真、处理数据显示的速度更快,本文使用了DirectX中的DirectDraw技术利用硬件加速器对数据的显示进行加速。
扩散及衰减处理 要对某种自然现象进行仿真,就必须对该现象的特性有很好的认识。比如对于本文所仿真的对象水波而言,就要对水波的诸多特性,如扩散性、衰减性、反射性以及水的折射等都要有所认识,并最终通过程序算法体现在程序中。这些关于波的特性属于普通物理的研究范畴,本文不再赘述。根据以上的特性,再利用计算机、数学和几何等有关知识就可以在计算机上模拟出真实的水波了。 由于在模拟时需要的是实时的渲染,所以每秒种至少要渲染15帧以上的画面才能使水波平滑地显示。考虑到普通计算机的运算速度较慢,所以不能用乘、除法,更不可以使用正、余弦函数以精确的公式来构造水波,只能通过使用简单而高速的加、减法的近似算法来实现。 首先,可以用两个与水池图像一样大小的数组buf1和buf2来保存水面上每一个点(对应于每一个像素的离散化点)的前、后两个时刻的波幅数据。在无外力干扰的稳定状态下,水面是一个平面,水面各点的波幅都为0。当有外力干扰时,如向水池投一颗石子会使水面泛起层层的涟漪,实际上并非水面上的点在向外扩散,而是仍停在原地上下移动,由于振动幅度的变化而引起视觉上的变化。由于水波上的任何一点在任何时候都是通过振幅的变化把能量以自己为中心向四周扩散,所以可以近似认为一个点只会对相邻的前、后、左、右4个点有影响。这样我们就可以用归纳法来根据任一点在某时刻周围4点的振幅来求出该点在下一时刻的振动幅度。假设表示该关系的公式为:
A0’=a×(A1+A2+A3+A4)+b×A0 (公式1)
其中a、b为待定系数,A0’为0点下一时刻的振幅,A0、A1、A2、A3、A4均为当前时刻周围各点振幅。在不考虑衰减情况下波的能量守恒,即上下时刻各点振幅之和守恒,这可以用公式2表示:
A0’+A1’+...+An’= A0+A1+...+An(公式2)
将公式1代入公式2:
(4a+b)×A0+(4a+b)×A1+...(4a+b)×An = A0+A1+...+An
化简公式可得4a+b=1, 取a = 1/2、b = -1时可以满足条件,而且除以2可以用运算速度很快的移位运算符“>>”来进行。这样,向公式1中代入系数可得到无阻力状态下的周围4点对中心点的影响关系式:
A0’=(A1+A2+A3+A4)/ 2- A0
因此,水面上下一时刻任意一点的波幅等于与该点紧邻的前、后、左、右4点的波幅之和的一半与该点在上一时刻的波幅之差。但在实际中水是存在阻力的,水波会在扩散过程中逐渐衰减直至消失。所以,还要对波幅数据进行衰减处理,让每一个点在经过一次运算后,波幅按一定的比例衰减。笔者实验发现,经验系数(衰减率)取1/32比较合适,同时它也可以通过移位运算很快地获得。下面是具体计算波幅数据的主要代码:
void Spread() { …… for(int i=BACKWIDTH;i<BACKWIDTH*BACKHEIGHT-BACKWIDTH; i++)>/td> { //能量的扩散 buf2[i] = ((buf1[i-1]+buf1[i+1]+buf1[i-BACKWIDTH]+buf1[i+BACKWIDTH])>>1)- buf2[i]; //能量的衰减 buf2[i] -= buf2[i]>>5; } //交换前后两时刻的能量缓冲区 short *ptmp =buf1; buf1 = buf2; buf2 = ptmp; …… }
光折射模拟 虽然模拟了对波的传播过程,但如不考虑起伏的水波对光的折射也是不逼真的。根据光学有关知识,我们所看到的水下的景物并非在观察点的正下方,而是存在一定的偏移。偏移的程度同水波的斜率、水的折射率和水的深度都有关系,出于对处理速度的考虑同样也不能对其进行精确的模拟,只能做线性的近似处理。因为水面越倾斜,所看到的水下景物偏移量就越大,所以,我们可以近似地用水面上某点的前后、左右两点的波幅之差来代表所看到的水底景物的偏移量:
void Render() { …… int xoff, yoff; int k = BACKWIDTH; for (int i=1; i<BACKHEIGHT-1; i++)> { for (int j=0; j<BACKWIDTH; j++)> { //计算偏移量 xoff = buf1[k-1]-buf1[k+1]; yoff = buf1[k-BACKWIDTH]-buf1[k+BACKWIDTH]; //判断坐标是否在窗口范围内 if ((i+yoff )< 0 ) {k++; continue;} if ((i+yoff )> BACKHEIGHT) {k++; continue;} if ((j+xoff )< 0 ) {k++; continue;} if ((j+xoff )> BACKWIDTH ) {k++; continue;} //计算出偏移像素和原始像素的内存地址偏移量 int pos1, pos2; pos1=ddsd1.lPitch*(i+yoff)+ depth*(j+xoff); pos2=ddsd2.lPitch*i+ depth*j; //复制像素 for (int d=0; d<depth; d++)>/td> Bitmap2[pos2++]=Bitmap1[pos1++]; k++; } } …… }
生成波源 在无外力影响的情况下,水面是不会自发产生水波的,必须对水面施加某种波源才能引起水波的扩散。扩散的速度与范围同波源的能量大小与受力范围有关。我们可以通过在程序中人为地修改振幅缓冲区buf,来模拟外力的加入,比如雨点入水等。在雨点落水的地点产生一个负的“尖脉冲”,即让buf[x,y]=-n。根据笔者实验发现,经验系数n的取值范围在32~128之间比较合适。受力半径是以入水中心点为圆心,以雨点半径为半径的圆,圆里所有的点产生一个负的“尖脉冲”。代码处理如下:
void DropStone(int x, /*x坐标 */ int y, /*y坐标*/int stonesize, /*半径*/int stoneweight/*能量*/) { …… //判断坐标是否在屏幕范围内 if ((x+stonesize)>BACKWIDTH ||y+stonesize)>BACKHEIGHT||(x-stonesize)<0||(y-stonesize)<0) return; …… for (int posx=x-stonesize; posx<x+stonesize; posx++) for (int posy=y-stonesize; posy<y+stonesize; posy++) if ((posx-x)*(posx-x) + (posy-y)*(posy-y) < stonesize*stonesize) buf1[BACKWIDTH*posy+posx] = -stoneweight; …… }
虽然在上述的推导中多处采用了看似过分的近似处理,但是完全不必担心效果,事实证明,用这种方法,在速度和图像上都可以获得非常好的效果。图1就是从其中截取的一帧画面,很逼真地再现了水波的产生过程。 <  
说明:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
1/2 1 2 下一页 尾页 |