| On this page |
HeightField Scatter SOPは、設定に応じてサーフェス全体にポイントを生成します。
しかし、不要な箇所にポイントが生成される場合もあります。
もちろん、マスクを作成してそれらの不要なポイントを除去することもできますが、適切なマスクの作成が容易でない場合もあります。
例えば、カメラフラスタム(カメラの視界範囲)外の領域をマスクしたい場合がそうです。
また、地形の境界に向かって散布ポイントを減少させたい場合もあるでしょう。
このページは、Height Fieldの散布に関する4ページ構成のガイドの第2部です:
-
散布: SOPでは、シーンの準備方法を説明しています。
-
“散布:ポイントの除去”では、散布ポイント数を制御する方法を説明しています。
-
散布: アトリビュートでは、
pscaleやorientといったアトリビュートを作成します。 -
散布: Solarisでは、地形をレンダリングする方法を説明しています。
一部の例では、不要なポイントを除去する便利な方法は、VEXスクリプトを書くことです。
Tip
Height Field全般におけるVEXスクリプトに興味があるようでしたら、Height Field向けのVEXスクリプトのページもご覧ください。
マスキング ¶
Note
散布: SOPのページで既に説明しています。 この章では、わざわざ別のページに切り替える必要がないよう便宜的に記載しています。
Height Field上の散布ポイントを用いたマスキングは、一般的なマスキングとまったく同じ方法で動作します。
正確に言えば、マスクはポイントを削除するのではなく、ポイントが出現する領域を定義します。
以下に、散布マスクの非常に基本的なセットアップを説明します(既にHeightField SOPが作成済みであることを前提にしています)。
-
HeightField Mask Noise SOP(または他のマスクノード)を配置して、 1番目 の入力をそのHeightField SOPの出力に接続します。
-
HeightField Scatter SOPを追加します。 1番目 の入力をそのHeightField SOPの出力に接続し、 2番目 の入力をそのHeightField Mask Noise SOPの出力に接続します。
-
そのHeightField Scatter SOPの青い Display/Render フラグを有効にして、その結果を表示します。
HeightField Mask Noise SOPのデフォルト値では、空白部分が生じ、残りの地形がポイントで覆われていることに気づくと思います。 よく見ると、密度の異なる領域が存在しています。 HeightField Mask Noise SOPの Amplitude パラメータによって、そのポイント密度を直接調整することができます。
-
1未満の値を設定すると、その空白領域が拡大します。 -
例えば
2のような値では、非常に密な構造が得られます。
次に、 Element Size パラメータを変更します。
このパラメータは、ノイズパターンのサイズを制御します。
値が小さいほどノイズが増加し、ディテールもより細かく表現されます。
下図は、 Amplitude パラメータを1.5に、 Element Size パラメータを200に設定した結果です。
これによって、現実的なポイント分布が得られます。
Note
以下に紹介するすべてのスクリプトをマスク処理されたHeight Fieldに適用することで、散布ポイントの数をさらに減らして面白い効果を生み出すこともできます。
Scatter Method ¶
HeightField Scatter SOPには Scatter Method ドロップダウンメニューが用意されています。 最初の3つの項目は、ポイント数を制御する Mask Layer もサポートしています。 デフォルトは By Coverage using Mask Layer メソッドで、その結果は先ほどの画像で確認することができます。 Coverage パラメータを変更すると、ポイントを追加/削除することができます。
By Density using Mask Layer メソッドは、その“By Coverage using Mask Layer”メソッドと同様に動作しますが、 こちらでは Density パラメータを設定します。
最後に、 Total Point Count using Mask Layer メソッドがあります。 こちらは、固定数のポイントを設定します。 マスクは地形全体を覆うことも10%の小さな領域のみを覆うことも可能です。 いずれの場合もポイント数は常に同じになります。
ノードの準備 ¶
ここではポイントを扱うので、HeightField Wrangle SOPではなくて
Point Wrangle SOPを用いてスクリプトを適用することになります。
適切なノードを作成してスクリプトを追加する手順は常に同じです。
-
Point Wrangle SOPを追加して、 1番目 の入力を上流の最終ノードの出力に接続します。
-
コードを⌃ Ctrl + Cでコピーして、 VEXpression フィールド内でそのコードを⌃ Ctrl + Vでペーストします。
-
ボタンをクリックしてカスタムパラメータを作成します。
境界ポイント ¶
最初のスクリプトは、カスタマイズ可能な領域内で地形境界からポイントを削除します。 ソフトな遷移かハードな遷移かを選択することもできます。 ポイントは削除されず、別グループ内に格納されます。 これは、後ほど別の場所で使用する必要が生じた場合に備えているだけです。 このスクリプトは、
-
X方向およびY方向における散布領域の境界を定義するカスタムパラメータを追加します。
-
境界に向かった滑らかな遷移を迅速に生成するためのランダムな
offsetを追加します。 -
ポイントの位置と散布領域の境界を比較します。
-
シーム内のポイントを削除します。
以下にVEXコードを載せます。 いつものごとく、これはそのようなスクリプトを作成するための一つの方法に過ぎません。
float limit_x = chf("limit_x"); float limit_z = chf("limit_z"); float offset = chf("offset"); float seam = fit01(random(@ptnum), -offset, 0); float border_x = limit_x + seam; float border_z = limit_z + seam; if (@P.x > border_x || @P.x < -border_x || @P.z > border_z || @P.z < -border_z) { removepoint(geoself(), @ptnum); }
上記のコードを適用して境界ポイントを削除するために、以下の手順を踏みます。
-
Limit X パラメータおよび Limit Z パラメータには、例えば
480と入力します。 これによって、標準のHeight Field(1000 m × 1000 m)の周囲に幅20mのフレームが生成されます。 -
散布ポイントから地形境界までの滑らかな遷移を得るために、 Offset パラメータを
100に設定します。
カメラフラスタム ¶
リソース節約の非常に一般的な手法として、カメラフラスタム外にあるポイントを削除する方法があります。
この考え方は、上記の境界ポイントの例と基本的に同じです。
主な違いは、カメラ用の特別なベクトルが必要となる点です。
このベクトルは、位置をカメラの標準デバイス座標系(“NDC”)にトランスフォームします。
HoudiniのVEXには、このトランスフォームを自動的に行なう組み込み関数があります:toNDC。
この関数はカメラの代わりに光源も使用することができます。
このスクリプトは、
-
カスタムカメラを受け取って、そのトランスフォームを計算します。
-
可視領域の周囲に安全バッファを作成するためのシームを定義することができます
-
ポイントの位置とNDCベクトルのXZコンポーネントを比較して、フラスタム外にあるポイントを判定します。
-
それに該当したポイントを直接削除します。
このスクリプトの利点は、Pointアトリビュートが維持されるので、アニメーションカメラでも動作する点です。 これによって、スケールやインスタンスオブジェクト自体が各フレームで変化しなくなります。
ただし問題として、特定の位置でHeight Field全体が削除されてしまいます。
Geometry Spreadsheetペインの Points モードを開くと、
最初の2つのエントリに値が
0になっている複数のアトリビュートが存在することが確認できます。
これら2つのエントリはHeight Fieldに属しています。
スクリプトが誤ってそれらのHeight Fieldを削除しないようにしたいのであれば、HeightField Scatter SOPの Relaxation セクションにある Keep Incoming Terrain オプションを有効にすると良いでしょう。
たとえそれらのHeight Fieldを削除しても、後でMerge SOPを使用して、ポイントと地形を再びマージすることができます。
ここでは、 Keep Incoming Terrain オプションを無効にします。
カメラフラスタムによるポイント削除を行なう場合は、2個のAttribute Wrangle SOPを使用するべきです。 これはカメラパスを読み取る必要があるのですが、そのカメラパスの読み取り処理をすべての散乱ポイントに対して行なうのは効率的ではありません。 Detailアトリビュートを使用してカメラを取得する方が適切なので、まずは Detail Wrangle SOP を作成し、 1番目 の入力を上流のノードの出力に接続します。
便宜的な理由で、そこで Seam パラメータも作成します。 ここで作成するDetail Wrangle SOPは、ポイントを削除するPoint Wrangle SOPより 前 のネットワークに必ずなければなりません。
以下がその Detail Wrangle SOP のVEXコードです:
s@campath = chs("camera_path"); f@seam = chf("seam");
の Creates spare parameters for each unique call of ch() ボタンをクリックしてそれらのカスタムパラメータを作成します。
-
ビューポートで地形の見やすい視点を探します。
-
ビューポートの右上コーナーに2つのメニューがあります。 No cam メニューを開いて、 New Camera を選択します。 これによって、 obj レベルに
cam1カメラオブジェクトが作成されます。 -
Camera Path パラメータには
/obj/cam1と入力します。 これは、手順2で新規作成したカメラのパスです。 -
Seam パラメータを変更します。 以下の設定では、これを
0に設定すると、カメラフラスタムの輪郭線上に正確に沿ったポイントを削除するようにします。0より大きい値を設定すると、カメラフラスタム外に安全バッファが作成されるようにします。
次に Point Wrangle SOP を作成して、 1番目 の入力を Detail Wrangle SOP の出力に接続します。 そのPoint Wrangle SOP上で、以下のVEXスクリプトを入力します:
vector camera_ndc = toNDC(s@campath, @P); float border_min = 0.5 - f@seam; float border_max = 0.5 + f@seam; if (camera_ndc.x + f@seam < 0 || camera_ndc.x - f@seam > 1 || camera_ndc.y + f@seam < 0 || camera_ndc.y - f@seam > 1) { removepoint(geoself(), @ptnum); }
下図では、結果をより見やすくするために、カメラフラスタム外のパーティクルを赤で表示しています。
Tip
カメラフラスタム外のパーティクルは、削除せずに代わりにグループに格納することもできます。
これは、removepoint(geoself(), @ptnum);を@group_outsidepoints = 1;に置換すれば良いです。
カメラ距離 ¶
カメラからポイントまでの距離に基づいてポイントを削除することもできます。 マスクを描画しなくても、スライダで距離を調整するだけで不要なポイントを除去することができます。 円形または円弧状の境界が生成されることに注目してください。 このスクリプトでは
-
カスタム距離閾値を定義することができます。
-
カメラのワールド空間での位置を照会します。
-
カメラと散布ポイント間の距離を計算します。
-
指定された閾値を超えたポイントを除去します。
-
カスタムパラメータの Seam を使用して、ソフトな遷移を作成します。
またも同様に位置情報を扱うので、Height Field自体が消えてしまいます。 これを回避したいのであれば、HeightField Scatter SOPの Keep Incoming Terrain オプションを有効にすると良いでしょう。 ここでは、 Keep Incoming Terrain オプションを無効にします。
カメラフラスタムと同様に、いくつかのパラメータや値を初期化するために Detail Wrangle SOP も必要になります。
以下に、関連するすべてのパラメータを含んだスクリプトを載せます。
4@camera_matrixは、4×4行列を計算します。
これを散布ポイント毎に実行すると処理が劇的に遅くなります。
この@camera_matrixを1回限りで実行するDetailアトリビュートとして定義することで、距離計算が非常に速くなります。
このスクリプトは、optransform関数を利用してカメラ行列を計算します。 カメラ固有のオフセットを補正するためには、高度な手法が必要になります。
// 4x4トランスフォーム行列を計算します。 4@camera_matrix = optransform(chs("camera_path")); f@dist_threshold = chf("distance_threshold"); f@seam = chf("seam");
カスタムパラメータを作成するために、 Creates spare parameters for each unique call of ch() ボタンをクリックします。
新規シーンのビューポートからカメラを作成した場合、 Camera Path は
/obj/cam1となります。
下記の画像における Dist Threshold の値は800で、滑らかな遷移を得るために Seam を200に設定しています。
以下が Point Wrangle SOP のスクリプトです:
vector camera_position = set(0,0,0) * 4@camera_matrix; float seam = fit01(random(@ptnum), f@seam, 0); float point_distance = distance(@P, camera_position); if (point_distance > f@dist_threshold - seam) { removepoint(geoself(), @ptnum); }
この画像では、 Dist Threshold の外側にあるポイントがオレンジ色で表示されています。
遮蔽されているポイント ¶
カメラ距離のスクリプトは、カメラからポイントまで距離に基づいてポイントを削除することができました。 これはこれで役に立ちますが、必ずしも効果的であるとは限りません。 より選択的な手法によって、遮蔽されたポイントを削除することができます。 このスクリプトは、カメラから光線を送り出して、地形との交差をテストします。 山やその他のジオメトリによって遮蔽されているポイントが削除されます。
Note
この手法では、山を生成するHeightField Noise SOPが必要です。
このスクリプトは、
-
カメラオブジェクトを選択することができます。
-
カメラのワールド空間での位置を照会します。
-
カメラの方向を計算します。
-
カメラからポイントまでの距離を計算します。
-
距離と方向に基づいた光線を送信して、遮蔽されたポイントを検出します
-
Height Field自体が消えないように、
tagアトリビュートを利用します。
これまでと同様に、 Detail Wrangle SOP と Point Wrangle SOP が必要です。 以下は、そのDetail Wrangle SOPのコードです:
// 4x4トランスフォーム行列を計算します。 4@camera_matrix = optransform(chs("camera_path"));
カスタムパラメータを作成するために、 Creates spare parameters for each unique call of ch() ボタンをクリックします。
新規シーンでは、デフォルトの Camera Path は
/obj/cam1となります。
カメラフラスタム用とカメラ距離用の両方のポイント除去スクリプトを正しく動作させるために、これまでは地形を削除しました。
この手法では、遮蔽領域を計算するために“ジオメトリ”が必要となるため、Height Fieldが不可欠です。
つまり、Height Fieldを定義した2つのポイントは処理の対象外にしなければなりません。
その一つの方法は、HeightField Scatter SOPで作成されたtagアトリビュートを利用することです。
Height Fieldを定義したポイントでは、そのtagアトリビュートに何も値が入らないので、その値が空っぽかどうかで判断することができます。
vector camera_position = set(0,0,0) * 4@camera_matrix; vector camera_direction = normalize(camera_position - @P); float camera_distance = distance(@P, camera_position); vector pos, uvw; int hit_test = intersect(0, @P, camera_direction * camera_distance, pos, uvw); if (hit_test != -1 && s@tag != "") { removepoint(geoself(), @ptnum); } if (hit_test != -1) { removepoint(geoself(), @ptnum); }
下図では、地形から遮蔽されたポイントが除去されているのが分かります。