近些年,我们经常会在一些网站上看到些炫酷的图片特效,这些效果大部分都是用WEBGL技术实现的, 借助shader我们可以实现css无法做到的炫酷视觉效果 。随着这门技术的兼容性越来越好,普及率越来越高,掌握它必将为你工作中的加分项。还在等什么?一起来学习下吧。

这次我们用three.js来实现一个图像波动特效,剖析其实现原理,最终可以举一反三,变成自己的东西,先来看效果。

图片变形特效演示

实现图片全屏显示

首先我们要将图片等比例撑满窗口,并可以适应浏览器动态缩放。要实现这个效果,我们可以通过在resize事件中动态改变透视相机的视野fov来实现。

视野fov和平面高度关系

我们将图片的高度设置为浏览器窗口的高度,根据三角函数可以很容易计算出需要的视野角度值。

const dist = this.camera.position.z 
this.camera.fov = 2 * (180 / Math.PI) * Math.atan(this.h / (2 * dist))

图像变形的原理

我们知道three.js中的纹理uv坐标是和模型的顶点坐标相对应的,我们只要改变模型的顶点坐标就能达到扭曲图像的目的。

为了方便观察,我先将平面的贴图去掉,变成纯色的wireframe材质。

纯色的wireframe网格平面

然后,我们在shader中对顶点坐标进行下面的变换,看看会发生什么。

vec3 pos = position;
pos.z = 0.5 * sin(pos.x * 10.0);
对顶点变形后的效果

顶点沿z方向进行了正弦变换,我们得到了一个变形后的平面模型。我们去掉网格并把材质还原,这个效果的原理就一目了然了。

贴图后的效果

效果具体实现

要实现我们的效果,我们需要先将模型通过顶点变换变成漏斗状。

根据当前顶点坐标和中心点坐标的距离计算出z值,距离中心越近z值改变越小,越远则改变越大,最终得到了一个漏斗状的模型。

float dist = length(uv - vec2(0.5)) * 2.0;
float maxdist = length(vec2(0.5));
float normalizedDist = dist / maxdist;
pos.z += normalizedDist;
变成漏斗状模型

为了实现动画效果,我们还需要一个变量progress,它是一个0-1之间的值,用来控制变形效果从无到有的过程。

先在shader中加入progress变量,根据progress值控制变形的进度。

float dist = length(uv - vec2(0.5)) * 2.0;
float maxdist = length(vec2(0.5));
float normalizedDist = dist / maxdist;
pos.z += normalizedDist * progress;

鼠标点击后,我们需要将progress值从0变成1。为了看到动画效果,需要将这个值渐进增加,这里用到了greensock的动画库,将动画时间控制在0.6秒,这里的ease属性是用来控制动画的缓动函数,不同的缓动函数会有不同的运动效果,可以根据需要多多尝试。

 _onTouchBegan(e) {
    gsap.to(this.material.uniforms.progress, {
      value: 1,
      duration: 0.6,
      ease: 'expo.easeOut'
    })
  }

到此这个效果的基础部分就介绍完了,具体还有一些实现细节,大家可以参考源码来理解,举一反三,如有问题欢迎给我留言。

github源码

https://github.com/imokya/threejs-image-distort

关注