Houdini 21.0 Height Fieldと地形

Height Field向けのVEXスクリプト

VEXによって、地形、マスク、レイヤをカスタマイズします。

On this page

このスクリプト集には、より複雑なスクリプトの作成を始めるのに役立ついくつかのVEXスニペットが含まれています。 ほとんどのスクリプトでは様々なタイプのマスクを作成することができますが、heightレイヤまでも制御することができます。 これを始める前に、以下のいくつかの事をご検討ください。

Tip

散布ポイントの除去のページには、興味深い様々なVEXスクリプトやテクニックも掲載されています。

ボクセル値を取得する

Height Fieldのボクセル値を読み出す処理は常に同じです。 ただし、関数が返すデータのタイプはレイヤの内容によって異なります。 例えば、heightレイヤの場合、高さ値が得られます。 maskレイヤの場合、各ボクセルにおけるマスクの強度値が得られます。

とはいえ、heightレイヤおよびmaskレイヤは組み込みレイヤであり、アトリビュート(@heightおよび@mask)として呼び出すだけで自動的に評価されるようになっているので、特殊なケースに該当します。 しかし、これらのレイヤの値を手動で読み出すこともできます。 以下の関数は、入力0からmaskレイヤを読み取り、すべてのボクセルを走査します。

float mask_value = volumeindex(0, "mask", set(i@ix, i@iy, i@iz));

以下は、グリッドのインデックスの代わりに@Pを使用したより汎用的な形式です。

signature layer_value = volumeindex(int input, "layer_name", @P);

円マスク

円は、クレーターを作成したり、パッチを切り抜いたりするなど、マスキングにおいて最も頻繁に必要とされる形状の一つです。 幸いなことに、このようなマスクを作成するには、わずか数行のVEXコードで済みます。

vector position = chv("position");
vector offset = set(position.x, 0, position.z);

if (length(v@P - offset) < chf("radius")) {
    @mask = 1;
}

最初の行は、カスタムのpositionベクトルから値を読み込みます。 このベクトルは円の中心を定義します。 Height FieldにはY値がないので、offsetベクトルではpositionのXとZのコンポーネントのみを使用します。 指定したカスタムのradiusの円内にボクセルがあれば、そのボクセルがマスクに追加されます。

他のマスクと同様に、適切なノードを用いて結果をぼかしたり、反転させたり、収縮/膨張させることができます。

リングマスク

円ではなく、カスタムの内側半径と外側半径を定義することでリングを作成することもできます。

vector position = chv("position");
vector offset = set(position.x, 0, position.z);
float midpoint = length(v@P - offset);

if (midpoint > chf("inner_radius") && midpoint < chf("outer_radius")) {
    @mask = 1;
}

このコードは、円マスクのスクリプトと非常に似ています。 主な違いは、ここでは1つの Radius だけではなく、2つの値と比較する点です。 また、if条件文内でlength(v@P - offset)を2回記述するのではなく、その記述を独立したmidpoint変数にまとめています。

楕円マスク

円マスクの代わりに楕円マスクを作成することもできます。 楕円のX軸とY軸の寸法を定義するために2つのパラメータが必要です。 オフセットは、地形のデフォルトの原点の[0,0]からの距離を定義し、形状を回転させることもできます。

float a = chf("x_dimension");
float b = chf("z_dimension");
vector offset = chv("offset");
float rotation = radians(chf("rotation")); // Degree -> radians

// オフセットを適用した現在のボクセルの位置。
vector pos = set(@P.x - offset.x, @P.z - offset.z);

// 楕円を回転させます。
vector rotated_pos;
rotated_pos.x = cos(rotation) * pos.x - sin(rotation) * pos.y;
rotated_pos.y = sin(rotation) * pos.x + cos(rotation) * pos.y;

// トランスフォーム座標を用いた楕円方程式のテスト。
float ellipse_equation = pow(rotated_pos.x / a, 2) + pow(rotated_pos.y / b, 2);

if (ellipse_equation <= 1.0) {
    @mask = 1.0;
}    

最初の4行は、楕円をカスタマイズするためのパラメータを定義しています。

その後、このスクリプトは、オフセットを適用した位置ベクトルを計算します。 この位置ベクトルが楕円の中心を決定します。 rotated_posベクトルには、回転後の位置の値を格納します。 ellipse_equationは、楕円の内側領域を定義し、この値が1.0以下であれば、ボクセルは楕円内にありマスクの一部となります。

高さマスク

この効果は、HeightField Mask by Feature SOPを使用して実現することができますが、 ここでは上下限値内のボクセルをマスクする簡潔で便利なスクリプトを紹介します。 例えば、このスニペットを他のスクリプトに追加したり、カスタムパラメータで独自のマスキングツールを作成することができます。

if (@height >= chf("lower_limit") && @height <= chf("upper_limit") ) {
    @mask = 1;
}

地形の高さがカスタムチャンネルのlower_limitupper_limitの値の範囲内にある場合、そのボクセルはマスクに追加されます。

勾配マスク

Houdiniの一部のHeight Field系ノードには勾配マスクが用意されていないので、勾配マスクはツールの選択肢として有用な追加となるでしょう。

vector gradient = volumegradient(0, "height", v@P);

if (fit01(length(gradient), 0, 1) > chf("threshold")) {
    @mask = 1;
}

最初の行は、@P位置を使用して、HeightField Wrangle SOPの1番目の入力(0)のheightレイヤからvolumegradientを読み込みます。

1行目で生成されたgradientベクトルのlengthを、比較しやすいように0, 1の範囲に再マッピングします。 ボクセルのgradient値がカスタムパラメータのthresholdを超えた場合、そのボクセルが@maskの一部となります。

クリップ

この短いスクリプトは、カスタムパラメータのthresholdを超えるHeight Fieldを制限します。 heightthreshold値以上である場合、その最大高さは調整値となります。

float threshold = chf("threshold");

if (@height >= threshold) {
    @height = threshold;
}

3D Worleyノイズマスク

HoudiniのVEX言語には、様々なノイズタイプが用意されています。 これらのノイズタイプはマスク定義だけでなく、地形生成にも利用することができます。 地形生成の事例に関しては、以下で説明しています。

// 変数
vector frequency = chv("frequency");
float amplitude = chf("amplitude");
vector offset = chv("offset");
int seed;
float f1, f2, f3, f4;

// ノイズ値
wnoise(v@P * frequency + offset, seed, f1, f2, f3, f4);
float noise_value = f1 * amplitude;

// マスクの生成
if (noise_value > chf("threshold")) {
    @mask = 1;
}  

マスクをsnoiseに適用

以下のスクリプトでは、地形のheightsnoise関数で計算します、 既存のmaskレイヤは、height値とそのノイズのエレメントサイズを減衰させます。

例えば、上流にHeightField Mask Noise SOPを追加することで、初期マスクを作成することができます。

float amplitude = chf("amplitude");
float element_size = chf("element_size") * @mask;
int turbulence = chi("turbulence");
int period_x = chi("period_X");
int period_y = chi("period_Y");
int period_z = chi("period_Z");
float attenuation = chf("attenuation");

@height = snoise(@P * element_size, period_x ,period_y, period_z, turbulence, 0, attenuation) * amplitude * @mask;

先頭がfloatおよびintで始まるステートメントの行は、以下のsnoise関数を設定するためのカスタムパラメータを作成します。

このスクリプトは、完全にパラメトリックなsnoise関数を用いて、地形のheight値を計算します。 その後、そのノイズは、指定された@P位置でのamplitude * @maskで乗算します。

ランプをHeight Fieldに変換

以下のスクリプトは、ランプをHeight Fieldに変換します。 ランプ上にカーブを描くと、地形がそのカーブの形状に追従します。 また、カスタムパラメータのheight_factorを定義して地形をスケールさせることもできます。

vector bounding_box = relbbox(0, @P);
float elevation = chramp("remap", bounding_box.z);

@height += elevation * chf("height_factor");

このスクリプトは、Height Fieldの周囲に境界ボックスを作成して、その内部の@P位置をサンプリングします。 そのZ位置をカスタムランプに渡して、地形の高さを計算します。

最終的な@heightは、elevationとカスタムパラメータのheight_factorの積と合算されます。

カーブを地形に沿わせる

カーブが地形に沿うようにトランスフォームさせることができます。 以下のスクリプトは、HeightField Wrangle SOPで動作します。 実際、この手法は、ジオメトリを地形上に投影する際にHeightField Project SOPが実行する処理とほぼ同じです。

  1. 地形を含んだGeometry OBJ内で、Curve SOPを追加します。 ポリゴンカーブ、ベジェカーブ、スプラインカーブ用に事前定義されたツールのどれかを使用しても構いません。

  2. ビューポート上にマウスカーソルを置いてEnterを押して、そのCurve SOPの描画モードに入ります。

  3. 同様にビューポート上にマウスカーソルを置いてSpace + 2を押して、視点をTOPビューに変更し、XZ平面上でカーブを描画します。

  4. ポリゴンカーブを描画する場合、クリックでカーブポイントを描画します。 ベジェカーブやスプラインカーブを描画する場合、でクリックしてドラッグすることで、曲率を調整することができます。

  5. カーブの形状に満足したら、ESCを押して描画モードを抜けます。

  6. Resample SOPを配置して、その入力をそのCurve SOPの出力に接続します。 このノードは、カーブ上のポイント数を増加させ、十分なサンプリングポイントを確保します。 デフォルトの Length パラメータの値で十分な解像度が得られるはずです。

  7. そのResample SOPの出力をそのHeightField Wrangle SOPの 1番目 の入力に、Height Fieldを 2番目 の入力に接続します。 正しい結果を得るためには、この接続順が重要です。

vector hit_pos, hit_uv;
int prim = intersect(1, @P, {0, -1000, 0}, hit_pos, hit_uv);

if (prim >= 0) {
   @P = hit_pos; 
   @N = prim(1, "N", prim);
}

このスクリプトの核心部は、intersect関数です。 この関数は、入力1で指定されたHeight Fieldに向けて、カーブから光線を送信します。 交差を検索する方向と最大距離は、{0, -1000}で定義されています。

光線が地形に当たると、posベクトルにはその交差点の位置が含まれます。 方向が0の場合、posベクトルのYコンポーネントは地形の高さに等しくなります。

最後の行は、posベクトルをカーブポイントの位置の@Pに変換します。 uvベクトルはここでは使用しませんが、intersect関数の必須引数です。

マスクの組み合わせ

様々なマスクを組み合わせて、結果を修正するためのランプを適用することで、まったく新しいマスクを作成することができます。

  1. ネットワーク内の任意の位置にHeightField Wrangle SOPを追加します。 ただし、組み合わせたいマスク/レイヤの下流に配置してください。

  2. 以下のコードを⌃ Ctrl + Cでコピーして、そのHeightField Wrangle SOPの VEXpression フィールドに⌃ Ctrl + Vでペーストします。

    以下のサンプルコードは、@slopeレイヤと@occlusionレイヤを使用しています。 これらのレイヤ名は、実際のネットワーク内のレイヤ名に置換してください。 レイヤを追加したい場合は、乗算演算子でエクスプレッションにレイヤを追加します: @layer_1 * @layer_2 * ... * @layer_n

  3. Create spare parameter… ボタンをクリックして、ランプを作成します。

@mask = @slope * @occlusion; // ここで実際のレイヤ名を使用してください。

Height Fieldと地形

生成

  • 地形の生成

    HoudiniのHeight Fieldを用いた基本的なワークフロー。

  • マップからHeight Fieldを生成

    マップからディテールの細かい地形を生成することができます。

  • 単独の山

    必要な場所にカスタムの丘や山を生成します。

  • レイヤ

    レイヤを積み重ねてマスクを適用することで、よりリアルな表現を高めます。

  • 段丘化(Terracing)

    頂上に近いほど段状の地形を生成します。

  • 歪曲(Distortion)

    地形を形作って自然な見た目にします。

  • 地形のペイント

    アーティストの気分で、ご自身で景観をペイントしましょう。

  • 投影

    任意のジオメトリをHeight Fieldの一部に変換します。

  • パッチ(継ぎ合せ)

    パッチ(継ぎ合せ)とカットアウト(切り抜き)を用いて地形を組み立てます。

  • パターン

    基本的なジオメトリパターンからリアルな地形を生成します。

  • リサンプリング

    地形の解像度を段階的に上げます。

  • 様々な特徴部

    地形を変換、エクスポート、特徴豊かにします。

散布

マスク

  • マスキング

    修正したい領域やディテールを加えたい領域を定義します。

  • ライトマスク

    地形上の日照領域からマスクを作成します。

自然現象

VEX

テクスチャ

Shallow Water Solver(浅水ソルバ)