インテリアマッピングの基礎

本記事では、スパイダーマンで使われて再脚光を浴びているインテリアマッピングの手法について解説してみたいと思います。

インテリアマッピングとは文字通りビルの壁面などに格子状に表示される室内の様子をモデルではなくマテリアルで描画しようというもので、壁、床、天井ごとにテクスチャを用意して、それを実際に壁があるかのようにマテリアル上でレイキャスト計算してピクセルを描画します。

技術自体は結構昔からあるようで、UnrealEngine4でも2014年にフォーラムにStefanderさんが実装例を投稿してくださっています。

forums.unrealengine.com

f:id:nca03132:20181008155954p:plain

ただ投稿自体に解説記事がなくフォーラムのやりとりも英語なので、今回はこの実装について簡単な解説を書くことにしました。

実行環境をそろえていただくため、上記フォーラムの最初の書き込みからInteriorMapping.rarをダウンロード、展開しておいてください。

■使ってみるよ

InteriorMapping.rarを展開しInteriorMapping.uprojectを開くと

 

f:id:nca03132:20181008160348p:plain

とバージョンを聞かれるので自分が使っているバージョンを選んでください。本記事では4.20前提で進めていきます。

エディタが開くと、いつもの空マップなので基本のキューブをレベル上に配置し、そこにMaterials/M_Interiorをドラッグ&ドロップしてマテリアルを適用してください。

f:id:nca03132:20181008161403p:plain

これでマテリアルが適用されると・・・

f:id:nca03132:20181008161636p:plain

はい、できました。

テクスチャが変なところで切られてるとか、格子模様が部屋構成とあってないとか、細かい問題はありますが調整の範疇です。同梱されているInteriorMapping.mapのレベルを開くと、この辺がちゃんと調整された

f:id:nca03132:20181008162344p:plain

という絵面を見ることができます。

 手軽!簡単!便利!

■M_Interiorについて

では、ここで設定したM_Interiorというマテリアル、どうなっているのでしょうか。

ちょっと中を覗いてみたいと思います。

f:id:nca03132:20181008162933p:plain

ブログだと字がつぶれて見づらいと思うので、実機を見ながら読み進めていただけるとよいかと思います。

赤線で囲った部分は格子表示なので今回説明は省略します。

表示が気になる場合は、MF_InteriorMappingSimpleのWallColorをそのままベースカラーにつないでしまいましょう。

マテリアルのロジック自体は中央にあるMF_InteriorMappingSimpleに集約されているようで、パラメータを大量に与えています。

まずは、このパラメータ群について説明します。
以下の内容を参考に適当にいじってみて、見た目にどういう影響を与えるか実験してみるとよいでしょう。

Room Floor(T2d)

 床テクスチャの指定です

Room WallXY(T2d)

 側壁(YZ平面)テクスチャの指定です。

Room WallZY(T2d)

 奥壁(XZ平面)テクスチャの指定です。

Room Frequencies(V3)

直訳すると部屋の周波数・・なんですが意味がわかりませんね、具体的にはxyzそれぞれの軸方向に対して単位距離あたり何部屋作るかの指定となります。
UE4の単位距離は1cmで、そんな距離には1部屋も入らないので小数で指定します。
M_Interiorでは1mで1.5部屋配置させているため
 1m(=100cm)で1.5部屋→1cmだと0.015部屋
となり、数値は0.015が指定されています。

※1mで1.5部屋なので2部屋きっちり表示するには100cm*(2/1.5)=133.333..cmの辺が必要です。エディタ上に1mキューブを設置しM_InteriorMappingSimpleをスケールを1.33..するときっちり2x2x2部屋表示になります。
が、壁の座標が境界とばっちり重なってしまいZオーダーの問題でちらつくのでその辺は随時調整しましょう。

Tiling WallZY(V2)

奥壁(XZ平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
このサンプルでは1.5mで1タイルとしているので、数値の大きさは0.015となります。
 R:0.015(X方向のタイリング数値)
 G:-0.015(Z方向のタイリング数値,Z方向のUVとテクスチャの向きが逆なのでマイナス)
となります。

Tiling WallXY(V2)

側壁(ZY平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
奥壁と同様、数値の大きさは0.015です。
 R:0.015(Y方向のタイリング数値)
 G:-0.015(Z方向のタイリング数値,Z方向のUVとテクスチャの向きが逆なのでマイナス)
となります。

Tiling Ceiling/Floor(V2)

床・天井(XY平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
奥壁と同様、数値の大きさは0.015です。
 R:0.015(X方向のタイリング数値)
 G:-0.015(Y方向のタイリング数値)
となります。

Room Ceiling(T2d)

 天井テクスチャの指定です。

■MF_InteriorMappingSimpleの解説

さて、いよいよインテリアマッピングの本丸、MF_InteriorMappingSimpleの説明に入っていきたいと思います。
このマテリアル関数では、大きく分けて
 ・レイキャストで壁面(側壁(ZY平面)、奥壁(XZ平面)、天井・床)に対して衝突する座標の計算
 ・各壁面で一番最初に衝突する壁の特定
 ・各壁面でのUV計算
の3つです。

早速ノードを見ていきましょう。

・レイキャストによる壁面(XZ,ZY,XY)の衝突座標の計算

まずはレイキャストによる壁面衝突座標の計算です。

f:id:nca03132:20181008171346p:plain

この「Calculate walls and ray fractions」というコメントでくくられている部分で計算を行っており、描画するワールド座標、オブジェクトの中心座標、カメラ座標、部屋の周波数から、衝突点を算出しています。
ノードだけだと理解するのが難しいので図を描きました。

f:id:nca03132:20181015114047p:plain

・・・このご時世に手書きですいませんorz。
3次元だと説明しずらいのでxy平面の2次元について考えてみます。

この例ではCameraが原点、Objectがオブジェクトの原点、(字が汚くて見づらいですが)Tが描画座標としています。

①がオブジェクトの原点から描画座標へのベクトルです。

②はオブジェクト原点からカメラ座標へのベクトル

③はカメラ座標から描画座標へのベクトル

となっており、ノードの計算とも合致しています。

④はノードの動きをしっかり追わないとわからないですね。
まず①の続きから追っていくと、①の各軸ごとにRoomFrequenciesと掛け算を行っています。これで何がわかるのでしょうか?
RoomFrequenciesは単位距離当たりの部屋の数です。これにオブジェクト原点から描画座標へのベクトル(軸ごとの距離)をかけるわけですから、計算結果は軸ごとの部屋座標になります。x方向に何番目、y方向に何番目の部屋にいるか?なので、上図の例で方眼紙の1cm平方を1部屋の区切りとするとTの部屋座標は(4.2,4.5)ぐらいの位置になるかと思います(オブジェクトを原点としてx,y方向への話ですよ、念のため)。
次にFloorで小数点以下が切られます。上図の例だとFで(4,4)ですね。オブジェクト原点寄りの部屋の区切りポイントとなります。
その後、MF_Step3が足されます。・・・MF_Step3って何でしょうか?

f:id:nca03132:20181009153308p:plain

これはMF_Stepのノードです。MF_Step3はこれのベクター版なので、MF_Stepの動作を見れば内容は理解できるはずです。
MF_Stepの入力にxとaがあり、xがaより大きい場合に1を返し、それ以外は0を返す単純な関数です。
MF_Step3ノードを見てみると、入力aは(0,0,0)、入力xは③(カメラ座標から描画座標へのベクトル)が接続されているので③の各軸要素がプラスであれば1が返ります。
手書き図だと③はx軸方向にプラス方向、yはマイナス方向に伸びているのでMF_Step3の結果としては(1,0)となります(本当は結果は3軸ですが、手書き図は2次元なので、ここでは(1,0))
MF_Step3の結果と先ほどのFloorで切ったF点の部屋座標(4,4)を足し算するとS点の部屋座標(5,4)となります。
さてこのS点は何者なんでしょう?
実はS点はカメラから描画座標へのベクトルである③を伸ばしていった際、仮にRoomFrequenciesで分割された部屋が存在したとすればどこの壁にぶつかるか、奥の壁の部屋座標(図の青い波線部分)を計算したものとなります(これのためにF点を必要に応じてMF_Stepでカメラの奥行方向にずらしS点を生成した)。
S点の部屋座標をRoomFrequenciesで割れば、S点は通常の座標値に戻ります。
最後に⑤ですが、こちらは④/③の値となっています。
この数値の意味について考えてみましょう。。
⑤は当然x,y,z各要素ごとに③を何倍すると④になるか?という値になるのですが、ここで手書き図右下の③x(③のx成分)と④x(④のx成分)に注目してください。
 ⑤x=④x/③x
となっています。
これはx軸について注目すると③xに⑤xをかけると④xになるという極めて当たり前の話ですが、よく注意して考えると③ベクトル全体に⑤xをかけると、③ベクトルはy軸に平行な部屋の奥の壁の衝突点に到達することに気が付きます(③ベクトルの点線の先)。
同様に③ベクトルに⑤yをかけると③ベクトルが伸びていったときに衝突するx軸に平行な奥の壁の座標が求まります。⑤zも同様です。

これによって衝突座標の計算が可能となります。

■各壁面で一番最初に衝突する壁の特定

ここはとても簡単です。

f:id:nca03132:20181010011405p:plain

さきほど⑤を求めたので、⑤のx,y,z成分のうち一番小さいものが最初に当たる壁となります。あとは当たる壁からテクスチャサンプルしてくればOKです。
床・天井だけは③zの正負によって天井に当たっているか、床に当たっているか判定してテクスチャを分けています。

■各壁面でのUV計算

各テクスチャごとのサンプリングはほぼ内容は一緒なので、ここでは側壁(ZY平面)についてのみ解説します。

 

f:id:nca03132:20181010012707p:plain

まず③に⑤xをかけてカメラ位置を起点とした側壁の衝突点を求めます。
そこに②のオブジェクトからカメラへのベクトルを足します。
これにより計算結果は、オブジェクトを起点とした衝突点へのベクトルになります。
側壁(ZY)平面はx軸の値が固定で、y,z座標値がUVの計算対象となるためG,Bでマスクをかけます。そこにタイリング係数をかけます。タイリング係数は単位座標当たりのループ数なので、そのまま側壁のUV値となります。
最後に指定のテクスチャからサンプリングします。

奥壁、天井・床も同様に、上記の計算でサンプリング可能です(※天井・床のみ③zの正負で、テクスチャを変える必要があります)

 

■まとめ

以上で説明は終わりです。
把握しづらいところもあると思うので、最初は「???」となるかもしれませんが、ノードとにらめっこしてるうちに理解できるようになると思います。
あとはテクスチャを差し替えるなり、タイリング値を変えてループしてることに気づきづらいよう工夫をしたり、手前を外壁テクスチャでマスキングしたりすれば、クオリティはグンと上がるはずです。

■この後は?

今回ご紹介したのは本当に基礎の部分だけです。
本格的な運用としては、光源処理や法線パス、エミッシブ、インテリアの工夫などまだまだやれること満載です。
資料として

UDK | DevelopmentKitGemsInteriorMappingJP

これ張り付けておきます。
UE3用の資料でノードの流れが右から左なのでちょっと面喰いますが、基本的にマテリアルノードは似たものがあるのである程度流用はできると思います。
また、今回は試していませんがStefanderさんの実装には、MF_InteriorMappingSimpleの高機能版でMF_InteriorMappingというものも用意されているようです。こちらを使ってみてもいいかもしれませんね。

みなさんの反響があるようであれば、高機能版の解説も検討してみたいと思います。
また、なにかわからないこと質問などあれば、どしどしコメントしてください!