翼度科技»论坛 编程开发 html5 查看内容

H5 WebGL实现水波特效

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
前言

零几年刚开始玩电脑的时候,经常在安装程序上看到一种水波特效,鼠标划过去的时候,就像用手在水面划过一样,感觉特别有意思。但是后来,就慢慢很少见过这种特效了。最近突然又想起了这种特效,于是开始折磨怎么实现这种效果。
思路

我们知道,水波的运动轨迹可以看成随时间变化的三角函数,那么我们可以记录每个水波形成的原点和运行时间,就能知道任一时刻水波的高度。但是,这种方法的运算量会随着水波数量而线性增长,当水波数量很多时,就很可能出现性能问题。
有没有一种办法,可以根据上一时刻的水波幅度,计算出下一时刻的水波幅度呢?如果对于一个点,如果我们把它与其周围的几个点的均值的差作为加速度运动,会怎样呢?
以二维平面为例,即加速度a有
  1. a = (h(x-1) + h(x+1)) / 2 - h(x)
复制代码
先用三角函数得到我们的水波的初始状态:

然后套用上面的公式把结果可视化

可见,水波会从中心想四周散开,这和我们平时看到的水波运动轨迹不正好相似吗?我们把它推导成3维的:
  1. a = (
  2.     h(x-1, y-1) + h(x-1, y+1)
  3.     + h(x+1, y-1) + h(x+1, y+1)
  4.     )/4 - h(x, y)
复制代码
所以,我们只需要画出第一帧的水波图像,就能通过上面的公式计算出下一帧水波的图像了。
有了水波的图像,再根据冯氏光照模型去计算镜面光,就能模拟出较为真实的水波了。
先来看看最终效果:
在线体验地址:https://kason.site/
实现

首先,我们需要一个生成初始化水波的着色器
  1. precision highp float;
  2. const float PI = 3.141592653589793;
  3. uniform sampler2D texture;
  4. uniform vec2 centerCoord;
  5. uniform float radius;
  6. uniform float strength;                       
  7. varying vec2 coord;
  8. void main() {
  9.     vec4 info = texture2D(texture, coord);
  10.     float d = min(distance(centerCoord, coord) / radius, 1.0);
  11.     info.r += (cos(d * PI) * 0.5 + 0.5) * strength;
  12.     gl_FragColor = info;
  13. }
复制代码
然后,再搞个更新水波的着色器
  1. precision highp float;
  2. uniform sampler2D texture;
  3. uniform vec2 delta;
  4. varying vec2 coord;
  5. void main() {
  6.     vec4 old = texture2D(texture, coord);
  7.     vec2 dx = vec2(delta.x, 0.0);
  8.     vec2 dy = vec2(0.0, delta.y);
  9.     float avg = (
  10.         texture2D(texture, coord - dx).r + texture2D(texture, coord - dy).r +
  11.         texture2D(texture, coord + dx).r + texture2D(texture, coord + dy).r
  12.     ) / 4.0;
  13.     old.g += avg - old.r;
  14.     old.g *= 0.995;
  15.     old.r += old.g;
  16.     gl_FragColor = old;
  17. }
复制代码
最后,再来个计算镜面光并完成最终渲染的着色器
  1. precision highp float;
  2. attribute vec2 vertex;
  3. uniform vec2 ripplesRatio;
  4. varying vec2 ripplesCoord;
  5. varying vec2 backgroundCoord;
  6. void main() {
  7.     gl_Position = vec4(vertex, 0.0, 1.0);
  8.     backgroundCoord = vertex * 0.5 + 0.5;
  9.     ripplesCoord = backgroundCoord  * ripplesRatio;
  10. }
  11. `, `
  12. precision highp float;
  13. uniform sampler2D samplerBackground;
  14. uniform sampler2D samplerRipples;
  15. uniform vec2 delta;
  16. uniform float perturbance;
  17. varying vec2 ripplesCoord;
  18. varying vec2 backgroundCoord;
  19. void main() {
  20.     float height = texture2D(samplerRipples, ripplesCoord).r;
  21.     float heightX = texture2D(samplerRipples, vec2(ripplesCoord.x + delta.x, ripplesCoord.y)).r;
  22.     float heightY = texture2D(samplerRipples, vec2(ripplesCoord.x, ripplesCoord.y + delta.y)).r;
  23.     vec3 dx = vec3(delta.x, heightX - height, 0.0);
  24.     vec3 dy = vec3(0.0, heightY - height, delta.y);
  25.     vec2 v = normalize(vec2(1.0, 1.0));
  26.     vec2 r = -normalize(cross(dy, dx)).xz;
  27.     vec4 specular = vec4(0.8, 0.8, 0.8, 1) * pow(max(0.0, dot(v, r)), 5.0);
  28.     gl_FragColor = texture2D(samplerBackground, backgroundCoord + r * perturbance) + specular;
  29. }
复制代码
至此,核心着色器的代码都写完了,只需要结合requestAnimationFrame调用WebGL接口完成最终渲染即可。
完整代码可以查看本人Github: https://github.com/kasonyang/ripples.js

来源:https://www.cnblogs.com/kason/p/17545125.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具