Android 算法 水纹

2023-10-25 18:50
文章标签 算法 android 水纹

本文主要是介绍Android 算法 水纹,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基础知识:

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

扩散 : 当你投一块石头到水中,你会看到一个以石头入水点为圆心所形成的一圈圈的水波,这里,你可能会被这个现象所误导,以为水波上的每一点都是以石头入水点为中 心向外扩散的,这是错误的。实际上,水波上的任何一点在任何时候都是以自己为圆心向四周扩散的,之所以会形成一个环状的水波,是因为水波的内部因为扩散的 对称而相互抵消了。

衰减 :因为水是有阻尼的,否则,当你在水池中投入石头,水波就会永不停止的震荡下去。

折射 :因为水波上不同地点的倾斜角度不同,所以我们从观察点垂直往下看到的水底并不是在观察点的正下方,而有一定的偏移。如果不考虑水面上部的光线反射,这就是我们能感觉到水波形状的原因。

反射 :水波遇到障碍物会反射。

衍射 :在水池中央放上一块礁石,或放一个中间有缝的隔板,那么就能看到水波的衍射现象了。

算法推导:

好了,有了这几个特性,再运用数学和几何知识,我们就可以模拟出真实的水波了。但是,如果你曾用 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/32

//

m_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 + xoffset

          offset = ( 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, intweight)

{

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;

         }

      }

   }

}

效果图:


划过水面时的涟漪特效

雨滴滴落水面特效

结语:

这种用数据缓冲区对图像进行水波处理的方法,有个最大的好处就是,程序运算和显示的速度与水波的复杂程度是没有关系的,无论水面是风平浪静还是波涛汹涌,程序的 fps 始终保持不变,这一点你研究一下程序就应该可以看出来

转载于:https://www.cnblogs.com/try-catch/p/4482300.html

这篇关于Android 算法 水纹的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/284522

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int