シェーダでノイズ2(パーリンノイズ)

シェーダでノイズ2(パーリンノイズ)

 パーリンノイズは単純なランダムノイズと異なり、滑らかなノイズが得られます。そのため、様々なテクスチャの生成や炎、雲及び地形などの自然物を表現する際によく使用されます。

一次元パーリンノイズ

 始めに、xの少数部分を取り出します。これにより、整数の間で\(0\)から\(1\)未満の値を繰り返すようになります。したがって、下図で色分けされているように整数の間を一つの区間として区切ることができます。ここで、区切られた区間を格子、区間の境目を格子線とします。また、丸で囲まれている座標を格子点とします。

ある格子内において、左側の格子点、つまり、\(floor(x)\)におけるランダムな値\(a_{L}\)と右側の格子点 \(floor(x+1)\)におけるランダムな値\(a_{R}\) を求めます。次に、それぞれの格子点を原点とし、ランダムな値を傾きとする直線を以下の式より求めます。

$$ \begin{align} &w_{L}(x)=a_{L}\cdot frac(x)\\ &w_{R}(x)=a_{R}\cdot (frac(x)-1) \end{align} $$

上式により求められる直線は下図のようになります。赤色の直線が\(w_{L}(x)\)、青色の直線が\(w_{R}(x)\)です。ある格子線の左側の青い直線と右側の赤い直線が一直線となっているのは、傾きとして求められるランダムな値が同じ格子点であれば、必ず同じ値となるためです。

この直線により求められる二つの値をエルミート補間することでノイズが求まります。使用するエルミート補間の式は以下の通りです。

$$ u=3f^2-2f^3 $$

また、エルミート補間を図にすると以下のようになります。

よって、ノイズを求める式は以下の式となります。

$$ \begin{align} &f=frac(x),u=f^{2}(3-2f)\\ &n=lerp(w_{L},w_{R},u) \end{align} $$

この式より、以下の図に示すノイズを求めることができます。

上図より、滑らかでかつランダムな値が得られることがわかります。また、パーリンノイズは格子線上において必ず\(0\)となることもわかります。

Script

 Unityで一次元パーリンノイズを求めるスクリプトを作成しました。スクリプトは以下の通りです。このスクリプトは適当なゲームオブジェクトにアタッチすれば動作します。このスクリプトでは、上記のように直線をエルミート補間する方法だけではなく、 こちらに掲載されているウェーブレット関数を求め、それらを線形補間する方法でもノイズを計算しています。

実行結果

 トグルを操作することで表示するグラフを変更できます。hermiteが直線をエルミート補間する方法で求められるノイズを、\(L(x)\)はノイズを求めるときに使用した直線を表示します。また、waveletはウェーブレット関数を線形補間することで求められるノイズを、\(W(x)\)はウェーブレット関数を表示します。

二次元パーリンノイズ

 二次元パーリンノイズは一次元の時と同様にして求めることができます。初めに、\(x\)及び\(y\)の少数部分を取り出すことで、下図に示すように格子状に区切ります。

一次元の場合は直線を補間しノイズを求めましたが、二次元の場合は平面を補間することでノイズを求めます。平面の式は以下の通りです。

$$ \begin{align} w(x,y)&=a_{x}\cdot x+a_{y}\cdot y\\ &=(a_{x},a_{y})\cdot (x,y) \end{align} $$

\(a_{x}\)及び\(a_{y}\)はそれぞれランダムな値を示しています。平面の方程式は上式のように、ランダムな値のベクトルと格子点から格子内にある任意の点に向かうベクトルの内積で表すことができます。よって、ランダムな値で構成されるグラディエントと距離ベクトルの内積を格子点ごとに求め、それらを補間することでノイズが求まることがわかります。この式を用いて、ある点\(P\)における値を求めます。下図は点\(P\)のある格子を示しています。

先ほどの式より格子点ごとに\(w\)を求めます。 それぞれの格子点から点\(P\)へ向かう距離ベクトルは以下の式で表されます。

$$ \begin{align} &\vec{d_{00}}=(frac(x),frac(y))\\ &\vec{d_{10}}=(frac(x)-1,frac(y))\\ &\vec{d_{01}}=(frac(x),frac(y)-1)\\ &\vec{d_{11}}=(frac(x)-1,frac(y)-1)\\ \end{align} $$

これより、格子点ごとの\(w\) は以下のようになります。

$$ \begin{align} &w_{00}(x,y)=(a_{00x},a_{00y})\cdot (frac(x),frac(y))\\ &w_{10}(x,y)=(a_{10x},a_{10y})\cdot (frac(x)-1,frac(y))\\ &w_{01}(x,y)=(a_{01x},a_{01y})\cdot (frac(x),frac(y)-1)\\ &w_{11}(x,y)=(a_{11x},a_{11y})\cdot (frac(x)-1,frac(y)-1)\\ \end{align} $$

この式より求められる値をエルミート補間することでノイズが求まります。よって、ノイズは以下の式より求めることができます。

$$ \begin{align} &f_{x}=frac(x),f_{y}=frac(y)\\ &u_{x}=3f_{x}^{2}-2f_{x}^{3},u_{y}=3f_{y}^{2}-2f_{y}^3\\ &n=lerp\{lerp(w_{00},w_{10},u_{x}),lerp(w_{01},w_{11},u_{x}),u_{y}\} \end{align} $$

shader

 二次元パーリンノイズのシェーダを以下に示します。uv座標に定数を掛けることにより格子数を決定しています。

実行結果

 上記シェーダの実行結果は以下の通りとなります。

参考サイト

○×(まるぺけ)つくろーどっとコム:その3 パーリンノイズとフラクタル ~ フラクタブルな地形を作る基盤 ~

The Book of Shaders:ノイズ

POSTD:パーリンノイズを理解する