Noise
説明
ノイズテクスチャをオフスクリーンで生成して、レイマーチングの距離関数にマッピングさせてみました。ここで紹介しているのはベーシックなノイズ生成手法のみですが、更に調べていくと頭のおかしい手法がまだまだたくさんあって面白い、というか発狂しそうです。
情報源としてはこちらやこちらのサイトが有名です。いつも勉強させていただいてます。
補助関数
ノイズの生成に使用する関数をいくつか準備しておきます。
// vec2の入力から0.0~1.0の乱数を出力する
float random(vec2 v)
{
// この実装が伝統的によく使われる
// ただし、乱数精度はあまり高くない
return fract(sin(dot(v, vec2(12.9898, 78.233))) * 43758.5453);
// 改良版としてよくみかけるのが、Dave_Hoskins氏のハッシュ関数(MITライセンス)
// https://www.shadertoy.com/view/4djSRW
// sin関数を使用しないため、ハードによる影響を受けずに高い精度を保つことができるらしい
// 注意として、"This is for integer scaled floats only!"とコメントにあるように、渡す値を分解能が整数になる程度にスケールさせてやる必要がある。
// つまり、画面幅に対して0.0~1.0を渡すような場合は、幅解像度でスケーリングさせてやればOK。だと思う。
}
// 値を2つ出力するバージョン
vec2 random2(vec2 v)
{
return vec2(random(v), random(v + vec2(10.0f, 10.0f)));
}
// 0.0~1.0の周期的なsin波アニメーションを返す
// ノイズをうねうねさせたかったので、これを挟んでいる
float animation(float f)
{
float speed = 2.0;
return sin(f * 6.283 + u_time * speed) * 0.5 + 0.5;
}
// 値を2つ受け取り2つ出力するバージョン
vec2 animation2(vec2 v)
{
return vec2(animation(v.x), animation(v.y));
}
// 0.0~1.0のイーズインアウトな補間式
float interpolation(float f)
{
// smoothstepの内部でも使われている式
// return f * f * (3.0 - 2.0 * f);
// こちらの方が最初の入り方と最後の抜け方がやさしい
// 結果、ノイズにアーティファクトがのりづらい
return f * f * f * (f * (6.0 * f - 15.0) + 10.0);
}
ホワイトノイズ(White Noise)
全ての点においてランダムな値を返します。
float noise_(vec2 uv)
{
float o = animation(random(uv));
return o;
}
ブロックノイズ(Block Noise)
一定間隔に区切られた矩形の範囲毎に同じランダム値を返します。
float noise_(vec2 uv)
{
uv *= 8.0;
vec2 i_uv = floor(uv);
float o = animation(random(i_uv));
return o;
}
バリューノイズ(Value Noise)
ブロックノイズの4点間を補間して滑らかにしたもの。
float noise_(vec2 uv)
{
uv *= 8.0;
vec2 i_uv = floor(uv);
vec2 f_uv = fract(uv);
float f1 = animation(random(i_uv + vec2(0.0, 0.0)));
float f2 = animation(random(i_uv + vec2(1.0, 0.0)));
float f3 = animation(random(i_uv + vec2(0.0, 1.0)));
float f4 = animation(random(i_uv + vec2(1.0, 1.0)));
float o = mix(
mix(f1, f2, interpolation(f_uv.x)),
mix(f3, f4, interpolation(f_uv.x)),
interpolation(f_uv.y));
return o;
}
パーリンノイズ(Perlin Noise)
クラシックなパーリンノイズの実装です。バリューノイズでは4点の単一値を補間しましたが、パーリンノイズでは4点に勾配ベクトルを設定し、計算する点へのベクトルとの内積をとることで影響値を算出します。
バリューノイズに比べ、ブロック状のアーティファクトが目立たずより自然な見た目になります。
float noise_(vec2 uv)
{
uv *= 8.0;
vec2 i_uv = floor(uv);
vec2 f_uv = fract(uv);
vec2 v1 = animation2(random2(i_uv + vec2(0.0, 0.0))) * 2.0 - 1.0;
vec2 v2 = animation2(random2(i_uv + vec2(1.0, 0.0))) * 2.0 - 1.0;
vec2 v3 = animation2(random2(i_uv + vec2(0.0, 1.0))) * 2.0 - 1.0;
vec2 v4 = animation2(random2(i_uv + vec2(1.0, 1.0))) * 2.0 - 1.0;
float o = mix(
mix(dot(v1, f_uv - vec2(0.0, 0.0)), dot(v2, f_uv - vec2(1.0, 0.0)), interpolation(f_uv.x)),
mix(dot(v3, f_uv - vec2(0.0, 1.0)), dot(v4, f_uv - vec2(1.0, 1.0)), interpolation(f_uv.x)),
interpolation(f_uv.y)) * 0.5 + 0.5;
return o;
}
ここでは実装していませんが、他にも改良版の手法が考案されています。
改良版パーリンノイズ …… 完全にランダムな勾配ベクトルではなく、前もって用意しておいた複数個の勾配ベクトルをハッシュにより参照させることで計算コストを抑える。
シンプレックスノイズ …… その次元での最小プリミティブ(2次元ノイズの場合、三角形)で補間を行う。次元数に対してオーダーが小さい。コードが難解で読めない。
セルラーノイズ(Cellular Noise)
ランダムに配置した複数の点と計算する点の最小距離を求めます。見た目の割に実装は非常にシンプルです。
float noise_(vec2 uv)
{
float o = 1.0;
for(int i = 0; i < 64; i++)
{
vec2 v = random2(vec2(float(i), 0.0));
v += (animation2(random2(v)) - 0.5) * 0.1;
o = min(length(v - uv), o);
}
o = min(pow(o * 5.0, 2.0), 1.0); // 値の勾配を調整
// o = 1.0 - o; // 反転
return o;
}
タイル分割した範囲にポイントの取りうる座標を限定して、ポイント間の距離計算コストを一定とする改良版もあります。
トリッキーなテクニックなので少々分かりづらい。
float noise_(vec2 uv)
{
float o = 1.0;
uv *= 8.0;
vec2 i_uv = floor(uv);
vec2 f_uv = fract(uv);
for(int y = -1; y <= 1; y++)
{
for(int x = -1; x <= 1; x++)
{
vec2 neightbor = vec2(float(x), float(y));
vec2 r = random2(i_uv + neightbor);
r += (animation2(r) - 0.5) * 0.5;
r = clamp(r, 0.0, 1.0);
o = min(length(f_uv - (neightbor + r)), o);
}
}
o = min(pow(o * 0.75, 2.0), 1.0); // 値の勾配を調整
// o = 1.0 - o; // 反転
return o;
}
fBm
周期と振幅の異なるノイズを段階的に重ね合わすことでいい感じのノイズに。地形生成や雲の生成なんかに。
float fbm = 0.0;
float fbm_max = 0.0;
for(int i=0; i>=0; i++)
{
if(u_fbm_iteration == i) break;
float f = pow(0.5, float(i));
fbm += f * noise_(v_uv0 * pow(2.0, float(i)));
fbm_max += f;
}
color.xyz *= fbm / fbm_max;
トップページへ戻る
Tweet Follow @yunta_robo