1. 程式人生 > >Android 上實現水波特效

Android 上實現水波特效

Android 上實現水波特效    

羅朝輝(http://www.cppblog.com/kesalin

說明:

       本文水波演算法部分整理自 GameRes 上的資料,原作者 Imagic。我只是在學習 Android 的過程中,想到這個特效,然後就在Android 上實現出來,並在源演算法的基礎上添加了雨滴滴落特效,以及劃過水面時的漣漪特效。 該程式在模擬器和真機上執行速度都較慢,需要進一步優化或使用 JNI 實現,如果你想到好的優化演算法,請聯絡我:[email protected]

    本文pdf文件下載:點選這裡    

基礎知識:

在講解程式碼之前,我們來回顧一下在高中的物理課上我們所學的關於水波的知識。水波有擴散,衰減,折射,反射,衍射等幾個特性:

擴散:當你投一塊石頭到水中,你會看到一個以石頭入水點為圓心所形成的一圈圈的水波,這裡,你可能會被這個現象所誤導,以為水波上的每一點都是以石頭入水點為中心向外擴散的,這是錯誤的。實際上,水波上的任何一點在任何時候都是以自己為圓心向四周擴散的,之所以會形成一個環狀的水波,是因為水波的內部因為擴散的對稱而相互抵消了。

衰減:因為水是有阻尼的,否則,當你在水池中投入石頭,水波就會永不停止的震盪下去。

折射:因為水波上不同地點的傾斜角度不同,所以我們從觀察點垂直往下看到的水底並不是在觀察點的正下方,而有一定的偏移。如果不考慮水面上部的光線反射,這就是我們能感覺到水波形狀的原因。

反射:水波遇到障礙物會反射。

衍射:在水池中央放上一塊礁石,或放一箇中間有縫的隔板,那麼就能看到水波的衍射現象了。

演算法推導:

好了,有了這幾個特性,再運用數學和幾何知識,我們就可以模擬出真實的水波了。但是,如果你曾用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, intstoneWeight) {

   // 判斷座標是否在螢幕範圍內

   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;

         }

      }

   }

}

效果圖:


劃過水面時的漣漪特效


雨滴滴落水面特效

結語:

這種用資料緩衝區對影象進行水波處理的方法,有個最大的好處就是,程式運算和顯示的速度與水波的複雜程度是沒有關係的,無論水面是風平浪靜還是波濤洶湧,程式的fps始終保持不變,這一點你研究一下程式就應該可以看出來。

相關推薦

Android 實現水波特效

Android 上實現水波特效     羅朝輝(http://www.cppblog.com/kesalin) 說明:        本文水波演算法部分整理自 GameRes 上的資料,原作者 Imagic。我只是在學習 Android 的過程中,想到這個特效,然後就在A

[譯] 在 Android 實現 Google Inbox 的樣式動畫

原文地址:Implement Google Inbox Style Animation on Android 原文作者:Huan Nguyen 譯文出自:掘金翻譯計劃 本文永久連結:github.com/xitu/gold-m… 譯者:YueYong 校對者:zx-

Android實現SSL握手,實現伺服器和客戶端之間Socket互動

public class MySSLSocket extends Activity {      private static final int SERVER_PORT = 50030;//埠號      private static final String SERVER_IP = "218.206.17

OpenCV在Android實現人臉背景虛化

1. 背景 在手機拍照技術日新月異的今天,很多手機廠商都陸續上了雙攝,並衍生出人像模式(背景虛化)。雖然博主不是很懂雙攝虛化的原理,但是看到一些樣張之後,還是被深深吸引,覺得很漂亮,很有虛幻的感覺。 自己也想動手做一個“影象虛化”的Demo來玩玩,當然並

Linux Centos搭建rtmp直播環境Android實現推流

本文目的: 在VPS伺服器上配置一個直播環境,在Android&IOS客戶端實現,直播推流到伺服器上,在任意播放器上實現,拉取伺服器上的流觀看直播。(附android原始碼) 伺服器環境: LSB Version:    :core-4.1-amd64:core-

Android實現WebView控制元件的完整截圖

最近總能看到好多APP都支援文章和網頁的長截圖,出於好奇研究了一下,分享給大家。 網上有好多的例子,其中好多都是已經過時的就不在複述了,我發現有一種還是比較通用的方法。 //android 5.0 之後需要開啟瀏覽器的整體快取才能擷取整個Web if (

Android實現一個簡單的天氣預報APP(六) 更新介面資料

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 前面我們已經設定好了基本的介面,獲取了網路上的天氣資料並解析出來了,接下來,我們要將介面上胡亂寫的天氣資料更新為實時獲取的真實的天氣資料。 1)

Android實現一個簡單的天氣預報APP(十) 城市列表搜尋框

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 前面我們已經實現了點選城市列表ListView中的Item,實現更新天氣資訊的動作。接下來,我們將每個item的資訊補充的更為完整,並實現搜尋功

cocos2dx-lua在android實現生成及掃描二維碼

首先說明下,生成二維碼是用android原生的BitMatrix和Bitmap類來生成的,而掃描二維碼用到了google官方的zxing包(core.jar)。 這裡我把所有生成二維碼的程式碼和lua呼叫的掃描二維碼方法都放在了專案->frameworks->

android實現二維碼生成和掃描

先在androidstudio上匯入libzing的Module,然後再app上把那個libzing新增上去,這樣我們的app就關聯了那個libzing庫了 然後我們就只在app這個工程下寫程式碼就行了。activity_main.xml佈局如下

Android實現一個簡單的天氣預報APP(十五) 釋出天氣預報APP

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 準備: 一張應用圖示 apk檔案 4~6張程式執行截圖 操作: 登入平臺,例如360移動開發平臺:http://dev.360.cn 按操作填寫

羅雲彬:實現水波特效的程式碼例子

_=_ _=_ Part 001 of 001 of file Ripper.zip _=_ begin 666 Ripper.zip M4$L#!!0````(`)BM5#&=/#HXK(H```!J`0`*````4FEP<&5R+F5X9>Q

Android實現一個簡單的天氣預報APP(三) 獲取網路資料

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 前面我們已經配置好了介面佈局,顯示佈局上的資料都是我們胡亂載入的,接下里我們要將這些資料更新為網路上的真實資料 1)檢查網路連線狀態 1.新建一

android實現富文字

控制元件上設定富文字可以減少很多不必要的佈局 樣例程式碼: SpannableString s = new SpannableString("MPAndroidChart developed\n by Philipp Jahoda"); s.setSpan(new R

Android實現一個簡單的天氣預報APP(十三) 導航ViewPager

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details ViewPager是安裝軟體後,第一次開啟軟體時展示的導航。 1)在進入天氣介面之前,先進入導航介面 1.建立一個導航佈局 guide.xml一

Android實現一個簡單的天氣預報APP(十二) 未來三天的天氣預報

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 前面我們已經可以獲取當天的天氣資料,並在螢幕上更新資料了,接下來我們獲取未來三天的天氣預報資料。 1)配置未來三天的佈局 在佈局檔案main.x

Android實現一個簡單的天氣預報APP(八) 從資料庫讀取城市資料

學習參考資源:https://www.gitbook.com/book/zhangqx/mini-weather/details 前面我們已經實現了今日天氣的主介面佈局,並可以從網路上實時獲取天氣資料更新到介面上,並通過按鈕切入選擇城市介面。接下來,我們通過讀取資料庫檔案獲

用Anko和Kotlin實現Android的對話框和警告提示(KAD 24)

posit eve linear 免費 clas testing size uil 如何 作者:Antonio Leiva 時間:Mar 9, 2017 原文鏈接:https://antonioleiva.com/dialogs-android-anko-kotlin/

Android簡單實現將手機圖片傳到server中

sdk etc mov 創建 ast bmi 以及 lena ews 在本例中。將會簡單的實現安卓手機將圖片上傳到server中。本例使用到了 server端:PHP+APACHE 客戶端:JAVA 先簡單實現一下server端的上傳並測試上傳

Android快速實現傳項目到Github

alt+ 紅色 選擇 index.php 就會 打開 ads 倉庫 http 本文為skylinelin原創,轉載請註明出處! 一、簡介 現在在網上瀏覽關於Git的文章,基本上都是使用命令行(Git Bash),命令行效率是很高的,但是有一定的復雜性,現在我們看如何用A