如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】

在最后我们将一起实现如下图水面波动的效果,不过阅读完本文,也将了解到这一思路的其他用法
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第1张图片
本篇默认读者本已经能读懂基本的glsl语言,语法不再做过多解释,如果有不理解可以翻看之前的文章有详细讲解
用shader制作马赛克
这是上帝的杰作:10行代码搞定“热成像”
几款2077风格的shader

如何扭曲图片

如何扭曲图片?方法多种多样,但偏移是其中比较常用也极其便宜的方式。比如可以让小骑士左侧身体偏移20%:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第2张图片

void main() {
 if(st.x>0.5 ){
   st.y+=0.2;
 }
 vec4 color = texture2D (u_image0, st);
 gl_FragColor = vec4(color.rgb, 1.0);
}

st是一张(0.0)->(1,1)的二维平面,让其中右侧区域的像素点都读取其上方0.2位置处的像素信息,就实现了平移掉过,这种线性的平移我们在几款2077风格的shader一文中已经领教,但用它模拟扭曲是有点为难胖虎了。所以我们给他来点非线形的偏移:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第3张图片

#define PI 3.14//圆周率
#define E 2.71828//自然对数的底
.....
float Distribution(float x){
  float a = 1.0/(4.0*PI*PI);
  float b = pow(E,-(x-0.5)*(x-0.5)/0.04);
  return a*b;
}

void main() {
  st.y = mod(st.y+ 10.0*Distribution(st.x),1.0); // mod保证画面连续
  vec4 color = texture2D (u_image0, st);
  gl_FragColor = vec4(color.rgb, 1.0);
}

这里我们在x=0.5处添加了一个正太分布的偏移:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第4张图片
由正太分布的性质可以知道,越接近x=0.5偏移量越大,越接近1和0偏移越小。在一些需要实现非平面镜的场景中,这个shader可以结合鱼眼及其他扭曲一起实现一些鬼魅的效果。

意外的涂鸦效果

如果叠加一个正太方程就能实现扭曲,那么叠加一个波动方程可以实现怎样的效果?这里简单选取一个正弦波:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第5张图片

void main() {
  vec2 offset = vec2(0.0);
  offset.x = sin(st.x * 14.0) * .2;
  offset.y = sin(st.y * 14.0) * .2;
  vec4 color = texture2D(u_image0, uv+offset);
  gl_FragColor = vec4(color.rgb, 1.0);
}

这里设置一个偏移量offset,由于需要在画面中出现连续的波峰和波谷,需要将函数的频率提高,同时我们不想让offset取值偏移到屏幕之外,所以控制了振幅,该选什么呢?就暂定0.2吧,然后:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第6张图片
克苏鲁降临了。
这是因为此时振幅还是过大,频率快可以在画面中出现多层次扭曲,振幅小意味着每次的波动是轻微的。可以做以下调整:

offset.x = sin(st.x * 14.0) * .02;
offset.y = sin(st.y * 14.0) * .02;

或者使用一个mix函数来实现:

vec2 coord = mix(st,st+offset, 0.01);
vec4 color = texture2D(u_image0, st+offset);

此时出现的效果就是:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第7张图片
这里控制下振幅就实现了一种常见的2D效果doodle(涂鸦):
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第8张图片
这里我封装了一个函数来实现周期性的涂鸦效果,这在effect等动画工具中也是很常见的:

vec2 doodle(vec2 st, float speed, float amount) {
 ....
 _Speed = (floor(u_time * 1.040 * _Speed) / _Speed) * _Speed;
 ...
 vec2 offset = vec2(0.0);
 offset.x = sin((st.x * _Amount + _Speed));
 offset.y = cos((st.y * _Amount + _Speed));
 ...
}

其中_Speed是一个阶梯状函数,这是常见的用来实现跨度式变化的函数:
如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第9张图片
speed控制变化的快慢,amount控制振幅的大小,越大图像越夸张,推荐speed在(1,5)之间,amount则选择(5,15)

水面波动效果

最后可能观众老爷也猜到了,其实我们只要把阶梯函数去掉就能够实现,让speed呈现线形变化就能实现本文开头例子中的波动效果了。

_Speed = u_time; // 简单粗暴!!!

如何实现水面波动?一种代码实现两种效果【shader 奇技淫巧】_第10张图片

其实这个波动效果已经足够用了,但有一点太过规律了,如果需要提升的话,我们还需要再添加一些渐变的随机,这部分就留给读者盆友们自行思考了。

至于这个一鱼两吃的效果有什么应用,也留给读者盆友们吧。

你可能感兴趣的:(webglshader图形学)