地形に転石を分布させる

環境:Gaea 2.0.2.3, Houdini 20.0.751

Gaeaを使った地形と転石について。DOPシミュレーションを使って整えます。Unreal Engineに出力することを目的としています。

転石の分布マスク画像を作成する

最近新しいバージョンになったGaea2のDebrisノードを使う。

1km×1kmの山岳地形。

DebrisノードのDebris出力で転石の分布画像を作成できる。

Erosion2で浸食させたハイトマップと、デブリ(転石)のマスクマップが生成されます。

ハイトマップを読み込む

HeightField File SOPでGaeaから出力したハイトマップを読み込み、四方の長さと高さを入力する。Convert SOPでポリゴンメッシュへ変換する。その後Transform SOPのY軸回転に90を入力して方向を合わせる。

UV Texture SOPでUV値を合わせる。

Gaeaからのデブリ画像をAttribute from Map SOPで貼り付ける。Export Attributeにdensityと入力しておく。

Scatter SOPでdensityアトリビュートを使って、8000個のポイントを分布させたところ。

ポイント群に岩の種類を割り当てる

続けて、ポイントに岩の種類を割り振る。岩の種類は小、中、大の三種類とした。

岩の種類のインデックス番号をポイントに割り振るコード。ここでは小さい岩を10、中の岩を6、大きい岩を3の割合で分布させることにした。

//
// ポイントに設定した割合でグループ番号を割り振る
//
// Run Over: Points

// 量の割合を設定する(10:6:3の割合で0,1,2の番号が振られるようにする)
int ratio[] = {10, 6, 3};

// 割合の合計を計算
float sum = 0;
for(int i = 0; i < len(ratio); i++)
{
    sum += ratio[i];
}

// 0から1の間に正規化する
float value[];
for(int i = 0; i < len(ratio); i++)
{
   value[i]= ratio[i] / sum;
}

// 割合を尺度に変換する(例:[0.3, 0.5, 0.2]→[0.3, 0.8, 1.0])
sum = 0;
for(int i = 0; i < len(value); i++)
{
   value[i] = value[i] + sum;
   sum = value[i];
}

// インデックスを振っていく
for(int i = 0; i < len(value); i++)
{
    if(@ptnum < value[i]*npoints(0))
    {
        i@type = i;
        break;
    }
}

ポイント同士の座標をシャッフルするコード

//
// ポイント座標をシャッフルする
//
// Run Over: Detail
for(int i = 0; i < npoints(0); i++)
{
    int n = int(rand(i*6.58) * npoints(0));
    vector pos = point(0, "P", n);
    vector pos2 = point(0, "P", i);
    setpointattrib(0, "P", i, pos);
    setpointattrib(0, "P", n, pos2);
}

Color SOPで種類別でポイントに色を付けてみたところ。緑色:小、黄色:中、オレンジ:大。

岩アセットを用意する

File SOPから岩のメッシュを読み込む。小さい岩はMegascansからSandstone_Boulder_vmiefjcという岩モデルを使うことにした。

転がすために座標は1m等、少し高めにオフセットしておく。

計算の負担を減らすために、PolyReduce SOPでローポリゴンモデルに削減する。

岩のスケールにはランダムをかけている。アトリビュートには@pscaleを使う。

float min = 1.2;
float max = 2.0;
@pscale = random(@ptnum)*(max-min) + min;

またprimitiveのnameアトリビュートにはrock_smallといれた。のちにCSV出力するときに識別するため。

@name = "rock_small";

Copy to Points SOPではPack and Instancesにチェックを入れ、Pivot LocationはCenterに、Copyingでpscaleの値を追加しておく。

これを中サイズと大サイズの岩で同じように作成し、Merge SOPでまとめる。

Wrangleで初期のランダムな回転値(クォータニオン)を作成しておく。

//
// ランダム回転のクォータニオンを保存しておく
//
// Run Over: Points

// Y軸ランダム
vector axis = {0, 1, 0};
float angle = radians(360*rand(@ptnum * 6.358));
vector4 add_rotate = quaternion(angle, axis);
p@orient = qmultiply(p@orient, add_rotate);

// X軸ランダム
axis = {1, 0, 0};
angle = radians(360*rand(@ptnum * 3.256));
add_rotate = quaternion(angle, axis);
p@orient = qmultiply(p@orient, add_rotate);

そしてNull SOPにつなぎ、名前を「OUT_Rock」としておく。

DOPシミュレーションで岩を転がす

コリジョン用にメッシュを削減し、Null SOPをつなぎ、「OUT_Colli」としておく。

DOP Networkノードをつくり、クリックして中に入る。

中のネットワークはこのように構成する。左側のStatic ObjectのSOP Pathにはコリジョンメッシュの「OUT_Colli」をリンクする。右側はのRBD Packed ObjectのSOP Pathには「OUT_Rock」をリンクする。

Dop Importノードをつくり、DOP Networkにdopnet1をリンクし、Object Maskにrbdpackedobject1をリンクする。

この時点でアニメーションを再生するとシミュレーションが始まる。100フレームも回せば転石も落ち着いてくる。

そのあとにWrangleノードをつなげて、クォータニオンから軸のベクトルを取得する。

//
// フォワードとアップベクターを求める
//
@up = qrotate(@orient, set(0,1,0));
@N = qrotate(@orient, set(0,0,1));

NとUpベクターを視覚化したもの。これで出力するデータがそろった。

結果をCSVに出力する

ポイント群をCluster Points SOPでクラスタリングしておいた。

named primitiveごとにループさせ、Cluster Points SOPをはさんで任意の数で分け、Sort SOPでクラスター番号順に整える。これはCSVで書き出すときに昇順の並びにするため。直後にAttribute Promote SOPでnameをprimitiveからpointへ移しておく。

PythonノードでCSV出力する。出力する値は、名前、クラスター番号、トランスフォーム。これをUnreal Engineで読み込んでアセットを配置する情報にする。

Pythonコードを実行するノードの作成

node = hou.pwd()
geo = node.geometry()
 
# UnrealEngineで読み込む形式でCSVを出力する
filePath = hou.node(".").parm("file").eval()
 
# aなら追加書き込みモード
file = open(str(filePath), "w")
 
row = "," + "Name" + ",Cluster" + ",Position" + ",Up" + ",Forward" + ',Scale' + "\n"
file.write(row)

num = len(geo.points())

i= 0
for point in geo.points():
    name = point.attribValue("name")
    cluster = point.attribValue("cluster")
    pos = point.position()
    up = point.attribValue("up")
    forward = point.attribValue("N")
    scale = point.attribValue("pscale")
    
    row = str(name)
    row += ',' + str(cluster)
    row += ',' + str(pos[0]) + "," + str(pos[2]) + "," + str(pos[1])
    row += ',' + str(up[0]) + "," + str(up[2]) + "," + str(up[1])
    row += ',' + str(forward[0]) + "," + str(forward[2]) + "," + str(forward[1])
    row += ',' + str(scale) + "," + str(scale) + "," + str(scale)
    file.write(row)
    
    # 最後以外の行なら改行
    if(i < num-1):
        file.write("\n")
        i+=1
 
file.close

出力されたCSVの中身はこのようになります。

,Name,Cluster,Position,Up,Forward,Scale
rock_large,0,-17755.26171875,21191.552734375,23533.630859375,-0.962436318397522,-0.26899343729019165,-0.03686538338661194,0.17768798768520355,-0.726696252822876,0.6635808944702148,1.063361406326294,1.063361406326294,1.063361406326294
rock_large,0,-24989.22265625,34606.2421875,24200.970703125,0.5980241894721985,0.5814560651779175,0.5516121983528137,0.26902106404304504,-0.7939407825469971,0.5452392101287842,1.1072680950164795,1.1072680950164795,1.1072680950164795
rock_large,0,-28005.95703125,18213.55078125,25254.4140625,0.5264458656311035,-0.8066633939743042,-0.26860547065734863,-0.23081330955028534,0.168465256690979,-0.9583029747009277,1.1269376277923584,1.1269376277923584,1.1269376277923584
rock_large,0,-26206.234375,13078.98828125,26113.857421875,0.19779109954833984,-0.8534136414527893,-0.4822486937046051,0.6014569997787476,-0.28281641006469727,0.7471708655357361,1.1120017766952515,1.1120017766952515,1.1120017766952515
rock_large,0,-24663.591796875,16217.54296875,25324.931640625,-0.5786615014076233,0.45900216698646545,-0.6741423606872559,0.10368727147579193,-0.7784808874130249,-0.6190447211265564,1.0884464979171753,1.0884464979171753,1.0884464979171753
rock_large,0,-29831.888671875,16830.966796875,25594.736328125,0.3826459050178528,0.32568761706352234,0.8645865321159363,0.06096058338880539,0.9248663783073425,-0.3753746449947357,1.0478107929229736,1.0478107929229736,1.0478107929229736
rock_large,0,-19907.146484375,19897.83984375,24035.396484375,0.6160900592803955,-0.740220844745636,-0.26926979422569275,0.27441728115081787,-0.11872908473014832,0.9542528986930847,1.0966349840164185,1.0966349840164185,1.0966349840164185

このデータを使って、ゲームエンジン上でアセットを配置することになります。

つづく・・。

タイトルとURLをコピーしました