[Houdini] Houdini Technical Artist Procedural HDA Related 031 Parameters and Interchangeable Meshes
① attribpaint1 を選択
ノードをクリック
② ビューポートで
Enterキー押す
③ すると変わる
カーソルがブラシになる
Geometry Spreadsheet
Scene View | Animation Editor | Render View | 👉 Geometry Spreadsheet
Alt + Shift + G
vector Nn = normalize(@N);
// maskで生やす場所制御
if (@mask < 0.5) {
removepoint(0, @ptnum);
return;
}
// 急斜面を除外
if (Nn.y < 0.7) {
removepoint(0, @ptnum);
return;
}
if (s@name == "tree" && rand(@ptnum) < 0.7) {
removepoint(0, @ptnum);
}
if (s@name == "tree" && @mask < 0.8) {
removepoint(0, @ptnum);
}
// 傾き調整
vector worldUp = {0,1,0};
Nn = normalize(lerp(worldUp, Nn, 0.3));
// 向き
matrix3 m = dihedral({0,1,0}, Nn);
//p@orient = quaternion(m);
if (s@name == "tree") {
// 木は基本まっすぐ
}
else if (s@name == "rock") {
float angle = rand(@ptnum) * M_PI * 2;
p@orient = qmultiply(p@orient, quaternion(angle, {0,1,0}));
}
else {
// 草はちょい倒す
vector tilt = normalize(Nn + rand(@ptnum));
p@orient = quaternion(dihedral({0,1,0}, tilt));
}
// ひねり
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
// サイズ
@pscale = fit01(rand(@ptnum), 0.5, 1.5);
float d = length(@P);
@pscale *= fit(d, 0, 10, 1.5, 0.3);
// 浮かせる
@P += Nn * (@pscale * 0.5);
// 種類切り替え
float r = rand(@ptnum * 17.123);
if (r < 0.33) {
s@name = "tree";
@pscale *= fit01(rand(@ptnum + 100.0), 0.9, 1.4);
}
else if (r < 0.66) {
s@name = "rock";
@pscale *= fit01(rand(@ptnum + 200.0), 0.05, 0.06);
}
else {
s@name = "grass";
@pscale *= fit01(rand(@ptnum + 300.0), 0.4, 0.9);
}
1.HDA最短ルート
今のネットワークをそのままHDAにするんじゃなくて、UEで触りたい入口だけ残して整理する。
やる順番はこれ。
1. HDA用の最終ノードを1本にまとめる
今は途中ノードが多いから、最後に Null を1個作って名前を
OUT_FOLIAGE
にする。
UEに持っていく出力はこれだけにする。
2. UEで触りたい値だけパラメータ化する
まず必要なのはこのへんだけでいい。
- Scatterの数
- Mountainの高さ
- maskしきい値
- tree / rock / grass の出現率
- 各種類のスケール
たとえば Wrangle でベタ書きしてる数値を消して、チャンネル参照に変える。
例
今こういうのを
if (@mask < 0.5) {
removepoint(0, @ptnum);
return;
}
こうする。
float mask_threshold = ch("mask_threshold");
if (@mask < mask_threshold) {
removepoint(0, @ptnum);
return;
}
3. 種類の比率もパラメータ化
今のランダム分岐もUEで触れるようにする。
float tree_ratio = ch("tree_ratio");
float rock_ratio = ch("rock_ratio");float r = rand(@ptnum * 17.123);if (r < tree_ratio) {
s@name = "tree";
@pscale *= fit01(rand(@ptnum + 100.0), 0.9, 1.4);
}
else if (r < tree_ratio + rock_ratio) {
s@name = "rock";
@pscale *= fit01(rand(@ptnum + 200.0), 0.25, 0.6);
}
else {
s@name = "grass";
@pscale *= fit01(rand(@ptnum + 300.0), 0.4, 0.9);
}
これなら UE 側で tree_ratio と rock_ratio を触れる。
4. 地形入力を固定生成から「外部入力」に変える
UEで使うなら、Houdini内の grid1 をそのまま使うより、UEの地形やメッシュを入力できる形の方が強い。
最初の段階ではこうすると早い。
方法
grid1 の代わりに Object Merge を置く。
そのObject Mergeの元に、HDA入力を使う。
最初は難しく感じるから、超最短なら今はこうでもいい。
- 今の
grid1 → mountain1 構成のままHDA化
- UE上では「生成ツール」として使う
- その後、入力メッシュ対応に拡張する
まず持っていくことが優先なら、これで十分。
5. 不要な表示用ノードを外す
merge1 は確認用だから、HDA出力には入れない方が楽。
出力は基本、
copytopoints1 → OUT_FOLIAGE
だけでいい。
地面まで一緒に出したいなら別Nullにする。
OUT_INSTANCES
OUT_TERRAIN
でも最初は OUT_FOLIAGE だけでいい。
attribwrangle1 の完成版
vector Nn = normalize(@N);
float mask_threshold = ch("mask_threshold");
if (@mask < mask_threshold) {
removepoint(0, @ptnum);
return;
}
float slope_threshold = ch("slope_threshold");
if (Nn.y < slope_threshold) {
removepoint(0, @ptnum);
return;
}
vector worldUp = {0,1,0};
float upright_blend = ch("upright_blend");
Nn = normalize(lerp(worldUp, Nn, upright_blend));
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
float base_scale_min = ch("base_scale_min");
float base_scale_max = ch("base_scale_max");
@pscale = fit01(rand(@ptnum), base_scale_min, base_scale_max);
float tree_ratio = ch("tree_ratio");
float rock_ratio = ch("rock_ratio");
float r = rand(@ptnum * 17.123);
if (r < tree_ratio) {
s@name = "tree";
@pscale *= fit01(rand(@ptnum + 100.0), ch("tree_scale_min"), ch("tree_scale_max"));
}
else if (r < tree_ratio + rock_ratio) {
s@name = "rock";
@pscale *= fit01(rand(@ptnum + 200.0), ch("rock_scale_min"), ch("rock_scale_max"));
}
else {
s@name = "grass";
@pscale *= fit01(rand(@ptnum + 300.0), ch("grass_scale_min"), ch("grass_scale_max"));
}
float offset_mult = ch("offset_mult");
@P += Nn * (@pscale * offset_mult);
最初に作るパラメータ
この名前で作ればかなり使いやすい。
mask_threshold = 0.5
slope_threshold = 0.7
upright_blend = 0.3
base_scale_min = 0.5
base_scale_max = 1.5
tree_ratio = 0.33
rock_ratio = 0.33
tree_scale_min = 0.9
tree_scale_max = 1.4
rock_scale_min = 0.05
rock_scale_max = 0.06
grass_scale_min = 0.4
grass_scale_max = 0.9
offset_mult = 0.5
HDA化の手順
1
最終的に使うノード群を全部選ぶ
2
サブネット化する
3
そのサブネットを右クリックして Create Digital Asset
4
名前を付ける
たとえば
furcraea_foliageScatter
5
Type Properties を開いて、今作ったパラメータを見えるようにする
UEに持っていくときの最初の形
最初は欲張らずこうするのが早い。
- 入力なし
- HDAを置く
- パラメータを触る
- インスタンスが更新される
これでまず成功体験を作る。
その後に
- Static Mesh Input
- Landscape Input
- World Partition対応
- bake
を足せばいい。
最終ノード構成
[VARIANTS]
box1
└→ attribwrangle2 // s@name="tree";
└→ pack1
└→ attribwrangle5 // s@name="tree";
sphere1
└→ attribwrangle3 // s@name="rock";
└→ pack2
└→ attribwrangle6 // s@name="rock";
tube1
└→ attribwrangle4 // s@name="grass";
└→ pack3
└→ attribwrangle7 // s@name="grass";
attribwrangle5 / 6 / 7
└→ merge_variants
[PLACEMENT]
grid1
└→ mountain1
└→ normal1
└→ attribpaint1
└→ scatter1
└→ attribwrangle1
[OUTPUT]
merge_variants ───────────────→ copytopoints1 (左)
attribwrangle1 ───────────────→ copytopoints1 (右)
copytopoints1 ────────────────→ OUT_FOLIAGE
HDAで外に出すパラメータ
まずはこれだけで十分。
地形
mountain_height
scatter_count
マスク・斜面
mask_threshold
slope_threshold
upright_blend
全体サイズ
base_scale_min
base_scale_max
出現率
種類別サイズ
tree_scale_min
tree_scale_max
rock_scale_min
rock_scale_max
grass_scale_min
grass_scale_max
浮かせ量
それぞれどこに仕込むか
mountain1
Height を promote
名前は mountain_height
scatter1
Force Total Count を promote
名前は scatter_count
attribwrangle1
今の ch("...") がそのまま出せる
1回HDAにしたあとの出力方法がわからない
① HDAとしてそのままUEで使う(←メイン)
手順
① OUTノードを確認
最後がこれになってるか👇
copytopoints1 → OUT_FOLIAGE
👉 Nullの名前が重要
OUT_FOLIAGE
② HDA化
- objレベルに戻る
box_object1 を右クリック
- 👉 Create Digital Asset
FoliageScatter
- 保存:
.hda
③ 保存(超重要)
👉 HDAは「Save Node Type」しないと更新されない
右クリック → Save Node Type
④ Unreal Engine 側
- Houdini Engine Plugin 有効化
.hda をContent Browserにドラッグ
- レベルに置く
⑤ 出てこない場合チェック
- OUTノードが無い
- OUTじゃないノードを表示してる
- copytopointsの後じゃない
これ、エラーじゃなくて**「UIレイアウトどうする?」って確認ダイアログ**だから安心していい。
✅ 今回はこれ押す
👉 Revert Layout
UEのバージョンはどれにする?
Houdini 21.x
👉 UE5.6 / 5.7 が正解
① Houdini側にあるPluginを使う
C:\Program Files\Side Effects Software\Houdini 21.0.xxx\engine\unreal
C:\Program Files\Side Effects Software\Houdini 21.0.671\engineに
unreal
ない
① Houdini Launcher開く
あるなら
C:\Program Files\Side Effects Software\Houdini Engine\Unreal\21.0.671
BLANK
Plugins
にいれる
Houdini Engineにチェックいれる
HDAをドラッグ
正しいノード構成
Subnetwork Input #1
object_merge1 Object1=../Subnetwork Input #1
object_merge1 Object1=../subnetwork_input1
object_merge1 Object1=opinputpath("..", 0)
↓
scatter1
↓
attribwrangle
↓
copytopoints1
↓
OUT_FOLIAGE
いみない
attribwrangle8
vector Nn = {0,1,0};
if (len(@N) > 0.0) {
Nn = normalize(@N);
}
if (haspointattrib(0, "mask")) {
float mask_threshold = ch("mask_threshold");
if (@mask < mask_threshold) {
removepoint(0, @ptnum);
return;
}
}
float slope_threshold = ch("slope_threshold");
if (Nn.y < slope_threshold) {
removepoint(0, @ptnum);
return;
}
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
@pscale = 1.0;
これで見える状態
書き出して
UEでも
これでいい この△だらけのデータをビビらずにUEに持っていく。
UE OK
さらに normalノード復帰
vector Nn = {0,1,0};
if (len(@N) > 0.0) {
Nn = normalize(@N);
}
if (haspointattrib(0, "mask")) {
float mask_threshold = ch("mask_threshold");
if (@mask < mask_threshold) {
removepoint(0, @ptnum);
return;
}
}
float slope_threshold = ch("slope_threshold");
if (Nn.y < slope_threshold) {
removepoint(0, @ptnum);
return;
}
if (length(v@N) > 1e-6)
{
Nn = normalize(v@N);
}
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
@pscale = 1.0;
HOUDINIで動作確認後
書き出し
Normalノード追加で復帰した
この状態
vector Nn = {0,1,0};
if (length(v@N) > 1e-6)
{
Nn = normalize(v@N);
}
if (haspointattrib(0, "mask"))
{
float mask_threshold = ch("mask_threshold");
if (f@mask < mask_threshold)
{
removepoint(0, @ptnum);
return;
}
}
float slope_threshold = ch("slope_threshold");
if (Nn.y < slope_threshold)
{
removepoint(0, @ptnum);
return;
}
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
@pscale = fit01(rand(@ptnum), 0.5, 1.5);
@P += Nn * @pscale * 0.2;
ch(“slope_threshold”);のパラメータ化
① attribwrangle8 選択
② 右のパラメータ欄の上にある
👉 ⚙️(歯車) → Edit Parameter Interface
③ 左側から追加
Name: slope_threshold
Label: slope_threshold
④ Default値入れる
例:
0.3
⑤ Accept
👉 Save Node Type(超重要)
② 一番上のノード(HDA本体)を選択
👉 furcraea_foliagescatter(一番外のやつ)
③ 右クリック
👉 Type Properties
④ Parametersタブ
今の状態👇
- slope_threshold は「Wrangleの中」
- HDA側には無い
⑤ ここでやる
左側の一覧から👇
👉 attribwrangle8 の slope_threshold をドラッグ
👉 右側(HDAのUI)にドロップ
../ を追加
vector Nn = {0,1,0};
if (length(v@N) > 1e-6)
{
Nn = normalize(v@N);
}
if (haspointattrib(0, "mask"))
{
float mask_threshold = ch("../mask_threshold");
if (f@mask < mask_threshold)
{
removepoint(0, @ptnum);
return;
}
}
float slope_threshold = ch("../slope_threshold");
if (Nn.y < slope_threshold)
{
removepoint(0, @ptnum);
return;
}
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);
float angle = rand(@ptnum + 10.0) * M_PI * 2.0;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));
@pscale = fit01(rand(@ptnum), 0.5, 1.5);
@P += Nn * @pscale * 0.2;
ちゃんと 1.01で消えた
👉 UE → Houdini → Wrangle → パラメータ制御
全部つながった
これはかなりデカい
🧠 今の到達点
| 要素 | 状態 |
|---|
| 入力 | ✅ |
| scatter | ✅ |
| orient | ✅ |
| pscale | ✅ |
| offset | ✅ |
| slope制御 | ✅ |
| UEパラメータ連動 | ✅ |
👉 プロシージャルツールとして成立してる
つまり問題はここ
👉 法線の分布が単調すぎる
ノード構成
furcraea_foliagescatter3 内で
Subnetwork Input #1
↓
normal_input
↓
attribnoise1
↓
normal1
↓
attribwrangle9 (f@slope を作る)
↓
scatter1
↓
attribwrangle8 (slope_threshold で削除)
↓
copytopoints1
attribwrangle9 に入れるコード (scatter 前に slope を保存するノード)
ノード名: attribwrangle9
役割: scatter 前に slope 属性を作る
VEX 全文をこれに置き換えてください。
vector Nn = {0, 1, 0};
if (length(v@N) > 1e-6)
{
Nn = normalize(v@N);
}
// slope: 0 = 平地, 1 = 垂直
f@slope = 1.0 - clamp(Nn.y, 0.0, 1.0);
接続:
normal1 → attribwrangle9 → scatter1
attribwrangle8 に入れるコード(scatter 後に slope_threshold で消すノード)
ノード名: attribwrangle8
役割: scatter 後のポイントを slope で削除する
VEX 全文をこれに置き換えてください。使いやすいレンジに圧縮します。
float ui_threshold = ch("../slope_threshold");
// UIの0.0~1.0を、実際の有効域に再マップ
float slope_threshold = fit(clamp(ui_threshold, 0.0, 1.0), 0.0, 1.0, 0.8, 1.0);
if (f@slope > slope_threshold)
{
removepoint(0, @ptnum);
return;
}
@pscale = 0.05;
接続:
scatter1 → attribwrangle8 → copytopoints1
0.0から増えて1.0から増えなくなった 完璧だね
今の挙動なら slope_threshold が UE 側で素直に使える状態 になっています。
パラメータ制御 + メッシュの入れ替えができてます