3D场景实现水波纹,咱们往往会应用网格去模仿实在的水流动,无论是简略的三角函数或是gerstner wave。而后通过实在物理渲染(base physcal render)来实现其中的折射与反射。这些实现能够参考《GPU GEMS》第一版。

原谅我,古早年代的书就这成果

但对于2D场景这样的模仿就显得开销过大,2D场景往往会应用一些“投机取巧”的形式,例如应用沃罗诺伊纹理(voronoi)来模仿焦散成果。

而本文就来聊聊如何投机出一个2D的水波纹成果,最终成果如下:

最终代码:

precision mediump float;/*  变量申明*/varying vec2 uv;uniform sampler2D u_image0;uniform float u_time;uniform float u_offset;uniform float u_radio;#define MAX_RADIUS 1#define DOUBLE_HASH 0#define HASHSCALE1 .1031#define HASHSCALE3 vec3 (.1031, .1030, .0973)/*  工具函数*/float hash12 (vec2 p) {  vec3 p3 = fract (vec3 (p.xyx) * HASHSCALE1);  p3 += dot (p3, p3.yzx + 19.19);  return fract ((p3.x + p3.y) * p3.z);}vec2 hash22 (vec2 p) {  vec3 p3 = fract (vec3 (p.xyx) * HASHSCALE3);  p3 += dot (p3, p3.yzx + 19.19);  return fract ((p3.xx + p3.yz) * p3.zy);}void main () {  vec2 frag = uv;  frag.x *= u_radio;  frag = frag * u_offset * 1.5;  vec2 p0 = floor (frag);  vec2 circles = vec2 (0.);  for (int j = -MAX_RADIUS; j <= MAX_RADIUS; ++j) {    for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {      vec2 pi = p0 + vec2 (i, j);      vec2 hsh = pi;      vec2 p = pi + hash22(hsh) ;      // hash12 增加随机      float t = fract (0.3 * u_time + hash12(hsh));      vec2 v = p - frag;      // 半径:      float d = length (v) - (float (MAX_RADIUS) + 1. )*t  ;      float h = 1e-3;      float d1 = d - h;      float d2 = d + h;      float p1 = sin (31. * d1) * smoothstep (-0.6, -0.3, d1) * smoothstep (0., -0.3, d1);      float p2 = sin (31. * d2) * smoothstep (-0.6, -0.3, d2) * smoothstep (0., -0.3, d2);      circles += 0.5 * normalize (v) * ((p2 - p1) / (2. * h) * (1. - t) * (1. - t));    }  }  // 两轮循环增加了weight个波(取均匀)  float weight = float ((MAX_RADIUS * 2 + 1) * (MAX_RADIUS * 2 + 1));  circles /= weight;  float intensity = mix (0.01, 0.05, smoothstep (0.1, 0.6, abs (fract (0.05 * u_time + .5) * 2. - 1.)));  vec3 n = vec3 (circles, sin ( dot (circles, circles)));  vec3 colorRipple = texture2D (u_image0, uv + intensity * n.xy).rgb;  float colorGloss = 5. * pow (clamp (dot (n, normalize (vec3 (1., 0.7, 0.5))), 0., 1.), 6.);  vec3 color = colorRipple + vec3(colorGloss);  gl_FragColor = vec4 (color, 1.0);}

折射和反射

2D模仿水波纹,次要就是要实现水波的折射与反射。
它们别离由反射项vec3(colorGloss)和折射项colorRipple管制
其中反射项由colorGloss管制

float colorGloss =5.* pow (clamp (dot (n, normalize (vec3(1.,0.7,0.5))),0.,1.),6.);

其中带有一个消退函数:

这里借用了布林冯反射模型的高光项:

float gloss = pow(max(0,dot(n, viewDir)),_Gloss);

而normalize (vec3(1.,0.7,0.5))则能够类比为布林冯反射模型的指向相机的向量。因为没有3D场景只能虚伪地模仿一个,对于这块相干的图形学内容就不开展了,感兴趣的能够浏览LearnOpenGL - Basic Lighting

colorRipple

让我再来看看折射项colorRipple:

vec3 colorRipple = texture2D (u_image0, uv + intensity * n.xy).rgb;

这次要依赖texture2D实现,个别咱们应用texture2D(u_image0, uv)来出现纹理,但也能够应用texture2D(u_image0, uv+offset)来实现一些奇异的成果,例如此前应用在10行代码搞定“热成像”实现的colorRamp,以及实现的几款2077格调的shader赛博朋克成果。
明天则通过offset加上一个与定点无关的间隔场实现稳定成果,例如:

......vec2 offset = sin(23.*length(uv-vec2(0.5))-u_time);vec3 color =  texture2D (u_image0, uv + offset).rgb;gl_FragColor = vec4 (color, 1.0);......


值得注意的是这里用到了反射项一样应用了向量n,但只用了向量的方向,而周期性则由intensity实现:

当初让咱们来看看如何实现波的叠加

实现叠加

实现一个水波很容易但如何实现波的叠加?最先想到的是通过noise生成随机波源,用framBuffer记录。本文提供了一个不错的思路:
首先应用阶梯函数让画面反复

vec2 frag = uv;frag *= u_radio;......vec3 color = texture2D (u_image0, fract(frag)).rgb;gl_FragColor = vec4 (color, 1.0);......


这里有一个小技巧,如果反复的不是uv坐标而是纹理,咱们就能让成果反复展现在一个换面中,例如实现一些故障成果:

而本篇咱们则须要应用循环来实现多波源成果:

vec2 frag = uv;frag = frag * 1.5;vec2 p0 = floor (frag);vec2 pp = frag - p0;float offset = 0.03*sin(31.*length(pp)-5.*u_time);vec3 color =  texture2D (u_image0, uv + normalize(pp)* offset).rgb;gl_FragColor = vec4 (color, 1.0);

彼此影响

但这样波源间接不会相互影响。此时咱们就要通过循环把不同波源的影响累加到同一个向量circles上:

vec2 circles = vec2 (0.);for (int j = -MAX_RADIUS; j <= MAX_RADIUS; ++j) {    for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {      vec2 pi = p0 + vec2 (i, j);      vec2 hsh = pi;      vec2 p = pi ;      // hash12 增加随机      float t = fract (0.3 * u_time);      vec2 v = p - frag;      // 半径:      float d = length (v) - (float (MAX_RADIUS) + 1. )*t  ;      float h = 1e-3;      float d1 = d - h;      float d2 = d + h;      float p1 = sin (31. * d1) * smoothstep (-0.6, -0.3, d1) * smoothstep (0., -0.3, d1);      float p2 = sin (31. * d2) * smoothstep (-0.6, -0.3, d2) * smoothstep (0., -0.3, d2);      circles += 0.5 * normalize (v) * ((p2 - p1) / (2. * h) * (1. - t) * (1. - t));    }  }


这里MAX_RADIUS=1,所以每一个floor宰割的区域不仅承受本人的波源,还同时承受以本人为核心的9宫格另外8个方向的波源。此外这里并没有采纳正弦波,而采纳了更为真切的复合波形,加上(1-t)*(1-t)产生的衰减,保障只承受相邻的波不至于穿帮:

如果没有衰减而穿帮,因为波只能传递向相邻的一个单位,无奈再持续流传上来:

但这样波就太过规定了,所以通过hash12,hash22两个noise函数给波源加上随机值:

float hash12 (vec2 p) {  vec3 p3 = fract (vec3 (p.xyx) * HASHSCALE1);  p3 += dot (p3, p3.yzx + 19.19);  return fract ((p3.x + p3.y) * p3.z);}vec2 hash22 (vec2 p) {  vec3 p3 = fract (vec3 (p.xyx) * HASHSCALE3);  p3 += dot (p3, p3.yzx + 19.19);  return fract ((p3.xx + p3.yz) * p3.zy);}......vec2 pi = p0 + vec2 (i, j);vec2 hsh = pi;vec2 p = pi + hash22(hsh) ;// hash12 增加随机float t = fract (0.3 * u_time + hash12(hsh));......

最初抉择一种波形:

因为是for循环叠加的circles,所以最初要对它进行均匀

// 两轮循环增加了weight个波(取均匀)float weight = float ((MAX_RADIUS * 2 + 1) * (MAX_RADIUS * 2 + 1));circles /= weight;

最终模仿一个向量n,参加上文的反射项方程,所以咱们须要抉择一个波形,我这里抉择sin(xx + yy)。不过这是模仿,各位看客也能够抉择本人喜爱的波形:

本篇就完结了,下一篇咱们来说说,上文中提到的glitch成果要如何制作: