Curl Noise書いてみた

  • 29
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Curl Noise書いてみた

WebGL Advent Calender 22日目遅刻しました。
やまだです。

WebGL総本山って皆さんご存知ですよね?
WebGLを使ったクールなページを紹介するサイトで私も楽しませていただいてます。

そのなかでもちょっと気になったのがこの記事でも触れられているCurl Noiseというキーワードです。
どうやらこれを使うとクールな流体がかけると聞いて自分でも実装してみました。

image

Demoはこちら
Internet Explorerでももちろん動きますよ。

パーティクルの色やノイズの周波数をUIで変更できるようにしています。
パラメーターを変更するだけでまったく違う表情を見せるのは見ていて面白いですね。

Curl Noiseとは

得られる結果のとおり流体っぽい振る舞いをするベクトルが計算できるノイズ関数です。

ベクトル解析でいう回転をパーリンノイズやシンプレクスノイズに対して計算し、速度ベクトルを求めます。
ベクトル解析の回転については物理のかぎしっぽさんの解説を参考にしました。

これによると回転はベクトル場$A$に対して次のようにかけるそうです。

\mathrm{rot}A
 = \nabla \times A
 = \left( \begin{array}{ccc}\frac{\partial}{\partial x}\\ \frac{\partial}{\partial y}\\ \frac{\partial}{\partial z}\end{array}\right) \times \left( \begin{array}{ccc}A_{x}\\A_{y}\\A_{z}\end{array}\right)
 = \left( \begin{array}{ccc}\frac{\partial A_z}{\partial y}-\frac{\partial A_y}{\partial z}\\ \frac{\partial A_x}{\partial z}-\frac{\partial A_z}{\partial x}\\ \frac{\partial A_y}{\partial x}-\frac{\partial A_x}{\partial y}\end{array}\right)

見たことのある記号と見たことあるけどよくわからない記号がありますね(個人の感想です)。

ベクトル場

まずベクトル場というのは空間にベクトルがばーってちらばってる空間のことです(雑い)
GLSLで書くとvec3を入力としてvec3を出力する関数をベクトル場とできます。
もしも三次元テクスチャが使えるのであればベクトル場は次のように書けるでしょう。

vec3 getVector(vec3 p) {
  return texture3D(tex, p).xyz;
}

Curl Noiseではベクトル場としてパーリンノイズやシンプレクスノイズなどが使われます。
image
今回はwebgl-noiseからシンプレクスノイズをおかりしました。
2Dのノイズだとこんな感じになります。

ですが困ったことに、このsnoise関数ですがvec3のようなベクトルを読み込んでfloatを出力する関数です。
欲しいのはベクトル場なのでここで少し細工をします。

const SNOISE_VEC3 = `
vec3 snoiseVec3( vec3 x ){
  float s   = snoise(vec3( x ));
  float s1  = snoise(
                vec3(
                  x.y + ${Math.random().toFixed(10)},
                  x.z + ${Math.random().toFixed(10)},
                  x.x + ${Math.random().toFixed(10)}
                )
              );
  float s2  = snoise(
                vec3(
                  x.z + ${Math.random().toFixed(10)},
                  x.x + ${Math.random().toFixed(10)},
                  x.y + ${Math.random().toFixed(10)}
                )
              );
  return vec3( s , s1 , s2 );
}
`;

上記のように軸を入れ替えてからJavaScriptで乱数をのせて無理やりベクトルにしています。

途中Math.random()とJavaScriptの数学関数が登場していますが、これはECMAScript2015のヒアドキュメントと呼ばれる記法です。
便利なのでWebGLかくときにおすすめなのですよー。

∇の正体は

同じことの繰り返しになりますが回転は$\nabla$とベクトル場とのクロス積で求まります。
$\nabla$の正体は$\left( \begin{array}{ccc}\frac{\partial}{\partial x} \ \frac{\partial}{\partial y}\ \frac{\partial}{\partial z}\end{array}\right)$のように偏微分のベクトルです。
クロス積は単位法線の計算でよくつかうのでわかるのですが偏微分についてはよくわかんなかったので調べてみました(大学で勉強した気がするのに)

雑に説明すると$\frac{\partial A_y}{\partial x}$は$A$をちょっぴり$x$方向に動かした時に$A_y$はどれだけ増加するかということのようです。

あとは言葉通りにちょっぴり移動させてその値で速度ベクトルを作るだけ、簡単ですね。

vec3 curlNoise( vec3 p ){
  const float e = 0.0009765625;
  const float e2 = 2.0 * e;

  vec3 dx = vec3( e   , 0.0 , 0.0 );
  vec3 dy = vec3( 0.0 , e   , 0.0 );
  vec3 dz = vec3( 0.0 , 0.0 , e   );

  vec3 p_x0 = snoiseVec3( p - dx );
  vec3 p_x1 = snoiseVec3( p + dx );
  vec3 p_y0 = snoiseVec3( p - dy );
  vec3 p_y1 = snoiseVec3( p + dy );
  vec3 p_z0 = snoiseVec3( p - dz );
  vec3 p_z1 = snoiseVec3( p + dz );

  float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y;
  float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z;
  float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x;

  return normalize( vec3( x , y , z ) / e2 );
}

さぁWebGLでレンダリングだ!

さて、ばばーっとパーティクルを散らばらせないと面白く無いですよね?
なのでVTFや浮動小数点テクスチャを使って高速にパーティクルをレンダリングしています。
頂点をattributeではなく、フレームバッファを使ってテクスチャに書き込むことにより、パーティクルを毎フレーム動かしています。
これも話せば長くなるので説明をwgldGPGPUの説明に丸投げしちゃいます。

最後に

おぉなんか俺数学っぽいことやってる!とか悦に浸りながら書いておりました。
学生時代真面目に数学とかやってなかったけどやっぱり勉強しておけばよかったなぁと後悔しております。
Curl Noiseは見た目がクールだけど実装はそこまで複雑ではないので一回挑戦してみてはいかがでしょうか?

参考

物理のかぎしっぽ
WebGL総本山
webgl-noise
glsl-curl-noise