チュートリアル 3
ワールドに3Dモデルを読み込む
ワールドへオブジェクト(3Dモデル)を配置するために、まずは3Dモデルをワールドに読み込ませる方法を学びましょう。TransportXが対応している3Dモデルの仕様についても解説しています。
3Dモデルを用意する
まずは手元に3Dモデルがないと話が始まりません。MetasequoiaやBlenderなどのモデリングソフトで3Dモデルを作成する必要があります。 3Dモデリング技術の解説は他のWebサイトへ委ね、本チュートリアルではサンプルワールドの3Dモデルを流用することとします。
参考:対応している3Dモデルの仕様
参考までに、TransportXが対応している3Dモデルの仕様についてまとめておきます。
TransportXは物理ベースレンダリング(PBR)により3Dモデルを描画するため、材質はPBR準拠のパラメータで設定してください。以下のものに対応しています。
- 定数項およびテクスチャを指定可能……基本色、金属感、粗さ、自己照明
- テクスチャを指定可能……法線、遮蔽
PBRについては、Metasequoia 4の機能紹介記事が分かりやすいのでおすすめです。
なお、従来のPhongシェーダ用パラメータ(拡散光、反射光、周囲光、鏡面反射などを指定する方式)も読み込むことはできますが、 PBR用のパラメータに自動で変換されるため、希望通りのレンダリング結果を得られない可能性があります。 あくまでも.obj形式や.x形式など、歴史がある形式の3Dモデルをそのまま読み込めるようにするための機能とお考えください。
また、TransportXでは3Dモデルの読込にAssimpを使用しているため、 基本的な形式の3Dモデルであればほとんどを読み込むことができます(参考:対応形式一覧)。 しかし、特段の事情がなければ.glb形式または.gltf形式とすることを推奨します。 これらは読込にAssimpではなくSharpGLTFを使用し、高速、高品質、かつ安定して読み込めるように最適化を施しています。
なお、TransportX公式のサンプルワールドに同梱している3Dモデルは全て.glb形式となっています。
必要な3Dモデルを複製してくる
さて、チュートリアルワールドの制作に戻りましょう。
今回は基本的なオブジェクトとして、背景、地面、そしてバス停標柱の3つをワールド上に設置してみることとします。
サンプルワールドの3Dモデルが格納されているフォルダ(samples\BasicSample\Models)へ移動し、次のフォルダを丸ごとコピーしてください。
BackgroundBusStopGrass
コピー先の位置に制約はありませんが、ワールドのフォルダ(MyFirstWorld など)直下に Models フォルダなどを作成し、
その中へ配置すると良いでしょう(例:~\MyFirstWorld\Models\Background、~\MyFirstWorld\Models\BusStop、…)。
モデルリストの仕様
次に、コピーしてきた3Dモデルをワールドから読み込めるようにするため、「モデルリスト」と呼ばれるファイルを用意する必要があります。 モデルリストはその名の通り、読み込みたい3Dモデルを一覧にしたファイルです。 ワールドファイルからモデルを参照するときに使うキー(識別子)、モデルのパスに加え、物理判定をどのように生成するかもここで指定します。
実際に作成する前に、例としてサンプルワールドのモデルリストを見てみましょう。samples\BasicSample\Models.txt をVSCodeやメモ帳などのテキストエディタで開いてください。
中身は次のようになっているはずです。
Background Models\Background\Background.glb $NonCollision
Grass Models\Grass\Grass.glb $OpenModel(0.6, 1, 30, 1)
Road1_1 Models\Road1\1.glb $OpenModel("Models\\Road1\\Collision.glb", 0.9, 1, 30, 1)
Road1_2 Models\Road1\2.glb $OpenModel("Models\\Road1\\Collision.glb", 0.9, 1, 30, 1)
Road1_3 Models\Road1\3.glb $OpenModel("Models\\Road1\\Collision.glb", 0.9, 1, 30, 1)
Road1_Sidewalk Models\Road1\Sidewalk.glb $OpenModel(0.9, 1, 30, 1)
Road1_10m Models\Road1\10_1.glb $OpenModel("Models\\Road1\\Collision10.glb", 0.9, 1, 30, 1)
RoadDeadEnd1 Models\RoadDeadEnd1\Model.glb $OpenModel(0.9, 1, 30, 1)
RoadTerminal1 Models\RoadTerminal1\Model.glb $OpenModel(0.9, 1, 30, 1)
BusStop Models\BusStop\Model.glb $ClosedModel
Signal_L Models\Signal\L.glb $ClosedModel
Signal_L_CarRed Models\Signal\L_CarRed.glb $NonCollision
Signal_L_CarYellow Models\Signal\L_CarYellow.glb $NonCollision
Signal_L_CarGreen Models\Signal\L_CarGreen.glb $NonCollision
Car Models\Traffic\Car.glb $ClosedModel
Car_BlinkerL Models\Traffic\Car_BlinkerL.glb $NonCollision
Car_BlinkerR Models\Traffic\Car_BlinkerR.glb $NonCollision
Car_Brake Models\Traffic\Car_Brake.glb $NonCollision
Pedestrian Models\Traffic\Pedestrian.glb $ClosedModel
レイアウトが崩れていて少し読みづらいかもしれませんが、例えば1行目だけを抜き出すと次のように記述されています。
Background Models\Background\Background.glb $NonCollision
Background、Models\Background\Background.glb、$NonCollision の3つの要素があり、これらの間はタブで区切られている、という構造になっています。各要素について解説していきます。
1つ目の要素:キー(例:Background)
1つ目の要素は「キー」と呼ばれるもので、ワールドファイルから3Dモデルを参照する際にはこの文字列を介して指定する仕様となっています。 キーは一意(重複がない)である必要がある他、大文字/小文字、全角/半角は区別されます。
2つ目の要素:ファイルパス(例:Models\Background\Background.glb)
2つ目の要素には3Dモデルファイルへの相対パスを指定します。
今回の例ではモデルリスト samples\BasicSample\Models.txt にて Models\Background\Background.glb と記述されているので、
samples\BasicSample\Models\Background\Background.glb が読み込まれることとなります。
3つ目の要素:物理モデル(例:$NonCollision)
3つ目の要素には衝突判定用の物理モデルをどのように作成するかをコマンドで指定します。次の通り、様々なコマンドがサポートされています。
$NonCollision または省略
今回の例がこちらです。この場合、物理モデルは作成されず、この3Dモデルに車両などが衝突してもそのまますり抜けるようになります。
$BoundingBox
$BoundingBox(a, b, c, d)$BoundingBox
物理モデルとして軸平行境界ボックス(AABB)を使用します。簡単に言えば、3Dモデルを包む最小の直方体で衝突判定を行います。
$NonCollision 以外では最も軽量な物理モデルのため、3Dモデルの形状がシンプルな場合や、大雑把な衝突判定さえできれば良い場合はこちらを推奨します。
a、b、c、d には物理モデルの材質のパラメータを指定します。
aは摩擦係数です。0の場合は摩擦が全くなく滑り続け、大きな値にするほど滑りにくくなります。bは物体同士が衝突した際、反発してめり込みを解消しようとする回復速度 [m/s] の上限値です。cは衝突時の反発の硬さ(剛性)を決めるバネの振動数 [/s] で、値が大きいほど硬い材質として振る舞います。下限値は1です。dは反発時の振動を抑えるバネの減衰比です。1に設定すると臨界減衰となり、衝突後に不要な振動を起こさず素早く静止状態に収束します。
例えば $BoundingBox(0.9, 1, 30, 1) なら、a(摩擦係数)が0.9、b(上限回復速度)が1 m/s、c(バネ振動数)が30 /s、d(バネ減衰比)が1となります。
単に $BoundingBox と記述するとパラメータ省略版を使用でき、a が1、b が+∞、c が30 /s、d が1となります。
各パラメータの意味がよく分からない場合は、省略版を使用しておけば無難な結果が得られます。
$ConvexHull
$ConvexHull(a, b, c, d)$ConvexHull
物理モデルとして凸包を使用します。
凸包とは、簡単に言うと3Dモデルを「ラップでピンと張って包み込んだような立体」のことです。
モデルの窪んでいる部分は平らに塞がってしまいますが、単なる四角い箱($BoundingBox)でモデル全体を囲むよりも本来の形にフィットするため、比較的軽量ながらもより自然な衝突判定を作れるのが特長です。
a、b、c、d の指定方法は $BoundingBox と同様です。
$ClosedModel
$ClosedModel(a, b, c, d)$ClosedModel$ClosedModel(path, a, b, c, d)$ClosedModel(path)
見た目用の3Dモデルの形をそのまま衝突判定として使用します。$BoundingBox(箱)や $ConvexHull(凸包)とは違い、元のモデルの凹凸を完璧に再現できるのが特長です。
この $ClosedModel は、モデルの中身が完全に詰まった塊として扱われます。形を正確に再現できる分、PCへの負担は大きくなり、計算処理は重くなります。
なお path を指定することで、見た目用とは別の「衝突判定専用の3Dモデル」を読み込ませることも可能です。
a、b、c、d の指定方法は $BoundingBox と同様です。
$OpenModel
$OpenModel(a, b, c, d)$OpenModel$OpenModel(path, a, b, c, d)$OpenModel(path)
前項の $ClosedModel と同じく、3Dモデルの形をそのまま衝突判定として使用します。path を指定して別のモデルを読み込める点も同様です。
$ClosedModel はモデルの中身が完全に詰まった塊として扱いますが、こちらはモデルの面で囲まれた内側は空洞となっているものとして扱います。
$ClosedModel も重量級ですが、こちらはそれ以上に計算負荷が高いため、複雑な形状のモデルに対して適用するとパフォーマンスが大幅に低下する可能性があります。
a、b、c、d の指定方法は $BoundingBox と同様です。
$LH
このコマンドは他と性質が異なり、読み込む3Dモデルが初めから「左手系」の座標系で定義されていることを指定するためのものです。
モデルファイルが座標データをどのように保存しているかは、ファイル形式(.glb、.obj、.xなど)や作成に使用した3Dソフトによって異なり、そうした出力元の仕様の違いを吸収するために使用するコマンドとなります。
.obj形式や.x形式、.fbx形式などでは指定が必要になる場合があります。
よく分からない場合は、まずはこのコマンドを指定せずに読み込んでみてください。その結果、モデルの向きや裏表が反転して描画されてしまった場合にのみこの $LH を追加で指定する、という考え方で問題ありません。
指定する際は、$BoundingBox$LH のように、物理モデル形状のコマンドの末尾に続ける形で記述してください。
モデルリストを作る
それでは、現在作成しているワールド用のモデルリストを実際に準備していきましょう。
ワールドのフォルダ(MyFirstWorld など)直下にファイル Models.txt を新規作成し、VSCodeやメモ帳などのテキストエディタで開いてください。
今回サンプルワールドからコピーしてきた3Dモデルは次の3つですから、これを参考に記述していきます。
Models\Background\Background.glbModels\Grass\Grass.glbModels\BusStop\Model.glb
Models\Background\Background.glb
背景用のモデルで、空が描かれています。背景に車両などが衝突することはまずありませんから、物理モデルは作成しないで良いでしょう。
Models\Grass\Grass.glb
地面のモデルで、草原のテクスチャが貼り付けられている他、法線マップによって凹凸が表現されています。大きさは250m四方で、ちょうどチャンクの大きさに一致するようになっています。
面は正方形1つの非常に単純な形状で、かつ衝突判定は正確に行わないと車両が奈落へ落ちてしまいますから、物理モデルは $OpenModel が良いでしょう。
また、草原はアスファルトなどと比較して滑りやすいですから摩擦係数を下げたり、柔らかさを表現するためにバネの振動数を下げたりするのも良いでしょう。
Models\BusStop\Model.glb
バス停の標柱のモデルです。入り組んだ形状で、かつバス車両はすぐ近くまで近づくことが想定されますから、$BoundingBox は「何もないはずの場所で車体が跳ね返った」という現象が起こりかねません。
$ConvexHull と $ClosedModel のどちらにするかは悩みどころですが、処理の軽さを優先するなら $ConvexHull、判定の正確さを優先するなら $ClosedModel が良いでしょう。
なお、サンプルワールドでは $ClosedModel を採用しています。
作例
以上の議論を踏まえて、モデルリストを記述してみましょう。次のようになるはずです。
Background Models\Background\Background.glb $NonCollision
Grass Models\Grass\Grass.glb $OpenModel(0.6, 1, 15, 1)
BusStop Models\BusStop\Model.glb $ClosedModel
ワールドファイルからモデルリストを読み込む
最後に、作成したモデルリストが読み込まれるように、ワールドファイルへスクリプトを追加しましょう。次のように記述してください。
Models.LoadList("Models.txt");
前章と同様に bin\TransportX.Player.exe を実行し、ワールドを動作確認してみてください。モデルリストに何らかのミスがあれば、エラーメッセージが表示されます。
特に何も表示されることなく灰色の画面が表示されれば、モデルリストは正しく作成できたということです。
付録
付録として、この章の完成データを配布しています。どうしても動かないときなどの参考にお使いください。