说明:
       本文算法部分整理自 GameRes 上的资料,原作者 Imagic。我只是在学习 Android 的过程中,想到这个特效,然后就在Android 上实现出来,并在源算法的基础上添加了雨滴滴落特效,以及划过水面时的涟漪特效。 该程序在模拟器和真机上运行速度都较慢,需要进一步优化或使用 JNI 实现

效果:



基础知识:
       在讲解代码之前,我们来回顾一下在高中的物理课上我们所学的关于水波的知识。水波有扩散,衰减,折射,反射,衍射等几个特性:

扩散:当你投一块石头到水中,你会看到一个以石头入水点为圆心所形成的一圈圈的水波,这里,你可能会被这个现象所误导,以为水波上的每一点都是以石头入水点为中心向外扩散的,这是错误的。实际上,水波上的任何一点在任何时候都是以自己为圆心向四周扩散的,之所以会形成一个环状的水波,是因为水波的内部因为扩散的对称而相互抵消了。
       衰减:因为水是有阻尼的,否则,当你在水池中投入石头,水波就会永不停止的震荡下去。
       折射:因为水波上不同地点的倾斜角度不同,所以我们从观察点垂直往下看到的水底并不是在观察点的正下方,而有一定的偏移。如果不考虑水面上部的光线反射,这就是我们能感觉到水波形状的原因。
       反射:水波遇到障碍物会反射。
       衍射:在水池中央放上一块礁石,或放一个中间有缝的隔板,那么就能看到水波的衍射现象了。

算法推导:
       好了,有了这几个特性,再运用数学和几何知识,我们就可以模拟出真实的水波了。但是,如果你曾用3DMax做过水波的动画,你就会知道要渲染出一幅真实形状的水波画面少说也得好几十秒,而我们现在需要的是实时的渲染,每秒种至少也得渲染20帧才能使得水波得以平滑的显示。考虑到电脑运算的速度,我们不可能按照正弦函数或精确的公式来构造水波,不能用乘除法,更不能用sin、cos等三角函数,只能用一种取近似值的快速算法,尽管这种算法存在一定误差,但是为了满足实时动画的要求,我们不得不这样做。

首先我们要建立两个与水池图象一样大小的数组buf1[PoolWidth * PoolHeight]和buf2[PoolWidth * PoolHeight](PoolWidth 为水池图象的象素宽度、PoolHeight 为水池图象的象素高度),用来保存水面上每一个点的前一时刻和后一时刻波幅数据,因为波幅也就代表了波的能量,所以在后面我们称这两个数组为波能缓冲区。水面在初始状态时是一个平面,各点的波幅都为0,所以,这两个数组的初始值都等于0。

下面来推导计算波幅的公式:

我们假设存在这样一个一次公式,可以在任意时刻根据某一个点周围前、后、左、右四个点以及该点自身的振幅来推算出下一时刻该点的振幅,那么,我们就有可能用归纳法求出任意时刻这个水面上任意一点的振幅。如左图,你可以看到,某一时刻,X0点的振幅除了受X0点自身振幅的影响外,同时受来自它周围前、后、左、右四个点(X1、X2、X3、X4)的影响(为了简化,我们忽略了其它所有点),而且,这四个点对X0点的影响力可以说是机会均等的。那么我们可以假设这个一次公式为:

    X0’ = a * (X1 + X2 + X3 + X4) + b * X0 (公式1)

a, b为待定系数,X0’ 为X0点下一时刻的振幅,X0、X1、X2、X3、X4为当前时刻的振幅

下面我们来求解a和b。

假设水的阻尼为0。在这种理想条件下,水的总势能将保持不变,水波永远波动。也就是说在任何时刻,所有点的振幅的和保持不变。那么可以得到下面这个公式:

    X0’ + X1’ + ... + Xn’ = X0 + X1 + ... + Xn

将每一个点用公式1替代,代入上式,得到:

  (4a + b) * X0 + (4a + b) * X1 + ... (4a + b) * Xn = X0 + X1 + ... + Xn => 4a + b = 1

找出一个最简解:a = 1/2、b = -1。

因为1/2可以用移位运算符 “>>” 来进行,不用进行乘除法,所以,这组解是最适用的而且是最快的。那么最后得到的公式就是:

   X0’=(X1 + X2 + X3 + X4)/ 2 - X0

好了,有了上面这个近似公式,你就可以推广到下面这个一般结论:已知某一时刻水面上任意一点的波幅,那么,在下一时刻,任意一点的波幅就等于与该点紧邻的前、后、左、右四点的波幅的和除以2、再减去该点的波幅。

应该注意到,水在实际中是存在阻尼的,否则,用上面这个公式,一旦你在水中增加一个波源,水面将永不停止的震荡下去。所以,还需要对波幅数据进行衰减处理,让每一个点在经过一次计算后,波幅都比理想值按一定的比例降低。这个衰减率经过测试,用1/32比较合适,也就是1/2^5。可以通过移位运算很快的获得。

到这里,水波特效算法中最艰难的部分已经明了,下面是Android 源程序中计算波幅数据的代码:

// 某点下一时刻的波幅算法为:上下左右四点的波幅和的一半减去当前波幅,即//    X0' =(X1 + X2 + X3 + X4)/ 2 - X0//  +----x3----+//  +     |      +//  +     |      +// x1---x0----x2//  +     |      +//  +     |      +//  +----x4----+void rippleSpread(){int pixels = m_width * (m_height - 1);for (int i = m_width; i < pixels; ++i) {// 波能扩散:上下左右四点的波幅和的一半减去当前波幅// X0' =(X1 + X2 + X3 + X4)/ 2 - X0//m_buf2[i] =(short)(((m_buf1[i - 1] + m_buf1[i + 1]+m_buf1[i - m_width] + m_buf1[i + m_width]) >> 1)- m_buf2[i]);// 波能衰减 1/32m_buf2[i] -= m_buf2[i] >> 5;}//交换波能数据缓冲区short[] temp = m_buf1;m_buf1 = m_buf2;m_buf2 = temp;
}

渲染:

然后我们可以根据算出的波幅数据对页面进行渲染。

因为水的折射,当水面不与我们的视线相垂直的时候,我们所看到的水下的景物并不是在观察点的正下方,而存在一定的偏移。偏移的程度与水波的斜率,水的折射率和水的深度都有关系,如果要进行精确的计算的话,显然是很不现实的。同样,我们只需要做线性的近似处理就行了。因为水面越倾斜,所看到的水下景物偏移量就越大,所以,我们可以近似的用水面上某点的前后、左右两点的波幅之差来代表所看到水底景物的偏移量。

在程序中,用一个页面装载原始的图像,用另外一个页面来进行渲染。先取得指向两个页面内存区的指针 src 和 dst,然后用根据偏移量将原始图像上的每一个象素复制到渲染页面上。进行页面渲染的代码如下:

void rippleRender(){int offset;int i = m_width;int length = m_width * m_height;for (int y = 1; y < m_height - 1; ++y) {for (int x = 0; x < m_width; ++x, ++i) {// 计算出偏移象素和原始象素的内存地址偏移量 ://offset = width * yoffset + xoffsetoffset = (m_width * (m_buf1[i - m_width] - m_buf1[i + m_width])) + (m_buf1[i - 1] - m_buf1[i + 1]);          // 判断坐标是否在范围内if (i + offset > 0 && i + offset < length) {m_bitmap2[i] = m_bitmap1[i + offset];}else {m_bitmap2[i] = m_bitmap1[i];}}}
}

增加波源:
       俗话说:无风不起浪,为了形成水波,我们必须在水池中加入波源,你可以想象成向水中投入石头,形成的波源的大小和能量与石头的半径和你扔石头的力量都有关系。知道了这些,那么好,我们只要修改波能数据缓冲区buf,让它在石头入水的地点来一个负的“尖脉冲”,即让buf[x,y] = -n。经过实验,n的范围在(32 ~ 128)之间比较合适。

控制波源半径也好办,你只要以石头入水中心点为圆心,画一个以石头半径为半径的圆,让这个圆中所有的点都来这么一个负的“尖脉冲”就可以了(这里也做了近似处理)。

增加波源的代码如下:

// stoneSize    : 波源半径
// stoneWeight : 波源能量
//
void dropStone(int x, int y, int stoneSize, int stoneWeight){// 判断坐标是否在范围内if ((x + stoneSize) > m_width || (y + stoneSize) > m_height|| (x - stoneSize) < 0 || (y - stoneSize) < 0) {return;}int value = stoneSize * stoneSize;short weight = (short)-stoneWeight;for (int posx = x - stoneSize; posx < x + stoneSize; ++posx)    {for (int posy = y - stoneSize; posy < y + stoneSize; ++posy)       {if ((posx - x) * (posx - x) + (posy - y) * (posy - y)< value){m_buf1[m_width * posy + posx] = weight;}}}
}

如果我们想要模拟在水面划过时引起的涟漪效果,那么我们还需要增加新的算法函数 breasenhamDrop。

void dropStoneLine(int x, int y, int stoneSize, int stoneWeight) {// 判断坐标是否在屏幕范围内if ((x + stoneSize) > m_width || (y + stoneSize) > m_height|| (x - stoneSize) < 0 || (y - stoneSize) < 0) {return;}for (int posx = x - stoneSize; posx < x + stoneSize; ++posx)    {for (int posy = y - stoneSize; posy < y + stoneSize; ++posy)       {m_buf1[m_width * posy + posx] = -40;}}
}// xs, ys : 起始点,xe, ye : 终止点
// size : 波源半径,weight : 波源能量
void breasenhamDrop (int xs, int ys, int xe, int ye, int size, int weight){int dx = xe - xs;int dy = ye - ys;dx = (dx >= 0) ? dx : -dx;dy = (dy >= 0) ? dy : -dy;if (dx == 0 && dy == 0) {dropStoneLine(xs, ys, size, weight);}else if (dx == 0) {int yinc = (ye - ys != 0) ? 1 : -1;for(int i = 0; i < dy; ++i){dropStoneLine(xs, ys, size, weight);ys += yinc;}}else if (dy == 0) {int xinc = (xe - xs != 0) ? 1 : -1;for(int i = 0; i < dx; ++i){dropStoneLine(xs, ys, size, weight);xs += xinc;}}else if (dx > dy) {int p = (dy << 1) - dx;int inc1 = (dy << 1);int inc2 = ((dy - dx) << 1);int xinc = (xe - xs != 0) ? 1 : -1;int yinc = (ye - ys != 0) ? 1 : -1;for(int i = 0; i < dx; ++i) {dropStoneLine(xs, ys, size, weight);xs += xinc;if (p < 0) {p += inc1;}else {ys += yinc;p += inc2;}}}else {int p = (dx << 1) - dy;int inc1 = (dx << 1);int inc2 = ((dx - dy) << 1);int xinc = (xe - xs != 0) ? 1 : -1;int yinc = (ye - ys != 0) ? 1 : -1;for(int i = 0; i < dy; ++i) {dropStoneLine(xs, ys, size, weight);ys += yinc;if (p < 0) {p += inc1;}else {xs += xinc;p += inc2;}}}
}

类图:

工程源码下载:http://download.csdn.net/detail/zhang957411207/4568353

THE END

Android 水波效果原理与实现相关推荐

  1. Android 水波效果 | 涟漪效果 实现

    在Xml 里面实现 给Button设置一个background <Buttonandroid:text="Ripple水波纹Background"android:layout ...

  2. android 涟漪背景,Android 水波效果 | 涟漪效果 实现

    在Xml 里面实现 给Button设置一个background ripple_background 内容如下: ripple 的Color属性,就是设置水波效果的颜色. ripple 的子节点可以设置 ...

  3. Android水波效果进度条

    Android,水波进度条 转载于:http://blog.csdn.net/u010386612/article/details/50751796 一.前言 这个自定义控件并不是我写的,而是Gith ...

  4. android水波效果,android动态壁纸中的水波纹效果

    [实例简介] android动态壁纸中的水波纹效果,采用opengl中的shader实现 [实例截图] [核心代码] @Override public String getVertexShader() ...

  5. android平台水波效果 源码

    2019独角兽企业重金招聘Python工程师标准>>> 一个比较初级的水波效果的源码,比较适合初学者. 转载:http://www.adobex.com/android/source ...

  6. Android教程之android平台水波效果!提供源码!

    基于Android平台实现的水波效果,提供源码,,效果图如下:

  7. android l 效果,[原]Android L中水波纹点击效果的实现

    博主参加了2014 CSDN博客之星评选,帮我投一票吧. 前言 前段时间android L(android 5.0)出来了,界面上做了一些改动,主要是添加了若干动画和一些新的控件,相信大家对view的 ...

  8. Android 系统(175)---Android硬件加速原理与实现简介

    Android硬件加速原理与实现简介 在手机客户端尤其是Android应用的开发过程中,我们经常会接触到"硬件加速"这个词.由于操作系统对底层软硬件封装非常完善,上层软件开发者往往 ...

  9. Android动画-Animation原理解析

    Android动画-Animation原理解析 一.概述 在android中动画分为3类,帧动画.补间动画.属性动画 今天要说的就是"补间动画",补间动画的基类是Animation ...

最新文章

  1. 「轻松支付,只需几步」使用 LeanCloud 云代码接入支付宝示例
  2. 中国棉纺织工业发展状况及未来竞争格局报告2022-2028年
  3. python中while循环_Python第12课:while循环案例 打印输出有规律的造型
  4. c++中的new、operator new、placement new
  5. Zookeeper包中,slf4j-log4j12和log4j冲突问题解决
  6. 【树状数组】递增子序列(金牌导航 数据结构优化DP-1)
  7. 调度Java应用程序中的主体
  8. 基于visual Studio2013解决算法导论之019栈实现(基于数组)
  9. 前端学习(2507):初始化多个实例化对象
  10. 如何调度spark程序_如何定时,周期性的运行程序?Python APScheduler实现任务灵活调度...
  11. 用Veritas制作MSI文件,Active Directory系列之二十四
  12. Unity开发《一起来捉妖》教程 | 2.用摄像头图像做背景
  13. ios浏览器居然不支持yyyy-MM-dd HH:mm:ss格式
  14. win10下安装maven
  15. S3C2440看门狗定时器(Watchdog)
  16. (转)鼎晖投资总裁焦震:别把投资高雅化,就是个做买卖的
  17. 微信扫一扫门禁开门小程序开发制作
  18. 修改图片exif信息
  19. 格创东智品牌形象升级,新Logo尽显创新活力
  20. MTK平台如何决定SensorMode

热门文章

  1. QT职工工资管理系统
  2. 大红喜庆版UI猜灯谜又叫猜字谜微信小程序源码下载
  3. 深度学习基础 - 积分
  4. 群晖nas介绍文档_群晖 NAS 选购 入门指南:动手打造自己的家庭数据中心
  5. 坚果云企业版服务器端,企业网盘服务器功能
  6. Java获取12306余票信息(一)
  7. FDCon2019 PPT分享以及memo整理
  8. 做好站外推广之论坛推广的技巧
  9. Facebook股价跌破20美元 这些股东早已抛售股票 包括扎克本人
  10. android微信分享怎么自定义样式,自定义微信分享样式教程