在广州动物园南门等朋友。进口旁是一滩池水,波光粼粼之中,倒映着红嘴白鹭们的身姿,嬉戏之间,水滴自觉地在池中酿起一圈一圈细细的涟漪,回味其中。——ZwqXin.com
上篇:[水效果Ⅰ - 水池]
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/opengl/water-simulation-2.html
Ripple,涟漪,是一种圆形波(Circular Wave),从波源处呈圆状向外扩散。它最大的特点是衰减。水滴入水的刹那,引起该位置的振动从而产生频率很大的波。波无节制地向它所能蔓延的任何方向(水平面任何方向)蔓延,但是随着时间的增加,波传递的时候频率会逐渐减小——在波速一定下,这相当于波长的增大,所以你看涟漪在水面上新产生的一圈与前一圈的差额会比前一圈与前前圈的差额大;同时,振幅也是随着时间——以及随着传播距离逐渐变小——所以你最后看到涟漪就像蔓延开来消失了一样。
因此,完全可以用一个正弦波函数来决定当前水面位置的高度。而且其振幅和频率按上述规律变化。理应,这个波只作用在波源附近一定范围内就够了,在之外的所产生的水面效应根本可以忽略,但是我只有一个水面网格,拆分不是那么容易的,特别是涟漪在随机位置产生的情况下。所以每个涟漪其实都影响整个网格。或者说,每个涟漪都在原来平静水面的基础上加添了一个PASS,使其基本形态改变,之后另一个涟漪同样作为一个PASS叠加到之前那个PASS之上——相当于网格每个顶点都加两个正弦函数值——多个涟漪互相叠加可形成趣味的交变效果。
H(x,y) = Amplitude(t,dis) * cos(freguency(t) * ( t + dis/velocity)) ;
dis = length(Point(x,y) - CenterPoint)
- double TheRipple::RippleFunction(double dist_to_waveCenter, float waveCenterAmplitude, float velocity, float time)
- {
- double attenuate = 1 + 0.07 * time * time + 10.0 * dist_to_waveCenter * dist_to_waveCenter;
- double factor = waveCenterAmplitude / attenuate;
- double OriginalFreq = 0.05 ;
- double freq = OriginalFreq / (1.0 + 0.07 * time);
- double deltaT = dist_to_waveCenter / velocity;
- double DestVertexHeight = factor * cos(freq * (time + deltaT) + PI);
- return DestVertexHeight;
- }
其中,具体的各参数的数值,包括各衰减因子都是调试效果时所得的较优效果值(针对具体应用)。attenuate 是振幅衰减因子,waveCenterAmplitude是最初的振幅;OriginalFreq是最初的频率,freq是经过衰减厚的频率;deltaT决定了波传递至时的时刻(由波长决定);DestVertexHeight作为输出反映该涟漪在该点对水面网格的高度增量。
实现涟漪的增量的叠加并不麻烦。麻烦的是涟漪的销毁。假设TheRipple是我的涟漪对象,我用一个容器譬如线性列表装下每个新生成的涟漪。问题是这种涟漪计算开销太大,如果涟漪不断生成[new],同样的计算量在每一帧都不断附加,FPS很快会走向灭亡边缘。这应是可避免的——在实时渲染中,涟漪在振幅很小很小的时候应该被消灭[deelete]。如何断定这个时机呢?
在每个涟漪对象里,检查每个顶点的的高度增量。要注意并非所有顶点都会在同一时间达到振幅最小,因为振幅除了是时间的函数外也是该顶点距波源中心的距离的函数。所以要用一个计数器记数,每有一个顶点满足要求就让计数器加1,并判断当前计数与总顶点数量的大小关系以判断是否该销毁整个涟漪对象,并把它从涟漪列表中去掉。
- //在涟漪的更新函数里
- for(z = 0; z < mGridScaleZ; z++)
- for(x = 0; x < mGridScaleX; x++)
- {
- index = x + z * mGridScaleX;
- mCurrentRippleDelta[index].y
- = RippleFunction(mDistToCenter[index], Amplitude, velocity, mTickCount);
- if(fabs(mCurrentRippleDelta[index].y) < 0.0001f)
- {
- if(mVertexStop[index] == false)
- {
- mVertexStop[index] = true;
- mVertexStops ++;
- }
- }
- }
- if(mVertexStops >= 0.99 * mGridScaleX * mGridScaleZ)
- {
- mRippleGenerated = false;
- }
mCurrentRippleDelta[index].y 记录当前顶点(index)的高度增量,其绝对值就是振幅,容差值是0.0001f。mVertexStop[index]是该顶点的bool变量,当它由false变true时就宣告了该顶点的“破产”,计数器mVertexStops加一。最后的判断里顶点总数为何乘以0.99呢?因为如果是0.995,你等待该涟漪毁灭的时间要加倍,0.998再加倍,0.999再加倍……所以千万别直接就取顶点总数口牙~~
效果:
近看的话,发觉波纹比较尖不是吗?事实上这既与网格的分辨率有关,也与波源函数有关。首先这是200*200的网格,如果是250*250乃至400*400会含有更多细节吧,不过就渲染效率来看这颇愚蠢;另外波源函数本质只是简单的单余弦,有比较尖的头也是正常的。如果改用 2*A*[1+cos(angle)/2]exp的话,就可以用exp指数项控制头部的平滑程度了。当然,就本应用来说,一般不会近看,所以这样做反而也没必要。
恩,在CPU上实现的涟漪就....到此吧。
下篇继续,见 水效果Ⅲ - 抖动波浪
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/opengl/water-simulation-2.html