先看效果
自然界中水波纹效果十分麻烦,我这里只是根据水波纹的几个特性,在理想环境下模拟水波纹的扩散效果。
这里应用到的属性有:扩散、波动、折射。
扩散:很好理解,水波纹会从触发原点开始向周围扩散
波动:水波纹就一直波,在切面上观看,就是一个正弦函数的波形图
折射:光在不同介质中传播速度不同导致出现折射效果。
如果在平静条件下,在垂直方向上看水底事物,很正常。
在波动条件下,因为水的上下波动,导致垂直方向上看到的水底物体,因为波的角度不同,导致水下事物反射的光到人眼的时候,出现一些偏移。
找出这个偏移的算法就是这个效果的精髓所在。
看下图:
基本算法如下:
1、 根据公式,正弦函数的波形上的某个点的切线角度。
sople=cos(len)【sople为斜率,len正弦位置距离原点的位置】
2、 len要根据水波纹的波长除以2π算出一个周期下r的值
len=r*PI*2/waveLen【waveLen:水波一个周期的长度】
sople=cos(r*PI*2/waveLen)
3、 知道斜率,求出切线的倾斜角
sopleDeg=atan(sople)
sopleDeg = (sopleDeg+360)%360
4、 切线的倾斜角区间为0-360度,入射角区间0-90度
inDeg= sopleDeg%90
5、 根据公式,入射角的正弦比上折射角的正弦为折射率
sin(inDeg)/sin(outDeg)=1.3333
sin(outDeg)=sin(inDeg)/1.3333
outDeg=asin(sin(inDeg)/1.3333)
6、 根据如图,偏移角度为入射角减去折射角
shiftDeg=inDeg-outDeg
7、 知道偏移角度和水深可以计算出偏移量
shift=tan(shiftDeg)*depth
8、 将偏移量分解为X轴偏移和Y轴偏移
shiftX=cos(deg)*shift
shiftY=sin(deg)*shift【deg为当前位置和原点位置所成的夹角】
那么把真实位置的像素赋值给期待位置,处理所有的点,这样就得到的水波纹效果。
代码如下:
1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta http-equiv="Pragma" content="no-cache" /> 5 <meta http-equiv="Cache-Control" content="no-cache" /> 6 <meta http-equiv="Expires" content="0" /> 7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 8 <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" /> 9 10 <meta charset="UTF-8"> 11 <title>Document</title> 12 </head> 13 <body bgcolor="#000000"> 14 <canvas id="canvas"></canvas> 15 <script type="text/javascript"> 16 (function(){ 17 var canvas = document.getElementById("canvas"); 18 canvas.style.position = "absolute" ; 19 canvas.style.left = 0 ; 20 canvas.style.top = 0 ; 21 var cxt = canvas.getContext("2d"); 22 var imgData ;//原始图片信息 23 var tempImageData ;//临时图片信息 24 var depth = 40 ;//水深 25 var waveLen = 30 ;//水波波长 26 var step = 0 ;//运动步长 27 var w , h ;//图片宽高 28 var sin = Math.sin, 29 cos = Math.cos, 30 tan = Math.tan, 31 atan = Math.atan, 32 asin = Math.asin, 33 sqrt = Math.sqrt, 34 abs = Math.abs , 35 round = Math.round, 36 max = Math.max, 37 min = Math.min, 38 PI = Math.PI; 39 40 var n = 1.3333 ;//n=sin(a1)/sin(a2) a2 = asin(sin(a1)/n) 41 42 var p = {x:150.01,y:150.01};//水波圆心 43 var r,slope,slopeDeg,inDeg,outDeg,shiftDeg,dir,shift,shiftX,shiftY,position,tmpDepth; 44 var radius = 0 ; 45 var speed = 2 ; 46 var radiusWidth = 60 ; 47 var timerAnimate = 0 ; 48 var damp = 1; 49 50 document.onmousedown = function(e){ 51 if(timerAnimate) 52 clearInterval(timerAnimate); 53 p = { 54 x:e.pageX + 0.01, 55 y:e.pageY + 0.01 56 }; 57 radius = 0 ; 58 damp = 1 ; 59 timerAnimate = setInterval(draw,40); 60 } 61 function draw(){ 62 var t = new Date; 63 step += 1; 64 radius += speed ; 65 damp -= 0.01; 66 for(var x = 0 ; x < w ; x ++){ 67 for(var y = 0 ; y < h ; y ++){ 68 var pxObj = getPoint(imgData,x,y); 69 setPoint(tempImageData,x,y,pxObj); 70 } 71 } 72 cxt.putImageData(tempImageData,0,0); 73 console.log(new Date - t); 74 if(damp < 0){ 75 if(timerAnimate) 76 clearInterval(timerAnimate); 77 cxt.putImageData(imgData,0,0); 78 } 79 } 80 function getPoint(img,x,y){ 81 r = sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y)); 82 if(r < radius){ 83 position = (r + step)/waveLen*PI*2;//当前位置的代表弧度 84 tmpDepth = sin(position)*waveLen/PI/2;//水波动引起的临时深度变化 85 slope = cos(position);//斜率 86 slopeDeg = atan(slope);//斜角 87 slopeDeg = abs(slopeDeg*PI*2)%90/(PI*2);//入射角保证为0-360 88 inDeg = slopeDeg;//入射角 89 outDeg = asin(sin(inDeg)/n);//折射角 90 shiftDeg = inDeg - outDeg ;//偏移角 91 shift = (x-p.x)/abs(x-p.x)*tan(shiftDeg)*(depth+tmpDepth)*damp ;//偏移量 92 deg = atan((y-p.y)/(x-p.x)); 93 shiftX = cos(deg)*shift ;//X偏移量 94 shiftY = sin(deg)*shift ;//Y偏移量 95 x = round(max(0,min(w-1,x+shiftX))); 96 y = round(max(0,min(h-1,y+shiftY))); 97 } 98 var i = (y*w + x ) * 4 ; 99 var pxObj = []; 100 pxObj[0] = img.data[i]; 101 pxObj[1] = img.data[i+1]; 102 pxObj[2] = img.data[i+2]; 103 return pxObj; 104 } 105 function setPoint(img,x,y,obj){ 106 var i = (y*canvas.width + x ) * 4 ; 107 img.data[i] = obj[0]; 108 img.data[i+1] = obj[1]; 109 img.data[i+2] = obj[2]; 110 img.data[i+3] = 255; 111 } 112 113 function init(){ 114 var img = new Image(); 115 img.onload = function(){ 116 canvas.width = this.width ; 117 canvas.height = this.height; 118 cxt.drawImage(this,0,0,this.width,this.height); 119 imgData = cxt.getImageData(0,0,this.width,this.height); 120 tempImageData = cxt.createImageData(imgData); 121 w = canvas.width ; 122 h = canvas.height ; 123 timerAnimate = setInterval(draw,30); 124 } 125 img.src = "4.jpg"; 126 } 127 init(); 128 })() 129 130 </script> 131 </body> 132 </html>
演示地址:http://suohb.com/work/newWater.html
更多特效,关注我的微信公众号: