(譯)波紋效果程序解構

概述:

在計算機圖形學的一衆效果中,水波效果無疑非常能引起觀衆眼球。這是一種模擬了水在受到干擾時的行爲。

本文由兩部分組成。第一部分講述如何模擬水的行爲。第二部分介紹光線到達透明表面時如何折射。通過這兩部分知識可以寫出效果矚目的程序。

第 1 部分 - 如何模擬波

這種效果背後的機制很簡單,簡單到我甚至覺得這是在做區域採樣實驗時被偶然發現的。不過在研究計算波浪模擬之前,先來聊一下什麼是區域採樣。

a. 區域採樣

區域採樣是計算機圖形中非常常見的算法。假設有一張二維貼圖,點(x, y) 受周圍的點影響,如 (x+1,y)、(x-1、y)、(x,y+1) 和 (x,y-1)。波形模擬實際上要使用三個維度,稍後我會進行解釋。

b. 區域採樣案例:模糊效果

對貼圖模糊處理很簡單,你需要使用兩張貼圖,一張包含需要進行模糊的數據,一張保存處理後的結果。算法大致如下(使用五點採樣):

ResultMap[x, y] := ( SourceMap[x, y] +
SourceMap[x+1, y] +
SourceMap[x-1, y] +
SourceMap[x, y+1] +
SourceMap[x, y-1] ) DIV 5

(x, y) 值取決於周圍值的平均值,當然圖片模糊算法沒那麼簡單,不過思路大致如此。

波形模擬也是基於這個原理,但是(x, y)的計算方式略有不同。之前提到波形模擬需要用到三個維度,這第三個就是時間維度。換句話說,在模擬波浪計算時,我們得知道海浪在上一幀的數據,把他作爲下一幀的輸入。
這是實際的波形模擬的算法:

ResultMap[x, y] := (( CurrentSourceMap[x+1, y] +
CurrentSourceMap[x-1, y] +
CurrentSourceMap[x, y+1] +
CurrentSourceMap[x, y-1] ) DIV 2 ) -
PreviousResultMap[x, y]

當前幀中獲取的上下左右四個值除以 2,結果是平均值的兩倍,然後減去上一幀點(x, y)的值。圖 a 和 b 解釋了這段代碼的原理

figure_a.gif

水平灰線表示波的平均高度。如果(x, y) 上一幀的值低於平均水平,則波將上升到平均水平,如圖 a 所示

figure_b.gif

如果上一幀(x, y)高於平均值,如圖 b 所示,則波將向平均值下降。

c. 阻尼

每次波浪上下運動時,其能量都會分佈在一個不斷增長的區域。這意味着波的幅度會下降,直到波變平。使用阻尼因子可以模擬這一點。從當前幅度中減去一定幅度或一定百分比的因數,以使高幅度快速消失,而低幅度緩慢消失。在下面的示例中,每次移動幅度都被減去十六分之一。

d. 波形模擬示例

下面的代碼片段最初包含一些彙編代碼,我用Pascal進行了重寫,以便移植到其他語言或平臺。

.
.
const
MAXX = 320; { 貼圖的寬高 }
MAXY = 240;
DAMP = 16; { 阻尼係數 }

{ 定義 WaveMap[frame, x, y] 和 frame-indices }
var
WaveMap: Array[0..1, 0..(MAXX-1), 0..(MAXY-1)] of SmallInt;
CT, NW: SmallInt;

.
.
procedure UpdateWaveMap;
var
x,y,n: Smallint;
begin
{ 跳過邊界用以區域採樣 }
for y := 1 to MAXY-1 do begin
for x := 1 to MAXX-1 do begin
n := ( WaveMap[CT,x-1,y] +
WaveMap[CT,x+1,y] +
WaveMap[CT,x,y-1] +
WaveMap[CT,x,y+1] ) div 2 -
WaveMap[NW,x,y];

n := n - (n div DAMP);

WaveMap[NW,x,y] := n;
end;
end;
end;
.
.

執行此代碼後,結果會被繪製到圖像緩衝區。在第2部分會說明如何完成此操作。這裏的重點是繪製到緩衝區後,需要將把當前圖像數據和繪製結果進行交換用於下一次繪製迭代:

Temporary_Value := CT;
CT := NW;
NW := Temporary_Value;

 

e. 開始運動

上面的過程只是對波浪“模糊”處理。如何才能使整體動起來呢?確切地說,是通過降低波形圖中的值來實現的。不受干擾的波形圖僅包含零。要創建波浪,只需選擇一個隨機位置並更改值,如下所示:

WaveMap[x, y] := -100;

值越高,波浪越大。

第2部分-透明表面光線追蹤

現在我們有了波形圖,我們想對其進行一些樂趣。我們取一束光束,使其垂直穿過表面。因爲水的密度比空氣高,所以光束會朝着法線折射,因此我們可以計算出光束撞擊到其下方的任何位置(例如,圖像)。

首先,我們必須找出入射光與表面法線之間的夾角是多少(圖c)。

figure_c.gif

在圖c中,紅線表示表面法線。穿過波形圖的垂直線代表入射光,連接到垂直線的箭頭是折射光束。如您所見,折射光束與表面法線之間的角度小於入射光束與表面法線之間的角度。

a. 確定入射光的角度

這是通過測量(x,y)與(x-1,y)之間以及(x,y)與(x,y-1)之間的高度差來完成的。這給了我們一個底數爲1的三角形。角度等於arctan(高差/ 1)或arctan(高差)。查看圖d進行解釋:

refract.gif

在我們的情況下,計算表面法線和入射光之間的角度非常簡單。如果我們繪製一個虛構的三角形(如此處紅色所示),則只需確定alpha即可。當將y(高度差)除以x(1)時,我們得到alpha的切線。換句話說,高度差是alpha的切線,而alpha是ArcTan(height Difference)。

爲了使您相信這實際上是表面法線和入射光之間的角度,我將紅色三角形逆時針旋轉了90度。如您所見,hypothenusa與表面法線平行。

接下來,我們計算折射。如果您還記得高中物理,那您就會知道:

[bquote]折射率= sin(入射光的角度)/ sin(折射光的角度)[/ bquote],
因此可以像這樣計算折射光束的角度:

[bquote]折射光的角度= arcsin(sin (入射光的角度)/折射率)[/ bquote]
,其中折射率是水的折射率:2.0。

第三,我們需要計算折射光束擊中圖像的位置,或它相對於入射光束最初進入的位置的相對位置:

[bquote]位移= tan(折射光束的角度)*高度差[/ bquote]
透明表面光線跟蹤示例
下面的代碼片段沒有經過優化,因爲您會錯過計算的所有重要細節

for y := 1 to MAXY-1 do begin
for x := 1 to MAXX-1 do begin
xDiff := Trunc(WaveMap[x+1, y] - WaveMap[x, y]);
yDiff := Trunc(WaveMap[x, y+1] - WaveMap[x, y]);

xAngle := arctan( xDiff );
xRefraction := arcsin( sin( xAngle ) / rIndex );
xDisplace := Trunc( tan( xRefraction ) * xDiff );

yAngle := arctan( yDiff );
yRefraction := arcsin( sin( yAngle ) / rIndex );
yDisplace := Trunc( tan( yRefraction ) * yDiff );

if xDiff < 0 then begin
{ Current position is higher - Clockwise rotation }
if yDiff < 0 then
newcolor := BackgroundImage[x-xDisplace, y-yDisplace]
else
newcolor := BackgroundImage[x-xDisplace, y+yDisplace]
end else begin
{ Current position is lower - Counterclockwise rotation }
if yDiff < 0 then
newcolor := BackgroundImage[x+xDisplace, y-yDisplace]
else
newcolor := BackgroundImage[x+xDisplace, y+yDisplace]
end;

TargetImage[x, y] := newcolor;
end;
end;

附件演示了這些效果。(ps:原文並沒有附件 ⊙﹏⊙∥)

原文地址:https://www.gamedev.net/tutorials/_/technical/graphics-programming-and-theory/the-water-effect-explained-r915/ ,作者:Myopic Rhino

ps:文章是千禧年,所以別吐槽爲啥作者用Pascal編寫了,問就是 聰明的程序員 (σ°∀°)σ..:*☆ ,大家就當僞代碼看好了~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章