2012年09月16日

離散フーリエ変換による海面の生成 その4

GLSLでレンダリングしてみました。

フレネルの式の近似で反射率を計算して、反射したベクトルはスカイドームから、屈折分はただ単に水色を合成しています。

スカイドームは下記のデータを使用させていただきました。
ttp://www.nicovideo.jp/watch/sm10968092

実行結果



スカイドームの使い方を間違えている気がしますので、少し微妙です。
白波やコースティクスなどもできていないので、もうちょっとレンダリング方法をちゃんと調べる必要がありそうです。

web拍手 by FC2
posted by シンドラー at 19:26 | Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2012年09月13日

離散フーリエ変換による海面の生成 その3

今日は法線ベクトルの計算、FFTの実装、ポリゴンでの表示まで行いました。

法線ベクトルは、FFTを使用する方法と、近傍の高さを用いる方法があるようでしたので、今回も前々回の[3]を参考にさせていただき、FFTを使用する方法を実装しました。

FFTについては[1]を参考にさせていただきました。DFTと比べてこんなに速いんですね。

ポリゴンの表示は、シェーダも何も使わず、GL_TRIANGLESで描画しているだけです。

実行結果

FFTのおかげで、128x128のメッシュにして、高さ、Choppy、法線の3回FFTを行ってもリアルタイムで動くようになりました。CUFFT等を使えばもっと速くなるかもしれません。

風向きは左奥、波をあまり高くしすぎないようにして、Choppyのλを0.5にしています。



波の部分は大体できたと思いますので、次は白波やコースティクスなど、レンダリングの方をやっていきたいですね。

[1] ttp://www.kurims.kyoto-u.ac.jp/~ooura/fftman/index.html

web拍手 by FC2
posted by シンドラー at 22:34 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

離散フーリエ変換による海面の生成 その2

続きです。Choppy wavesを試してみました。
Choppyというのは、波が荒いとか、波立ったという意味だそうです。

Choppy wavesは、波の位置x=(x, z)にλD(x, t)を加えることで、尖った波を表現する方法のようです。
D(x, t)は、高さフィールドを計算するために生成したh~(k,t)を使って、以下の式で計算できるようです。

exp008.png

その後難しそうな計算が続いてよくわかりませんので、前回の参考文献[3]のソースコードを参考にさせていただきました。

waves関数内のchoppyの部分ですが、-i(n1,n2)/sqrt(n1^2+n2^2) * h~(n1, n2)を計算して、それを逆フーリエ変換して符号の調整を行うとDが計算できるようです。
FFTW用をDFT用に書き換えたため間違っているかもしれませんが、逆フーリエ変換後のdx(赤)とdz(緑)として、下図のようなデータが得られました。

choppy_after.png

実行結果

FFTがまだ実装できていないので、とりあえず64x64のメッシュをワイヤフレームでOpenGLで描画してみました。



最初の方がλ=0で、Choppyなしの波になっています。後半はλを少しずつ増やしていき、今回の例では0.6辺りが違和感ない係数でした。

Choppyを入れるとかなり波っぽくなっている感じがします。

次はFFTの実装とちゃんとしたレンダリングを行いたいと思います。

web拍手 by FC2
posted by シンドラー at 02:56 | Comment(2) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2012年09月12日

離散フーリエ変換による海面の生成 その1

ランダムノイズ関係の続きです。

海面の生成には、FFT(高速フーリエ変換)を用いた方法が主流のような気がします。
探し方が悪いのかもしれませんが、日本語の情報があまり見当たらないので調べながら試していきます。
(良いページがありましたら教えてください。)

フーリエ変換での海面生成では、大体のところで参考文献[1]をそのまま使ったり改良していたりするようです。
以下は和訳というわけではなくて適当に書いていますので間違っている可能性があります。

FFTベースの海面の高さh(x, t)は、座標x=(x, z)の位置において、次の式で計算します。

ex001.png

ここで、tが時間、kが2次元ベクトルk=(kx, kz)で、kx=2πn/Lx, kz=2πm/Lzでnとmが-N/2≦n<N/2,-M/2≦m<M/2です。
FFTの処理で、離散点x=(nLx/N, mLz/M)における高さフィールドを生成します。ここで、h~(k,t)がフーリエ変換後の周波数領域でのフーリエ係数で、これをランダムノイズをうまく使って逆フーリエ変換すれば海面のようなものが得られる、ということです。

このh~(k,t)をどうやって求めるかということですが、h~0(k)を求めておいて、あとは時間tでexpの部分を変化させることで求めます。
最初のh~0(k)は、次の式で求めます。

ex002.png

ζrとζiは、ガウス乱数(正規分布に従った乱数)で、Box-Muller法[2]などで生成できます。今回は平均0、分散1の乱数を生成します。
iは虚数単位ですので、計算機で計算する場合は(複素数を扱えない場合は)分けて計算する必要があります。

Ph(k)は、Phillips spectrumというもので、風の向きなどを反映させることができるようです。式は次のようになります。

ex003.png

ここで、L=V^2/gで、Vが風速、gが重力定数(≒9.8)、w^が風の方向ベクトル、Aが定数、|k^・w^|^2がコサイン要素で、この要素によって風の方向に動いているように見えるそうです。
この式ではkとkがあって分かりにくかったのですが、kはkの大きさ(=sqrt(kx*kx+kz*kz))のようです。

上の式では、|k|の大きさが値に大きく影響してしまうので、l<<Lとなる小さな長さを定義して、次の式の値を掛けることで抑えることができます。

ex004.png

h~0(k)が求まれば、後は時間で変化させていけばいいということになります。h~(k,t)は次式で計算できます。

ex005.png

ここで、w(k)は周波数で、以下の式などで計算できます。

ex006.png

以上で求まったh~(k,t)を逆フーリエ変換すれば、波の高さh(x, t)が求まることになります。

正直ここまで読んでも実装の仕方がよくわからなかったので、参考文献[3]のソースコードを参考にさせていただきました。

[3]では、FFTWという高速なFFTのライブラリを使用しているのですが、自分は自分で作ったDFTの関数を使っています。
実装はwater.cでほぼ行われています。

wave関数で海面の生成を行っています。まず最初に、initializeWaves関数で、h~0(k)の計算を行っています。
その中で、Phillips関数でPhillips Spectrumを計算して、FFTを使用するために実部と虚部を持つFFTW用の構造体に、(25)式を少し変更した形で計算してあります。

次に、時間tに依存したh~(k,t)の計算を行います。(26)式ではexpの形式で書かれていますが、オイラーの公式e^iθ=cosθ+isinθを使って、式の前半(plus)と後半(minus)、実部(.re)と虚部(.im)の計算に分けています。
このソースコードでは、FFTを用いる場合には半分のデータでいいので、N/2までのループを回しています。

フーリエ変換後の係数は、実部のみのデータをフーリエ変換した場合、0番目が低周波成分、実部の残りのデータがN/2を中心とした線対称の値、虚部の残りのデータがN/2を中心とした点対称の値をとりますので、FFTWを使用する場合には半分でいいということだと思います。
この辺りのお話は離散フーリエ変換で検索すると出てくるかと思います。

自分ではまだFFTを実装していないので、0〜N-1まで計算しています。
その結果を赤を実数、緑を虚数として画像化したh~0(k)の結果(左)と、ある時刻tのh~(k,t)を画像化した結果(右)が以下のようになりました。

h0k.png h1k.png

これは、恐らく風の方向が原因で角度は違いますが、h~(k,t)に関して[4]の左図と同じような傾向が得られているので、ここまでは合っているかと思います。

で、IDFTを行って高さ情報にしてみると、以下のような拡大すると市松模様が入ったような結果が得られました。

big001.png

FFTで計算しているわけではないのでバグの可能性もあるのですが、参考にしたソースコードでは、下記のようなコメントが書かれており、(-1)^(i+j)された値になっているので戻す必要があると書かれていました。

ex007.png

これを計算すると、下記のように一応それっぽい結果が得られました。

idft001.png

実行結果

時刻tを変化させた場合の結果です。



三角関数の重ね合わせでは滑らかになりすぎるので、Choppy Wavesというもので鋭い波を作るところもあるのですが、その辺は後日にしたいと思います。

[1] Jerry Tessendorf: "Simulating Ocean Water", http://graphics.ucsd.edu/courses/rendering/2005/jdewall/tessendorf.pdf
[2] "Generating Gaussian Random Numbers", http://www.taygeta.com/random/gaussian.html
[3] http://www.people.fas.harvard.edu/~reichard/water/
[4] http://www.edxgraphics.com/2/post/2011/10/simulating-ocean-waves-with-fft-on-gpu.html
web拍手 by FC2
posted by シンドラー at 01:38 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2012年09月05日

ランダムノイズによるテクスチャ生成について その2

昨日の続きです。

Wood(木)


自然な見た目の木の年輪は、次の数式にturbulenceを加えることで生成できます。

noise_wood0.png

上記のパターンを得るためには、中心からある座標までの距離のsineをとればよいので、座標x,yのピクセルの色は256*sin(sqrt(x*x+y*y))になります。
このパターンにturbulence項を加えることで、より自然な木の見た目を得ることができます。

木の色に見えるようなRGB値は、次のように計算できます。

(参考サイトにソースコードがあります)

ここでは、年輪は見えることを想定しているため、マーブルの場合と違って、turbPowerは小さくするべきです。

noise_wood1.png

これは、年輪の数を増やす、つまりxyPeriodを25に設定した結果です:

noise_wood2.png

これは再び12個の年輪ですが、よりturbulenceにするために、turbPowerを0.2に設定しています:

noise_wood3.png

turbPowerを大きくしすぎると、年輪が見えなくなり、マーブルパターンのような結果を得ることになります。
この場合、turbPowerを0.5に設定しています:

noise_wood4.png

数学的な2次元関数にノイズを加えることによって、自然の見た目が得られることが分かったと思います。
同様に、色々な関数で試すことができます。例えば、sin(x)+sin(y)というパターンは、次のようになります:

noise_example0.png

次のコードでノイズを加えます:

(参考サイトにソースコードがあります)

次の結果が得られます:

noise_example1.png

2次元のランダムノイズは、地形の高さマップや、物理シミュレーションなどに使用することもできます。

3D Random Noise

ランダムノイズは、どのような次元数にも拡張できます。
3次元への拡張には、幅と高さに加えて、深度を表すZ要素を追加する必要があります。

(参考サイトにソースコードがあります)

generateNoise関数も、3次元に拡張する必要があります:

(参考サイトにソースコードがあります)

平滑化の関数も、4近傍ではなくx、y、z方向それぞれに補間する必要があるため、8項でてきます:

(参考サイトにソースコードがあります)

turbulence関数は、z/sizeをsmoothNoise関数の引数に追加するだけです:

(参考サイトにソースコードがあります)

以下に示すメイン関数は、3次元のランダムノイズによって生成された時間でアニメーションする雲です。
雲は時間とともに滑らかに形状が変化していきます。

(参考サイトにソースコードがあります)

スクリーンショットでは、もちろんアニメーションは示せません:

noise_cloud3D_000.png

3次元のランダムノイズは、2次元テクスチャのアニメーション、3次元テクスチャ(中身が詰まった岩で、一部切り取った内部でもテクスチャパターンが存在するようなもの)、3次元惑星テクスチャ、3次元ボリューム霧などに使用することができます。

[1] http://lodev.org/cgtutor/randomnoise.html

付録:
3Dテクスチャを動画にしてみました。
上が線形補間、下がLanczos2補間です。
線形補間の場合は縦横の線が気になってしまいます。




ただ、効率よく計算してないからなんでしょうが、3次元のLanczos2補間は時間がかかりすぎですね。
バイキュービックかcosine補間ぐらいにしておいた方が良さそうです。

web拍手 by FC2
posted by シンドラー at 19:02 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2012年09月04日

ランダムノイズによるテクスチャ生成について その1

以前はPerlin Noiseをそのままソースコードを使用させていただいていましたが、勉強ということで今回はランダムノイズについて試してみました。
基本ランダムノイズは、ランダム関数で生成した点を関数補間するようです。
しかし、ただ関数補間しただけでは滑らかすぎるので、周波数を変えた複数の関数で補間して、その結果を足し合わせる、ということだと思います。

以下は参考サイトの適当和訳です。
張ってある画像は参考サイトを参考に作成したプログラムで生成したもので、それ以外は参考サイト参照としてあります。

Lodeのコンピュータグラフィックスチュートリアル[1]

ランダムノイズを使用したテクスチャ生成

序論

自然は、全てランダムな見た目を持っているように見えるので、数式でランダムな見た目を生成することは基本的にはできません。
Ken Perlinによって導入されたPerlinノイズのようなランダムノイズは、自然のような見た目のテクスチャを生成するために乱数を使用します。

Smooth Noise

ランダムノイズのためのソースとして、私たちはnoise[x][y]と呼ばれるランダム値の配列を必要とします。
私たちの興味は2次元テクスチャの生成にあるので、2次元配列を使用します。
generateNoise関数は、ノイズで配列を満たし、メイン関数はこのノイズ配列を画面に表示するように書かれています。
ノイズ自身は、<cstdlib>ヘッダファイルにあるrand()関数によって生成されています。
rand()関数は、0〜32768(ヘッダファイルで定義されている)のInt型のランダム値を返します。
これは、32768.0で割ることにより、0〜1の実数に正規化することができます(注:float型で割ることを忘れないでください)。

(参考サイトにソースコードあります)

これが生成されたノイズです:

noise.png

このノイズは、拡大すると特に自然には見えません。xとyを8で割ることによって拡大すると、ブロックっぽい画像が得られます。

(参考サイトにソースコードあります)

拡大する時は、もっと滑らかにしたいので、線形補間します。
現在は、ノイズの配列を示すインデックスを減らしただけです。
分数の部分で線形補完を使用することによって、より滑らかにすることができます。
そのための新しいsmoothNoise関数を紹介します:

(参考サイトにソースコードあります)

これはランダムノイズにとても有効です。滑らかにする方法はもっと良いものがあるかもしれませんが、バイリニア補間はグラフィックボードがゲームなどでテクスチャの平滑化に使用するもので、コストが低く高速な手法です。

noise_bilinear.png

この画像を"ノイズテクスチャ"と呼びましょう。

Turbulence(乱流?)

Turbulenceは、平滑化したノイズをより自然に見せるためのものです。トリックは、異なった拡大率の複数のノイズテクスチャを重ね合わせることにあります。
この自然表現の一例として、山脈があります。ここに、とても大きい(ノイズが深く拡大された)特徴があります。

(参考サイトの画像参照)

次に、山に小さな特徴(複数の頂上、傾斜地の振動など)を加算します。

(参考サイトの画像参照)

さらに、より小さいスケールのもの(山の上の岩のような)を加えます。

(参考サイトの画像参照)

砂のようなより小さいレイヤを加えます。全てのレイヤを足し合わせた結果は、自然な山のような形になります。

2次元では、これは異なるサイズの平滑化されたノイズを重ね合わせることでできます。

(参考サイトの画像参照)

拡大率は、ここでは16から始まり、2で割っていきます。これを拡大率が1.0になるまで繰り返します。
山の例の小さい特徴は、幅だけでなく高さについても小さくなります。
これを2次元のテクスチャで実行すると、小さい拡大画像がより暗くなり、加算する効果が少なくなります:

(参考サイトの画像参照)

この5枚の画像を加算して、平均を取るために5で割ることで、あなたはturbulenceテクスチャを得ることができます。

noise_turbulence16.png

次の関数は、上記の処理を一つのピクセルで自動的に行う関数です。パラメータ"size"が初期拡大率で、上記の例では16になります。
戻り値は0~255の間の値になるように正規化します。

(参考サイトにソースコードがあります)

turbulence関数を使用するためには、次のように全てのピクセルを計算するループのコードの一部を変更する必要があります。

(参考サイトにソースコードがあります)

sizeに64を設定した結果はこのようになります:

noise_turbulence64.png

256に設定した場合、結果はより大きく滑らかになります:

noise_turbulence256.png

そしてとても小さい値、8を設定すると次のようになります:

noise_turbulence8.png

このテクスチャには、バイリニアフィルタの平滑化関数を使用しているため、明らかな縦と横の線があります。
PhotoshopのCloudsフィルタは、上記に似たテクスチャを生成しますが、より良い平滑化関数を使用しています。
より良い平滑化関数は、この記事の範囲外です。

もしあなたが平滑化関数を使用しなければ、下記のような画像を得ます:

(参考サイトの画像参照)

Clouds(雲)

雲がある空を生成するためには、上記のturbulenceテクスチャを使用することができますが、白黒の変わりに青白カラーパレットを使用する必要があります。
そのために、HSLtoRGB関数が使用できます。色相は、青(169または249度)に設定し、明度は192から255に設定すれば良いです。
新しいメイン関数はこのようになります:

(参考サイトにソースコードがあります)

turbulence結果を(4の変わりに)3で割ることで、ピクセルの色パラメータが大きくなりすぎるかもしれませんが、通常は明るい雲が得られます:

noise_clouds64.png

Marble(マーブル)

マーブルのようなテクスチャもランダムノイズを使用することによって生成できます。
これを生成するためには、sineパターンをベースとします:

noise_marble_sin.png

sineテクスチャは、ピクセル位置(x, y)と、カラー値255*sin(x+y)で生成できます。
あなたは角度と周期(=線の数)を、xとyに倍率を掛けることで変更することができます。
sineパターンは、暗い線と明るい線を持ち、turbulence項を加えることによって、それらの線を乱すことができ、
マーブルのような見た目を得ることができます:

(参考サイトにソースコードがあります)

"xyValue"は、x、y、そしてturbulenceに倍率を掛けて足したものです。
xPeriod、yPeriod、そしてturbPowerは、異なったテクスチャを得るために変更することができるパラメータです。
turbulence関数は、0〜255の値を返すように作成したため、0〜1の値に変換するためにturbulenceを256で割っています。
上記プログラムで得られる結果は次のようになります:

noise_marble32.png

turbPowerを小さくすることで、ねじれの小さい結果を得ることができます。
例えば、もし1.0に設定すれば、次の結果が得られます:

noise_marble1.png

sineパターンを使用することによって、(黒と白の線が少しねじれるだけで)自然な見た目が得られ、より良く見えるでしょう。

turbulence関数の初期サイズを変更することで、ねじれが大きく(そしてより鋭く、turbPowerを小さくするような)なり、そして初期サイズを小さくすることでより積極的なねじれを得ることができます。
ここでは、turbPowerを5.0に設定し、turbSizeをそれぞれ128.0と16.0にした結果を示します:

noise_marble128.png noise_marble16.png

xとyの周期を変更することで、黒い線を増やしたり減らしたりすることができます。
例えば、1つの黒い横線のみにするために、xPeriodを0、yPeriodを1に設定することで、0度の角度の広い線を作ることができます。
線の方向を良く見えるようにするために、turbSizeは32に設定し、turbPowerは1に設定します:

noise_marble_line.png

turbPowerを5.0に戻す以外は同じパラメータにすると、1本の黒線からできたという事実を隠してしまうような十分大きなturbulenceを見ることができます:

noise_marble_line5.png

あなたはR,G,Bで異なる値を使用することによって、マーブルの色を変更することができます。
例えば、少し赤または黄色にしたい場合は、次のようにします:

(参考サイトにソースコードがあります)

noise_marble32_color.png

参考サイトの方は木や3次元テクスチャなどもう少し続きますが、長くなってきたので残りは後日にします。

付録:
参考サイトでは線形補間しか使っていないので、直線が見えてしまう場合がありますが、他の補間法を使えば消えるようです。
左が線形補間、右がLanczos2補間を使用した場合です。計算時間は増えますが、なんとなく改善されている気はします。

noise_clouds32.png noise_clouds32_l2.png
noise_marble32_color.png noise_marble32_color_l2.png

[1] http://lodev.org/cgtutor/randomnoise.html

web拍手 by FC2
posted by シンドラー at 20:35 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする