OpenGL水波纹算法
/ / 阅读耗时 4 分钟 由于需要做一些特效,所以学了一下水波纹的生成算法。
水波纹特效看起来就像这样:
大约就是模拟自然界中水中波纹的图形效果。
对于水中的波纹,我们将它们看成简谐波,对于简谐波的传播,有波函数:
其中,$A$是最大振幅,$\omega$是角速度,$u$是波速,$\phi$则是初相位。这个二元方程描述了离波源$x$位置处,时间为$t$时的震荡情况。
这样的话,我们根据波源位置和时间,就可以确定任意一点的振幅。振幅越大,折射的角度越大,即偏移量越大。由于精确的计算会带来不小的性能损耗,不妨认为这个偏移量与振幅呈线性关系,根据振幅来计算偏移量。
实际中,波在水中的传播是有阻尼的。在波的传播过程中,我们将最大振幅做线性衰减,在波源处也进行同样的操作。
对于多个波的叠加,我们将它们的偏移量加在一起即可,这样就可以达到多种波在同一点相互叠加的视觉效果。下附水波纹算法的着色器程序源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
uniform sampler2D uni;
uniform float u_time[100]; //最大支持100个波源
uniform float x[100]; //波源x
uniform float y[100]; //波源y
in vec2 tex;
void main(void)
{
float dx = 0, dy = 0;
for(int i = 0;i < 100;i++){
float maxA = 0.05; //最大振幅
float v = 0.1; //波速
float omega = 8; //omega
if(u_time[i] < 0)continue; //u_time为时间,<0即为无波源
float dis = distance(tex, vec2(x[i], y[i]));
if(u_time[i] * v < dis) maxA = 0; //波尚未传到此处
else maxA *= (dis / v + 4.0 - u_time[i]) / 4.0; //波源衰减
maxA -= 0.04 * dis; //波能衰减
if(maxA < 0) maxA = 0;
dx += fract(- maxA * sin(omega * (u_time[i] - dis / v))); //用sin的话,更加真实
dy += fract(- maxA * sin(omega * (u_time[i] - dis / v)));
}
vec3 color = texture(uni, vec2(fract(tex.x + dx), fract(tex.y + dy))).rgb;
gl_FragColor.a = 1.0;
gl_FragColor.rgb = color;
}