| On this page |
このスクリプトは、基本のAbelian Sandpileソルバの上級バージョンです。 4つの標準方位では、丘の斜面がブロック状に切り取られたかのような不自然で機械的な見た目の結果が生じる場合があります。
このスクリプトには、以下の追加と拡張が施されています:
-
4つの方向ではなく、あり得る8つの方向をテストします。
-
堆積処理によって侵食された砂を保持し、谷や窪みを埋めます。
-
堆積物の表面上のアーティファクトを回避するために、ソルバで基本的な風モデルを使用します。
-
標準方位の反転を修正しました。
セットアップ ¶
基本編を既に進まれているのであれば、そのプロジェクトを別名で保存して、そのセットアップを再利用することができます。 そうでない場合は、基本編のページの説明に従ってください。
方向を決めるWrangle ¶
このWrangleには、ソルバを調整するためのカスタムパラメータが含まれています。
以下のスクリプトは、砂の移動方向を決定し、その方向をdeltaレイヤに書き込みます。
この問題は、Height Fieldレイヤにはベクトル値を格納できないことです。
そのため、あり得る8つの方向を数値で表現する必要があります。
砂の輸送制御に必要なパラメータもDetailアトリビュートとして格納します。
以下のコードを⌃ Ctrl + Cでコピーします。 そのコードを 1つ目 のHeightField Wrangle SOPの VEXpression フィールドに⌃ Ctrl + Vでペーストします。
//============================================================================ // 侵食方向Wrangle // 勾配、風の影響、ユーザ定義の閾値に基づいて砂が滑る方向を決定します。 // これは、侵食/堆積サイクルにおける第1工程です。 //============================================================================ // --- UIからシミュレーションパラメータを取得します --- float grid_resolution = chf("grid_resolution"); //各グリッドセルのサイズ float slope_threshold = chf("slope_threshold"); //砂が滑るために必要な最小勾配 float scale_factor = chf("scale_factor"); // 侵食強度を制御する乗数 float sedimentation_scale = chf("sedimentation_scale"); // 低地における堆積物の蓄積量を制御します float wind_angle_deg = chf("wind_direction"); // 単位が度の風向(0° = +X, 90° = +Y) float wind_bias_strength = chf("wind_bias_strength"); // 砂の移動に対する風の影響の強さ // --- 滑りのための有効な勾配閾値を計算します --- float sliding_threshold = grid_resolution * slope_threshold; /* 風向を度からラジアンに変換して、風向を指した単位ベクトルを生成します。 このベクトルは砂の移動を風向きへ寄せるために使用されます。 */ float wind_angle_rad = radians(wind_angle_deg); vector base_wind_dir = set(cos(wind_angle_rad), sin(wind_angle_rad), 0); /* あり得る8つの隣接方位を定義します: - 4つの標準方位(左、右、上、下) - 4つの対角方位(左上、右上、左下、右下) これらの方位は、勾配を評価し、あり得る滑り方向を決定するために使用されます。 */ vector dirs[] = array( set(-1, 0, 0), set( 1, 0, 0), set( 0, -1, 0), set( 0, 1, 0), set(-1, -1, 0), set( 1, -1, 0), set(-1, 1, 0), set( 1, 1, 0) ); // --- 有効な滑り方向を格納する配列を初期化します --- int slide_indices[]; // --- 現在のセルの高さとグリッド位置をキャッシュ化します --- float h_self = @height; int x = @ix; int y = @iy; /* すべての隣接セルをループして、勾配を評価します。 隣接セルに向かった勾配が調整済みの閾値を超えると、 その方向は砂が滑る有効な候補方向と見なされます。 */ for (int i = 0; i < len(dirs); i++) { vector dir = dirs[i]; int nx = x + int(dir.x); int ny = y + int(dir.y); float h_neighbor = volumeindex(0, "height", set(nx, ny, 0)); float slope = h_self - h_neighbor; float distance = length(dir); // 1.0(標準方位)、約1.414(対角方位) float adjusted_threshold = sliding_threshold * distance; if (slope > adjusted_threshold) { append(slide_indices, i); // 有効な方向のインデックスを格納 } } /* 有効な滑り方向が存在した場合、そのどれかを選択します。 この選択は、内積を用いて風向に偏るように調整されます。 風向に沿った方向ほど高いウェイトを持ちます。 */ int direction_code = -1; if (len(slide_indices) > 0) { float weights[] = {}; for (int i = 0; i < len(slide_indices); i++) { vector dir = dirs[slide_indices[i]]; float bias = dot(normalize(dir), base_wind_dir); // 風向との偏り float weight = 1 + bias * wind_bias_strength; // 風バイアスのスケール append(weights, weight); } /* ウェイト付きリストからランダムにどれかの方向を選択します。 これによって、確率的な手法を取り入れつつ、風向に沿った移動が優先されます。 */ int pick = sample_discrete(weights, rand(@ix * 17 + @iy * 43 + @Time)); direction_code = slide_indices[pick]; } // --- 次のWrangleで利用できるように、選択した方向を浮動小数点アトリビュートとして格納します --- @delta = float(direction_code); /* 最初のフレームで、グローバルパラメータをDetailアトリビュートとして格納します。 これらのパラメータは、2つ目のWrangleでアクセスされ、ボクセル全体で一貫した値を保証します。 */ if (@Frame == 1) { setdetailattrib(0, "sliding_threshold", sliding_threshold, "set"); setdetailattrib(0, "scale_factor", scale_factor, "set"); setdetailattrib(0, "sedimentation_scale", sedimentation_scale, "set"); }
パラメータ ¶
上記の最初のスクリプトには、6つのカスタムパラメータの定義が含まれています。
これらのパラメータをWrangleのUIに追加するために、 Creates sparse parameter for each unique call of ch() ボタンをクリックします。
Note
下記のテーブルに記載されている値はあくまで目安であり、地形に応じて調整が必要となる可能性があります。
| パラメータ | 説明 | 値 |
|---|---|---|
| Grid Resolution | このパラメータは、侵食構造を解像度非依存にします。HeightField SOPの Grid Space パラメータの値を使用してください。 |
n/a
|
| Slope Threshold |
砂が滑るために必要な最小勾配。値を小さくすると緩やかな斜面も考慮され、より広い範囲で侵食が発生します。非常に小さい値(<0.3)では、結果が不自然に見える可能性があります。
|
0.7
|
| Scale Factor | 高低差が大きい場合、ノイズやその他のアーティファクトが発生する可能性があります。 Scale Factor パラメータはこの影響を軽減します。特定の結果を得る必要がある場合を除き、値を変更するべきではありません。 |
0.25
|
| Wind Direction | ソルバは、堆積構造上のノイズパターンを回避するために、基本的な風モデルを使用します。ここでは、風向を度単位で指定することができます。なお、風モデルの影響には限界があることに注意してください。 |
60
|
| Wind Bias Strength |
このパラメータは、ボクセルに対する風の最大影響度を制御します。 Wind Direction と高い Wind Bias Strength (>50)を組み合わせることで、段丘化現象の軽減に役立ちます。
|
1
|
| Sedimentation Scale |
0では堆積を完全に無効にすることができます。1は質量保存を意味し、侵食された砂の量が堆積量と等しくなります。1より大きい値は地形を速く埋めます。1より小さい値は堆積量を減少させます。
|
1
|
浸食Wrangle ¶
このスクリプトは、deltaレイヤの符号化された方向値を読み込み、砂が滑るボクセルを判定します。
また、地形のheightレイヤを更新して、最初のWrangleで設定したカスタムパラメータに基づいて堆積処理を適用します。
以下のコードを⌃ Ctrl + Cでコピーします。 そのコードを 2つ目 のHeightField Wrangle SOPの VEXpression フィールドに⌃ Ctrl + Vでペーストします。
// ============================================================================ // 侵食の適用&堆積のWrangle // 事前に計算された方向に基づいて砂の移動を適用し、 // 低地帯に堆積物を追加します。 // これは、侵食/堆積サイクルにおける第2工程です。 // ============================================================================ // --- 1つ目のWrangleで格納されたグローバルパラメータを取得します --- float sliding_threshold = detail(0, "sliding_threshold") * detail(0, "scale_factor"); float sedimentation_scale = detail(0, "sedimentation_scale"); /* 8つの隣接方位を定義します: - 4つの標準方位(左、右、上、下) - 4つの対角方位(左上、右上、左下、右下) これらの方位は、流入/流出する砂の流れを評価するために使用されます。 */ vector dirs[] = array( set(-1, 0, 0), set( 1, 0, 0), set( 0, -1, 0), set( 0, 1, 0), set(-1, -1, 0), set( 1, -1, 0), set(-1, 1, 0), set( 1, 1, 0) ); // --- 現在のボクセル位置をキャッシュ化します --- int x = @ix; int y = @iy; // --- 高さ変化の累積値を初期化します --- float height_increment = 0; // --- 1つ目のWrangleから自身の滑り方向を取得します --- int my_delta = int(@delta); vector my_dir = my_delta >= 0 ? dirs[my_delta] : {0, 0, 0}; /* すべての隣接セルをループして、いずれかの隣接セルがこのセルに向けて砂を滑らせているかチェックします。 隣接セルの滑り方向がこのセルの方に向いている場合、そのセルから砂を受け取ります。 */ for (int i = 0; i < len(dirs); i++) { int nx = x + int(dirs[i].x); int ny = y + int(dirs[i].y); float neighbor_delta = volumeindex(0, "delta", set(nx, ny, 0)); if (neighbor_delta >= 0) { vector neighbor_dir = dirs[int(neighbor_delta)]; /* 隣セルの方向が現在の方向と逆の場合、 砂がこのセルに流入していることを意味します。 */ if (length(neighbor_dir + dirs[i]) < 0.01) { height_increment += sliding_threshold; } } } /* このセルが砂をアクティブに滑らせている場合(つまり、有効な方向を持っている場合)、 その高さから砂を減算します。 */ if (my_delta != -1) { height_increment -= sliding_threshold; } /* このセルが滑っていない場合(つまり、安定状態の場合)、このセルが低地にあるかどうかをチェックします。 隣接セルの平均高さが高いほど、それに比例して砂を追加することで堆積をシミュレーションします。 */ if (@delta == -1) { float h_self = @height; float h_sum = 0; int count = 0; for (int i = 0; i < len(dirs); i++) { int nx = x + int(dirs[i].x); int ny = y + int(dirs[i].y); float h_neighbor = volumeindex(0, "height", set(nx, ny, 0)); h_sum += h_neighbor; count++; } float h_avg = h_sum / count; float sediment_amount = clamp(h_avg - h_self, 0, sliding_threshold); // ユーザ定義の係数でスケールさせた堆積物を追加します。 height_increment += sediment_amount * sedimentation_scale; } // --- 累積した高さの変化を適用します --- @height += height_increment;
結果 ¶
パラメータ調整後、プレイバーに移動して、 Play ボタンをクリックしてシミュレーションを開始してください。
このスクリプトの実行速度は比較的速く、ほとんどの場合50~70フレーム以上のシミュレーションは必要ありません。
以下の動画では、荒々しい岩山の地形が、堆積物の丘が点在する侵食された地形へと変化する様子をご覧いただけます。 非常に滑らかな見た目になるのを避けたいのであれば、ネットワークの最後にHeightField Distort by Noise SOPを追加して、適度な設定で砂の外観を保つことができます。
地形の右側の部分をよく見ると、砂が流れ落ちる様子が確認できます。
この効果を強調するために、ソルバの Sedimentation Scale パラメータを1.6に設定しています。