2014-7-29 13:05| 发布者: tianzc| 查看: 279| 评论: 0
开发滤镜的一大乐趣就是研发滤镜特效。好的滤镜特效可以给用户带来视觉的美,又能让开发者感受到数学的美。而“涟漪”滤镜就是一种将视觉的美和数学的美高度融合的特效。 要还原出一个自然的涟漪效果,需要了解波的特性。我们看到水面晃动的原因,在于产生水纹时,水面并不平整。水纹上的每个点,由于振幅(也就是水深)的不一样,与水底的光源距离不一,从而会产生视差。弄清楚这个原因之后,只需要还原这个现象,就可以绘制出惟妙惟肖的水纹特效了。 首先,需要知道水面上每个点的振幅。可以将水面看作是一个连续的点阵: 那么根据波的传导性,任意时刻一个点的振幅,都是由这个点当前振幅和其前后左右四个点的振幅所影响的(任何非正交的波都能投影到这两个维度上来)。因此我们可以用归纳法求出任意时刻这个水面上任意一点的振幅。假设某一时刻,X0点有前后左右四个点X1,X2,X3,X4,那么可得X0’=aX1+bX2+cX3+dX4+eX0(其中X0’为下一时刻X0的振幅)。由常识可知,前后左右四个点是对称的,他们对中心点的影响是一样的,所以X1,X2,X3,X4的系数应该一样,那么这个公式就简化成了X0’=a(X1+X2+X3+X4)+bX0。如果水的阻力为0,也就是说水波的势能不会被损耗的话,那么所有点的振幅的和应该是不变的,即X0’+…+Xn’=X0+…+Xn,带入到前面的公式里,可得(4a+b)X0+…+(4a+b)Xn=X0+…+Xn,而每个点的系数又必须一致,推出(4a+b)=1。这里取一个最简单的解a=1/2,b=-1。取这个解除了其本身较为简单外,对计算机而言1/2完全可以用位运算而不用乘除法,能提高运算效率。于是原式变形为X0’= (X1+X2+X3+X4)/2-bX0。 然而,自然界中的水波不会在湖面上无尽的漫延,为了更加真实的还原,必须将水的阻力考虑进去。测试的数据显示用1/32比较合适,也就是说水波每个时刻衰减为原来的1/32,而1/32也可以用位运算来解决。 现在我们知道了任意时刻,水面上任意一点的振幅,然而这离渲染出水纹还有一段距离。因为折射的作用,不垂直于水面的光因折射而对观察物产生了偏移。这个偏移量受制于诸多因素,包括水的深度和水的折射率等等。将这些全部都算进去,手机CPU的运算能力达不到实时的要求,而且会产生画面lag,反而降低了真实性。其实这里做线性的近似处理即可。水面越倾斜,观察者看到水底的偏移量就越大,因此只需要简单的用水面上某个点的前后,左右两点见的波幅之差来表示这个偏移量的x,y方向上的值。 到这里之前,移动终端绘制水纹的步骤还是和个人电脑保持一致,可以借鉴的。接下来,个人电脑会离屏保存原图像并根据前文的原理实时绘制另一个图像并渲染。然而移动终端则不能简单的这么做,原因有两个:第一,手机的运算能力达不到个人电脑的水平,运算耗时多,对于某些低端机型来说会产生严重的lag,导致视觉体验很差(事实上测试发现在itouch5和galaxy3这些机型上是不会有明显lag的,可以采用个人电脑的解决方案),没有能很好的利用到GPU的强大并发运算能力;第二,手机显卡受制于参数长度限制,有的显卡支持4096位的参数,有的连1024位都支持不到,但是,即使支持到4096位参数的显卡,也明显支持不了640×480(一般的预览分辨率大小)位的参数,将处理数据的工作交给GPU似乎也并不可行。 为了解决这两个问题,我们可以尝试把振幅参数当作一张纹理贴图(texture)传给GPU来进行运算。这么做的好处多多,首先,每一个点都有自己的振幅,振幅和点可以一一对应起来,即640×480个点就对应出640×480个振幅参数,这样两张纹理(既原图和振幅参数)的坐标可以统一,避免了繁杂的坐标转换计算;其次虽然GPU也有纹理数上限,但是几乎都能支持两张纹理,巧妙的避开了参数纬度过长的问题;最后,我们可以将偏移量计算放入到GPU中并发进行,在CPU这边,只需要计算每个点在某一时刻对振幅的影响即可,运算量急剧下降,从而提升了绘制的效率。对于原纹理textureA,和振幅纹理textureB,输出的颜色color: float xoff = (textureB(x-unitWidth)-textureB(x+unitWidth)).a float yoff = (textureB(y-unitHeight)-textureB(y+unitHeight)).a color.rgb = textureA(x-xoff, y-yoff).rgb; (以上把振幅参数作为alpha值传入,事实上作为a,r,g,b任何一个亦可) 以下附上一张手机上水纹算法的截图,是不是特别有feel呢! PS:水纹算法来自网络,基于手机和Opengl es的特殊处理为原创。 感谢李老湿和小k的技术支持! |