Tiling Noise – プロシージャルGPUパターン
Tiling Noise – Procedural GPU Patterns (catlikecoding.com)
要約
この記事は、Godot Engineのシェーダーを使用してGPU上でプロシージャルパターンを作成するシリーズの第9弾です。前回のバリューノイズに、シームレスなタイリング機能を追加する方法を解説しています。タイリングの仕組み、UV座標の調整、そしてアニメーションとの連携について、具体的なコード例と共に説明しています。
全文翻訳
Tiling Noise
[画像: トーラスの周りにラップされたバリューノイズ]
これは、シェーダーでGPU上にプロシージャルパターンを作成するGodotチュートリアルシリーズの9番目であり、Godot Engineバージョン4.7を使用します。このチュートリアルはValue Noise Derivativesに続き、それにタイリングを追加します。
Torus上のValue Noise
これまでは平面に適用されるバリューノイズのみを扱ってきました。トーラスにどのように適用されるかも見てみましょう。sine_waves_torus.tscnのようなシーンを作成し、value_noise_plane.tscnで使用されているマテリアルに合わせます。トーラスが厚くなりすぎるのを避けるため、変位(displacement)はデフォルトの0.2のままにします。
[画像: seamsのあるトーラス]
シームに沿って不連続性のあるトーラス。
トーラスには、UV座標の0と1が接する2つの明らかなシームがあります。これについては後で扱います。まず、value_noise.gdshaderのバンプネス(bumpiness)と周波数(frequency)のタイプをvec2にアップグレードして、サイン波の場合と同様にトーラス用にこれらをより良く調整できるようにします。
uniform vec2 bumpiness = vec2(1.0);
uniform float displacement : hint_range(-1.0, 1.0) = 0.2;
uniform bool vertex_displacement = true;
#define PATTERN_FREQUENCY_TYPE vec2
#define USE_STANDARD_FRACTAL_PATTERN_SETTINGS
新しい周波数タイプで動作するようにsample_value_noise()を調整します。
PatternSample sample_value_noise(
int octave,
vec2 frequency,
vec2 uv,
float time
) {
…
s.dx *= frequency.x;
s.dy *= frequency.y;
return s;
}
次に、平面シーンを再構成し、バンプネスと周波数の両方のコンポーネントに古い値を複製します。トーラスでは、バンプネスに(0.212, 0.637)を再度使用し、ベース周波数を(6,4)に設定します。
[画像: 2D周波数を持つトーラス]
ベース周波数(6,4)を持つトーラス。
UV範囲のテスト
パターンがトーラスの周りでシームレスにラップするようにするには、座標が0未満および1を超える場合にもパターンが繰り返されるようにタイリングする必要があります。これを試すために、一時的に平面に戻します。現時点では、UV座標は-0.5から0.5の範囲になっています。平面のベース周波数を4に設定し、オクターブを1つだけにして結果を見てみましょう。
[画像: 元のUV範囲]
元のUV範囲、-0.5から0.5。
タイリングをテストするために、UVを-1から2の範囲に変更します。これにより、vertex()とfragment()の両方で使用するsample_noise()関数が導入され、UV範囲の調整は一度だけで済みます。
PatternSample sample_noise(vec2 uv) {
return sample_fractal_pattern(
standard_fractal_pattern_settings(),
uv * 3.0 - 1.0,
TIME
);
}
void vertex() {
if (vertex_displacement) {
PatternSample pattern_sample = sample_noise(UV);
VERTEX += NORMAL * (pattern_sample.v * displacement);
}
}
void fragment() {
PatternSample pattern_sample = sample_noise(UV);
ALBEDO = colorize(pattern_sample.v);
…
}
[画像: 拡張されたUV範囲]
拡張されたUV範囲、-1から2。
通常のUV範囲は0から1なので、目標はパターンをそのUV範囲でタイリングすることです。これは現在、平面の中央にあるタイルです。このタイルは、現在異なるパターンを表示している8つのタイルに囲まれています。完了すると、3x3領域の9つのタイルすべてが同じパターンを表示し、それらの間にシームがなくなります。
タイリングラティス座標
ハッシュノイズパターンを使用している場合、UV座標の小数部分を取るだけでパターンは完全にタイリングされました。しかし、バリューノイズは隣接するラティスポイント間でブレンドするため、シームをなくすためにタイル境界を越えてブレンドする必要もあります。これは、タイル境界のラティスブロックが、タイルの反対側にラティスポイントをラップする必要があることを意味します。例えば、周波数4の場合、1次元のラティス座標シーケンスは、1つのタイルで0、1、2、3、4となります。ラティスブロックに分割すると、ラティスポイントペアのシーケンスは(0,1)、(1,2)、(2,3)、(3,4)となります。タイリングを行う場合、末尾は先頭に戻るようにラップする必要があるため、座標シーケンスは0、1、2、3、0となり、ブロックは(0,1)、(1,2)、(2,3)、(3,0)となります。
ラティス座標を変更する必要があるため、sample_value_noise()が単一次元のラティスブロックの最初と2番目のラティス座標を取得するために使用するlattice01()関数を導入します。これは元のラティス座標を取り、それと次の座標を返します。これらはローカルでl0とl1と名付けます。これらはvec2にパックして返します。タイリングを実行するには、周波数を知る必要もあります。
vec2 lattice01(float lattice_coordinate, float frequency) {
float l0 = lattice_coordinate;
float l1 = l0 + 1.0;
return vec2(l0, l1);
}
この関数を使用するようにsample_value_noise()を適応させます。
vec2 fractional_coordinates = sample_coordinates - lattice_coordinates;
vec2 uLattice = lattice01(lattice_coordinates.x, frequency.x);
vec2 vLattice = lattice01(lattice_coordinates.y, frequency.y);
Hasher h = hasher(hash_seed + uint(octave));
Hasher h0 = hasher(h, uLattice.x);
Hasher h1 = hasher(h, uLattice.y);
Hasher h00 = hasher(h0, vLattice.x);
Hasher h10 = hasher(h1, vLattice.x);
Hasher h01 = hasher(h0, vLattice.y);
Hasher h11 = hasher(h1, vLattice.y);
これで、観察している3x3ブロック領域でパターンを繰り返すようにlattice01()の座標を調整し始められます。まず負の座標を考えます。これらを中央のタイルに一致させるには、正の値になるように変更する必要があります。これは、l0に周波数を追加することで行います。
float l0 = lattice_coordinate;
if (l0 < 0.0) {
l0 += frequency;
}
float l1 = l0 + 1.0;
[画像: 負の座標のシフト]
負の座標のシフト。
これにより、負の側で部分的なタイリングとシームが生成されます。シームをなくすには、l1がタイルの右側にある場合、周波数と等しいかそれ以上になった場合に、左側にシフトする必要があります。
float l1 = l0 + 1.0;
if (l1 >= frequency) {
l1 -= frequency;
}
[画像: 負の座標のタイリング]
負の座標のタイリング。
これにより、負の側でシームレスにタイリングされますが、正の側のタイルはめちゃくちゃになります。それらのタイルを修正するには、l0が負で始まらなかった場合に、l1に行ったのと同じ調整をl0にも行う必要があります。
float l0 = lattice_coordinate;
if (l0 < 0.0) {
l0 += frequency;
}
else if (l0 >= frequency) {
l0 -= frequency;
}
[画像: 座標のタイリング]
座標のタイリング。
これで、3x3領域全体で完璧なシームレスタイリングが得られます。しかし、より大きな領域では失敗し、表示領域を5x5に拡張すると可視化されます。
[画像: タイリング範囲の制限]
タイリング範囲の制限。UVは-2から3。
この制限は許容範囲内です。なぜなら、目標はUV座標0と1をシームレスに一致させることだからです。現在の安価でシンプルなアプローチはこれをサポートしています。-1から2の範囲でも機能するため、0-1範囲の外側に最大1単位まで移動できます。これは、後で追加するスライドアニメーションなどのための余裕を与えます。
ただし、追加の制限があります。ラティスポイントがタイルエッジに整列するため、周波数は整数である必要があります。そうでないと、パターンはタイリングされなくなります。
[画像: タイリング周波数の制限]
タイリング周波数の制限。周波数4.5。
最後に、タイリングはアニメーションでも機能しますが、すべてのオクターブの周波数が偶数の場合に限ります。これは、ずらしたアニメーションを使用するためです。周波数が奇数の場合、タイルの反対側にあるラティスブロックは、一致するラティスポイントに対して異なる時間オフセットを使用します。
オプションタイリング
タイリングは通常必要ないため、トグルオプションを追加し、デフォルトで無効にします。次元ごとにオプションのタイリングをサポートすることも可能ですが、ここではシンプルにすべてまたはなしの取引にします。タイリングとその要件を説明するコードドキュメントも追加します。/* */の間に複数行コメントを使用します。開始ステートメントに2番目の*を追加すると、Godotエディタのインスペクターにもシェーダーパラメータのツールチップとして表示されます。
uniform uint hash_seed = 0u;
/**
シームレスにUV 0から1のパターンをタイリングします。
UVが-1から2の間でタイリングが機能します。
すべてのオクターブに整数周波数を必要とします。
アニメーションには偶数の周波数を必要とします。
*/
uniform bool tiling = false;
タイリングが無効な場合、lattice01()の元の方法に戻ります。
vec2 lattice01(float lattice_coordinate, float frequency) {
if (!tiling) {
return vec2(lattice_coordinate, lattice_coordinate + 1.0);
}
else {
float l0 = lattice_coordinate;
if (l0 < 0.0) {
l0 += frequency;
}
else if (l0 >= frequency) {
l0 -= frequency;
}
float l1 = l0 + 1.0;
if (l1 >= frequency) {
l1 -= frequency;
}
return vec2(l0, l1);
}
}
テストが完了したので、UV範囲を1単位に戻します。以前の0.5オフセットを減算しなくなります。
PatternSample sample_noise(vec2 uv) {
return sample_fractal_pattern(
standard_fractal_pattern_settings(),
uv,
TIME
);
}
[画像: タイリングあり][画像: タイリングなし]
周波数8、3オクターブの平面。タイリングありとなし。
これで、トーラスの周りにラップされたシームレスなバリューノイズパターンを最終的に取得できます。トーラスリングを128に、リングセグメントを64に増やして、より滑らかにしました。
[画像: タイリングあり][画像: タイリングなし]
周波数(6,4)、3オクターブのトーラス。タイリングありとなし。
アニメーション
前述したように、タイリングはすべてのオクターブの周波数が偶数の限り、アニメーションでも機能します。
[動画: モーフィングするトーラス]
モーフィングするトーラス。
最後に、スライドアニメーションのサポートも追加して、トーラスのアニメーションをより面白くすることができます。スライド速度用の別の設定オプションを追加します。
uniform bool animation = true;
uniform vec2 slide_velocity = vec2(0.0, 0.0);
アニメーションが有効な場合、TIMEでスケーリングされたスライドオフセットを使用してsample_noise()でUV座標をオフセットします。
PatternSample sample_noise(vec2 uv) {
if (animation) {
vec2 slide_offset = slide_velocity * TIME;
uv += slide_offset;
}
return sample_fractal_pattern(
standard_fractal_pattern_settings(),
uv,
TIME
);
}
これはタイリングをすぐに壊します。なぜなら、サポートされているUV領域の外に出るからです。そのため、オフセットの小数部分のみをUV座標に追加することで、オフセットを最大1単位に制限する必要があります。これにより、常に有効な領域内に留まることが保証されます。
vec2 slide_offset = slide_velocity * TIME;
if (tiling) {
slide_offset = fract(slide_offset);
}
uv += slide_offset;
[動画: モーフィングしてスライドするトーラス]
モーフィングしてスライドするトーラス。スライド速度(0.1,−0.2)。
次のチュートリアルはCurled Noiseです。