線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:3287
推到 Plurk!
推到 Facebook!

水波算法實例

 
bcb902146
一般會員


發表:67
回覆:33
積分:22
註冊:2002-07-20

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-09-27 21:08:19 IP:61.70.xxx.xxx 未訂閱
水波算法實例           看到左邊這幅動畫(如果沒有出現,請耐心的稍等片刻),你也許不會相信它其實是用電腦做出來的,這就是「水波」特效的魅力所在。這個小動畫是從程序的執行畫面中抓出來的,如果你想親自執行一下源程序,看看實際的效果,請先下載程序一看。  在介紹編程之前,先讓我們來回顧一下在高中的物理課上我們所學的關於水波的知識。 水波有如下幾個特性:      擴散:當你投一塊石頭到水中,你會看到一個以石頭入水點為圓心所形成的一圈圈的水波,這裡,你可能會被 這 個現象所誤導,以為水波上的每一點都是以石頭入水點為中心向外擴散的,這是錯誤的。實際上, 水波上的任何一點在任何時候都是以自己為圓心向四周擴散的,之所以會形成一個環狀的水波,是因為 水波的內部因為擴散的對稱而相互抵消了。      衰減:因為水是有阻尼的,否則,當你在水池中投入石頭,水波就會永不停止的震盪下去。      水的折射:因為水波上不同地點的傾斜角度不同,所以,因為水的折射,我們從觀察點垂直往下看到的 水底 並不是在觀察點的正下方,而有一定的偏移。如果不考慮水面上部的光線反射,這就是我們能感覺 到水波形狀的原因。     反射:水波遇到障礙物會反射。      衍射:忽然又想到這一點,但是在程序裡卻看不到,如果能在水池中央放上一塊礁石,或放一個中間有縫的隔 板,那麼就能看到水波的衍射現象了。    好了,有了這幾個特性,再運用數學和幾何知識,我們就可以模擬出真實的水波了。但是,如果你曾用3DMax做過水波的動畫,你就會知道要渲染出一幅真實形狀的水波畫面少說也得好幾十秒,而我們現在需要的是實時的渲染,每秒種至少也得渲染20幀才能使得水波得以平滑的顯示。考慮到電腦運算的速度,我們不可能按照正弦函數或精確的公式來構造水波,不能用乘除法,更不能用sin、cos,只能用一種取近似值的快速算法,儘管這種算法存在一定誤差,但是為了滿足實時動畫的要求,我們不得不這樣做。 首先我們要建立兩個與水池圖像一樣大小的數組buf1[PoolWidth*PoolHeight]和buf2[PoolWidth*PoolHeight](PoolWidth=水池圖像的象素寬度、PoolHeight=水池圖像的象素高度),用來保存水面上每一個點的前一時刻和後一時刻波幅數據,因為波幅也就代表了波的能量,所以以後我們稱這兩個數組為波能緩衝區。水面在初始狀態時是一個平面,各點的波幅都為0,所以,這兩個數組的初始值都等於0。    下面來推導計算波幅的公式    我們假設存在這樣一個一次公式,可以在任意時刻根據某一個點周圍前、後、左、右四個點以及該點自身的振幅來推算出下一時刻該點的振幅,那麼,我們就有可能用歸納法求出任意時刻這個水面上任意一點的振幅。如左圖,你可以看到,某一時刻,X0點的振幅除了受X0點自身振幅的影響外,同時受來自它周圍前、後、左、右四個點(X1、X2、X3、X4)的影響(為了簡化,我們忽略了其它所有點),而且,這四個點對a0點的影響力可以說是機會均等的。那麼我們可以假設這個一次公式為:    X0'=a(X1+X2+X3+X4)+bX0 (公式1) a、b為待定係數,X0'為0點下一時刻的振幅 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。可以通過移位運算很快的獲得。    到這裡,水波特效製作中最艱難的部分已經度過了,下面是源程序中計算波幅數據的代碼。    //******************************************************* //計算波能數據緩衝區 //******************************************************* void RippleSpread() {     for (int i=BACKWIDTH; i>1) - buf2[i]; //波能衰減 buf2[i] -= buf2[i]>>5; } //交換波能數據緩衝區 short *ptmp =buf1; buf1 = buf2; buf2 = ptmp; } 寫到這裡,我已經兩眼發暈了,呼呼——,先休息一下...... 好了,下面再來根據算出的波幅數據對頁面進行渲染。 因為水的折射,當水面不與我們的視線相垂直的時候,我們所看到的水下的景物並不是在觀察點的正下方,而存在一定的偏移。偏移的程度與水波的斜率,水的折射率和水的深度都有關係,如果要進行精確的計算的話,顯然是很不現實的。同樣,我們只需要做線形的近似處理就行了。因為水面越傾斜,所看到的水下景物偏移量就越大,所以,我們可以近似的用水面上某點的前後、左右兩點的波幅之差來代表所看到水底景物的偏移量。 在程序中,用一個頁面裝載原始的圖像,用另外一個頁面來進行渲染。先用Lock函數鎖定兩個頁面,取得指向頁面內存區的指針,然後用根據偏移量將原始圖像上的每一個象素複製到渲染頁面上。進行頁面渲染的代碼如下:(下面的代碼為了便於理解,並沒有進行優化,實際上,優化後的代碼比它要麻煩許多) 水波算法實例 //******************************************************* //根據波能數據緩衝區對離屏頁面進行渲染 //******************************************************* void RenderRipple() { //鎖定兩個離屏頁面 DDSURFACEDESC ddsd1, ddsd2; ddsd1.dwSize = sizeof (DDSURFACEDESC); ddsd2.dwSize = sizeof(DDSURFACEDESC); lpDDSPic1->Lock(NULL, &ddsd1, DDLOCK_WAIT, NULL); lpDDSPic2->Lock(NULL, &ddsd2, DDLOCK_WAIT, NULL); //取得頁面象素位深度,和頁面內存指針 int depth=ddsd1.ddpfPixelFormat.dwRGBBitCount/8; BYTE *Bitmap1 = (BYTE*)ddsd1.lpSurface; BYTE *Bitmap2 = (BYTE*)ddsd2.lpSurface; //下面進行頁面渲染 int xoff, yoff; int k = BACKWIDTH; for (int i=1; i BACKHEIGHT) {k ; continue;} if ((j xoff )< 0 ) {k ; continue;} if ((j xoff )> BACKWIDTH ) {k ; continue;} //計算出偏移象素和原始象素的內存地址偏移量 int pos1, pos2; pos1=ddsd1.lPitch*(i yoff) depth*(j xoff); pos2=ddsd2.lPitch*i depth*j; //複製象素 for (int d=0; dUnlock(&ddsd1); lpDDSPic2->Unlock(&ddsd2); } 增加波源 俗話說:無風不起浪,為了形成水波,我們必須在水池中加入波源,你可以想像成向水中投入石頭,形成的波源的大小和能量與石頭的半徑和你扔石頭的力量都有關係。知道了這些,那麼好,我們只要修改波能數據緩衝區buf,讓它在石頭入水的地點來一個負的「尖脈衝」,即讓buf[x,y]=-n。經過實驗,n的範圍在(32~128)之間比較合適。 控制波源半徑也好辦,你只要以石頭入水中心點為圓心,畫一個以石頭半徑為半徑的圓,讓這個圓中所有的點都來這麼一個負的「尖脈衝」就可以了(這裡也做了近似處理)。 增加波源的代碼如下: //***************************************************** //增加波源 //***************************************************** void DropStone(int x,//x坐標 int y,//y坐標 int stonesize,//波源半徑 int stoneweight)//波源能量 { //判斷坐標是否在屏幕範圍內 if ((x stonesize)>BACKWIDTH || (y stonesize)>BACKHEIGHT|| (x-stonesize)<0|| (y-stonesize)<0) return; for (int posx=x-stonesize; posx
------
劉維翔
佰八
一般會員


發表:0
回覆:2
積分:0
註冊:2002-08-24

發送簡訊給我
#2 引用回覆 回覆 發表時間:2002-10-02 16:33:19 IP:61.219.xxx.xxx 未訂閱
水波算法實例   看到左邊這幅動畫(如果沒有出現,請耐心的稍等片刻),你也許不會相信它其實是用電腦做出來的,這就是「水波」特效的魅力所在。這個小動畫是從程序的執行畫面中抓出來的,如果你想親自執行一下源程序,看看實際的效果,請先下載程序一看。  在介紹編程之前,先讓我們來回顧一下在高中的物理課上我們所學的關於水波的知識。 水波有如下幾個特性:      擴散:當你投一塊石頭到水中,你會看到一個以石頭入水點為圓心所形成的一圈圈的水波,這裡,你可能會被 這 個現象所誤導,以為水波上的每一點都是以石頭入水點為中心向外擴散的,這是錯誤的。實際上, 水波上的任何一點在任何時候都是以自己為圓心向四周擴散的,之所以會形成一個環狀的水波,是因為 水波的內部因為擴散的對稱而相互抵消了。      衰減:因為水是有阻尼的,否則,當你在水池中投入石頭,水波就會永不停止的震盪下去。      水的折射:因為水波上不同地點的傾斜角度不同,所以,因為水的折射,我們從觀察點垂直往下看到的 水底 並不是在觀察點的正下方,而有一定的偏移。如果不考慮水面上部的光線反射,這就是我們能感覺 到水波形狀的原因。     反射:水波遇到障礙物會反射。      衍射:忽然又想到這一點,但是在程序裡卻看不到,如果能在水池中央放上一塊礁石,或放一個中間有縫的隔 板,那麼就能看到水波的衍射現象了。    好了,有了這幾個特性,再運用數學和幾何知識,我們就可以模擬出真實的水波了。但是,如果你曾用3DMax做過水波的動畫,你就會知道要渲染出一幅真實形狀的水波畫面少說也得好幾十秒,而我們現在需要的是實時的渲染,每秒種至少也得渲染20幀才能使得水波得以平滑的顯示。考慮到電腦運算的速度,我們不可能按照正弦函數或精確的公式來構造水波,不能用乘除法,更不能用sin、cos,只能用一種取近似值的快速算法,儘管這種算法存在一定誤差,但是為了滿足實時動畫的要求,我們不得不這樣做。 首先我們要建立兩個與水池圖像一樣大小的數組buf1[PoolWidth*PoolHeight]和buf2[PoolWidth*PoolHeight](PoolWidth=水池圖像的象素寬度、PoolHeight=水池圖像的象素高度),用來保存水面上每一個點的前一時刻和後一時刻波幅數據,因為波幅也就代表了波的能量,所以以後我們稱這兩個數組為波能緩衝區。水面在初始狀態時是一個平面,各點的波幅都為0,所以,這兩個數組的初始值都等於0。    下面來推導計算波幅的公式    我們假設存在這樣一個一次公式,可以在任意時刻根據某一個點周圍前、後、左、右四個點以及該點自身的振幅來推算出下一時刻該點的振幅,那麼,我們就有可能用歸納法求出任意時刻這個水面上任意一點的振幅。如左圖,你可以看到,某一時刻,X0點的振幅除了受X0點自身振幅的影響外,同時受來自它周圍前、後、左、右四個點(X1、X2、X3、X4)的影響(為了簡化,我們忽略了其它所有點),而且,這四個點對a0點的影響力可以說是機會均等的。那麼我們可以假設這個一次公式為:    X0'=a(X1+X2+X3+X4)+bX0 (公式1) a、b為待定係數,X0'為0點下一時刻的振幅 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。可以通過移位運算很快的獲得。    到這裡,水波特效製作中最艱難的部分已經度過了,下面是源程序中計算波幅數據的代碼。    //******************************************************* //計算波能數據緩衝區 //******************************************************* void RippleSpread() { for (int i=BACKWIDTH; i { //波能擴散 buf2[i] = ((buf1[i-1]+ buf1[i+1]+ buf1[i-BACKWIDTH]+ buf1[i+BACKWIDTH]) >>1) - buf2[i]; //波能衰減 buf2[i] -= buf2[i]>>5; }    //交換波能數據緩衝區 short *ptmp =buf1; buf1 = buf2; buf2 = ptmp; }    寫到這裡,我已經兩眼發暈了,呼呼——,先休息一下......    好了,下面再來根據算出的波幅數據對頁面進行渲染。    因為水的折射,當水面不與我們的視線相垂直的時候,我們所看到的水下的景物並不是在觀察點的正下方,而存在一定的偏移。偏移的程度與水波的斜率,水的折射率和水的深度都有關係,如果要進行精確的計算的話,顯然是很不現實的。同樣,我們只需要做線形的近似處理就行了。因為水面越傾斜,所看到的水下景物偏移量就越大,所以,我們可以近似的用水面上某點的前後、左右兩點的波幅之差來代表所看到水底景物的偏移量。    在程序中,用一個頁面裝載原始的圖像,用另外一個頁面來進行渲染。先用Lock函數鎖定兩個頁面,取得指向頁面內存區的指針,然後用根據偏移量將原始圖像上的每一個象素複製到渲染頁面上。進行頁面渲染的代碼如下:(下面的代碼為了便於理解,並沒有進行優化,實際上,優化後的代碼比它要麻煩許多)    水波算法實例   //******************************************************* //根據波能數據緩衝區對離屏頁面進行渲染 //******************************************************* void RenderRipple() { //鎖定兩個離屏頁面 DDSURFACEDESC ddsd1, ddsd2; ddsd1.dwSize = sizeof (DDSURFACEDESC); ddsd2.dwSize = sizeof(DDSURFACEDESC); lpDDSPic1->Lock(NULL, &ddsd1, DDLOCK_WAIT, NULL); lpDDSPic2->Lock(NULL, &ddsd2, DDLOCK_WAIT, NULL);    //取得頁面象素位深度,和頁面內存指針 int depth=ddsd1.ddpfPixelFormat.dwRGBBitCount/8; BYTE *Bitmap1 = (BYTE*)ddsd1.lpSurface; BYTE *Bitmap2 = (BYTE*)ddsd2.lpSurface;    //下面進行頁面渲染 int xoff, yoff; int k = BACKWIDTH; for (int i=1; i { for (int j=0; j { //計算偏移量 xoff = buf1[k-1]-buf1[k+1]; yoff = buf1[k-BACKWIDTH]-buf1[k+BACKWIDTH];    //判斷坐標是否在窗口範圍內 if ((i+yoff )< 0 ) {k++; continue;} if ((i+yoff )> BACKHEIGHT) {k++; continue;} if ((j+xoff )< 0 ) {k++; continue;} if ((j+xoff )> BACKWIDTH ) {k++; continue;}    //計算出偏移象素和原始象素的內存地址偏移量 int pos1, pos2; pos1=ddsd1.lPitch*(i+yoff)+ depth*(j+xoff); pos2=ddsd2.lPitch*i+ depth*j;    //複製象素 for (int d=0; d Bitmap2[pos2++]=Bitmap1[pos1++]; k++; } } //解鎖頁面 lpDDSPic1->Unlock(&ddsd1); lpDDSPic2->Unlock(&ddsd2); }    增加波源  俗話說:無風不起浪,為了形成水波,我們必須在水池中加入波源,你可以想像成向水中投入石頭,形成的波源的大小和能量與石頭的半徑和你扔石頭的力量都有關係。知道了這些,那麼好,我們只要修改波能數據緩衝區buf,讓它在石頭入水的地點來一個負的「尖脈衝」,即讓buf[x,y]=-n。經過實驗,n的範圍在(32~128)之間比較合適。    控制波源半徑也好辦,你只要以石頭入水中心點為圓心,畫一個以石頭半徑為半徑的圓,讓這個圓中所有的點都來這麼一個負的「尖脈衝」就可以了(這裡也做了近似處理)。    增加波源的代碼如下:    //***************************************************** //增加波源 //***************************************************** void DropStone(int x,//x坐標 int y,//y坐標 int stonesize,//波源半徑 int stoneweight)//波源能量 { //判斷坐標是否在屏幕範圍內 if ((x+stonesize)>BACKWIDTH ||  (y+stonesize)>BACKHEIGHT|| (x-stonesize)<0|| (y-stonesize)<0) return; for (int posx=x-stonesize; posx for (int posy=y-stonesize; posy if ((posx-x)*(posx-x) (posy-y)*(posy-y) < stonesize*stonesize) buf1[BACKWIDTH*posy posx] = -stoneweight; } 好了,至此,水波特效的製作原理就此就全部揭示了。在上面的推導中,每一步都進行了很多看似非常過分的近似處理,但是,你完全不必擔心,事實證明,用這種方法,在速度和圖像上都可以獲得非常好的效果。源程序中有非常詳盡的註釋,仔細推敲一下,看懂它們應該不成問題。 這個程序是Win32下的DirectX編程,沒有使用任何包裝庫。在我的電腦上(AMDK6-200、2MVRam、64MSRam),320x240的畫面大小,每秒可以達到25幀。與前幾個程序不一樣,這個程序使用了窗口模式,所以調試起來很方便。如果你對窗口模式編程不熟悉,這個程序也是一個很好的例子。 這種用數據緩衝區對圖像進行水波處理的方法,有個最大的好處就是,程序運算和其示的速度與水波的複雜程度是沒有關係的,無論水面是風平浪靜還是波濤洶湧,程序的fps始終保持不變,這一點你研究一下程序就應該可以看出來。實際上,如果你掌握了這種方法,將這種方法推廣一下,完全可以做出另外一些特殊的效果,如煙霧、大氣、陽光等,我現在也正在研究這些特效的製作,相信不久以後就會有新的收穫。 劉維翔
bestlong
站務副站長


發表:125
回覆:734
積分:506
註冊:2002-10-19

發送簡訊給我
#3 引用回覆 回覆 發表時間:2006-08-13 23:57:31 IP:211.20.xxx.xxx 未訂閱

請問 DDSURFACEDESC 要如何引入呢 ? 來源是什麼控件?

------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
系統時間:2017-10-21 16:17:42
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!