げーむ開発徒然日記~怠惰のために勤勉~

Unreal Engineや3DCG制作について学んだことを記事にしていきます

【UE5】Proceduralなナンバープレートのマテリアルを組んでみる

目次

0.はじめに

 今回組んでみたのは、様々な組み合わせの番号や用途表示などに対応可能なナンバープレートのマテリアルです・・・というのはあくまで具体例で、実際のところやっていることを抽象的に言えば、

 「あるテクスチャの任意の範囲だけ」「任意のモデルの任意のUV座標範囲」合成するというものです。

 イメージはこんな感じ↓

 今回のナンバープレートの場合で言うと、何も書かれていない"更地"のナンバープレートに、数字や文字を合成していくイメージですね。

 この方法は、色々なことに使えると思います。例えば、

【例1】UV空間のほんの一部にしか発光する部分を持たないオブジェクトで、わざわざ4K解像度のベースカラーテクスチャのAチャンネルをエミッシブのマスクに使いたくない場合

 ⇒今回の手法を使えば必要最低限のサイズ(64x64サイズなど)のエミッシブマスクを用意すればよくなります。

【例2】学校の教室のプレートや看板など、ベース部分を使いまわしたい場合

 ⇒今回紹介するナンバープレートと同じパターンです。

 

 きっかけはGhostwire: Tokyoで車が様々なナンバーだったのを見かけたことです。最初はテキストレンダーコンポーネントでも仕込んであるかと思ったんですが、ちゃんと文字がプレート表面から盛り上がっている=ノーマル情報があるんですよね。ただ、Ghostwire: Tokyoの車で使われている数字は完全にランダムではなく何らかの規則性はありそうな気がします。

1.実装例

 今回使用したUEはバージョン5.1.1です。

1-1.完成形

 以下の動画をご覧ください。

 付与した機能は色々ありますが、本記事で取り上げるのは以下の2つです。

①ナンバー(10-00~99-99)が、ランダム変化する

1~3桁ナンバーには対応させていないのでご了承ください。そこまで難しいことではないですが、対応させるには別途改変する必要があります。

②手動でも設定可能にする

⇒今回は、①②を実現するため最終的にBP Actorにします。

1-2.用意するもの

単純なアトラス化が施されたテクスチャ(合成したいもの)

※もちろんアトラス化されていないものでも大丈夫です(補足参照)。

 今回は、R+Gチャンネルに法線情報Bチャンネルにマスク情報を持つテクスチャを用意しました(上から順にR+G+B,R+G,B)。

※ブログ用に作ったものなのですごく雑ですし、サイズも圧縮のことも何も考えていません。ナンバーって特殊なフォントだなあ…。

 単純なアトラス化と言っているのは「要素が等間隔に分割されているもの」を指しており、上のテクスチャはどちらも各数字が等間隔に配置されています。

Tips:

・必要な解像度は使用する自動車モデルに使われているテクスチャの解像度に合うようにすることを推奨します。

・3桁ナンバーなどに対応させる場合は「・」もテクスチャに含めないといけません。

1-3.マテリアルの作成

最初はBチャンネルのマスク情報を使って合成するための準備段階から始めるので留意ください!!

1-3-1.テクスチャの任意の範囲を切り取る

 まず最初に、冒頭で述べた

「あるテクスチャの任意の範囲だけ」「任意のモデルの任意のUV座標範囲」合成する

の、「あるテクスチャ(この例では、合成に使うテクスチャ)の任意の範囲」を切り取ることから始めます。

 エンジンに標準で用意されているFlipBookというマテリアル関数を使うだけでこれを実現できます。以下(紫色でコメントされた部分)をご覧ください。

 0123456789から3だけが切り取られています。上の画像では、Numberというパラメータに与えられた数字が切り取られるようにしています。

Tips:FlipBookは本来的には、エフェクトに使う連番テクスチャを入力して、Animation Phaseの値を変化させることでパラパラ漫画のようにアニメーションさせることに使うことが多いと思います。

1-3-2.任意のUV座標範囲を切り取る

 次は、

「あるテクスチャの任意の範囲だけ」「任意のモデルの任意のUV座標範囲」合成する

の、「任意のモデルの任意のUV座標範囲」を切り取ることを行います。

 こちらもエンジンに標準で用意されているTextureCroppingというマテリアル関数を使うだけでこれを実現できます。以下(茶色でコメントされた部分)をご覧ください。

 UpperLeftCorner(V2)およびLowerRightCorner(V2)という入力を与える必要があります。これは切り取りたいUV範囲の正規化座標となります。

1-3-3.ナンバーを合成したい場所に合わせてポジショニングとスケーリングする

 ここまでできたら、先ほどの1-3-1で得られた「切り取られたナンバー」を、合成したい範囲の大きさに合わせてポジショニングおよびスケーリングしていきます。

 厳密に言えば、「タイリングをした後に、必要な部分のみを残す」といったことを行います。

 以下のように1-3-2で使ったTextureCroppingのCrop UVsという出力を、FlipBookのUVsに与えてやると、FlipBookの出力にタイリングをかけることができます。

 次に、タイリングで作られる不要部分を除去するためには、Crop Maskという出力が、切り取られた範囲のマスク情報として使えます。

 それでは、以下のようにFlipBook(タイリングされたナンバー)から、不要部分をマスクで除去します。なお、途中でBチャンネルをマスクしているのは、Bチャンネルには文字部分のマスク情報があるため、Crop Maskと乗算することで、CropMaskにある範囲内かつ数字のみのマスク情報というのを作ることができるためです。最終出力はMaskedNumberというNamed Rerouteに格納しました。この情報を後々使いまわします。

1-3-4.カラー情報を合成する

 ようやくカラーや法線を合成する準備が整いましたので、まずはカラーから乗せていきますが、これは簡単です。ナンバープレートのベースカラーテクスチャに対して、先ほどのマスク情報を使ってLerpで合成するだけです。

1-3-5.法線情報を合成する

 まずは、合成する法線情報を作ります。以下のようにマテリアルを組みます。

 1-2で説明したように、今回用意したテクスチャはR+Gチャンネルに法線情報があるだけであってBチャンネルを再構築する必要があるため、DeriveNormalZを使って法線情報を再構築します。その後、ナンバー以外の部分はFlattenNormalによって平坦化し、強度調整用のFlattenNormalも別途配置します。

 あとは、BlendAngleCorrectedNormalsを使って、ナンバープレートのベースの法線マップに対して合成をかけます。

1-3-6.4桁ナンバーにする

 4桁ナンバーにするにあたって、まずはマテリアル関数化し、コンパクトにしました。

 マテリアル関数内の、TextureCroppingのところも少し変更しています。

 あとは、この関数を4桁分=4つ配置して合成します。

 NumberSizeのパラメータのみ使いまわしています。さすがに若干コストが高くなります。とりあえずこれで一通りは完了です!あとはランダム化に対応していきましょう。

1-4.ランダム化への対応

1-4-1.Custom Primitive Data

 Material Instance Dynamics(MID)を使っても可能なのですが、今回はCustom Primitive Data(CPD)を使います。

 MIDではスカラーパラメータとベクターパラメータに加えて各種テクスチャパラメータなどを動的に変更可能なのに対し、CPDではスカラーパラメータとベクターパラメータしか扱えませんが、今回のケースではCPDで十分に対応できます。

 また、CPDのほうがドローコールを節約するうえで特に有効な手段ですので、CPDで対応できるものは積極的にCPDを利用したほうがよいです。これについては公式ドキュメントを一読しておくことを推奨します。

 マテリアル内のNumber_1~Number_4に、Custom Primitive Dataを使うように設定します。

 パラメータを選択した状態で、詳細タブからUse Custom Primitive Dataにチェックを入れて、Primitive Data Indexを指定します。

 今回は、Number_1~Number_4の順に、Primitive Data Indexに1~4を割り当てました。

1-4-2.Actorを継承したBPクラスを作成

 今回は、Actorクラスを継承したBP_LicensePlateMatTestを作成しました。この中のBP処理にてランダム化対応を実装していきます。

 あとはConstruction Scriptにて、それぞれのCustom Primitive Dataに0~9のランダムな値を与えてやるだけです。また、4桁の場合、先頭に0はこないので、先頭のみ1~9となるようにしています。

 ここまででランダムナンバーは終わりです!お疲れ様でした。次はオプション的なものですが、手動設定にも対応させていきます。

1-5.手動設定への対応

 すぐにできます。それぞれのナンバーに対応する変数と、手動で設定するかどうかを決めるbool変数を用意します(目玉マークが開いているようにすること)

 あとは以下のように、分岐処理を組んであげるだけです。

 レベル上で編集してみると、手動設定が機能していることを確認できます。

 おしまい!

補足

アトラス化されていないテクスチャを使用する場合

 切り取る必要がないということなので、FlipBookを使わずにTextureSampleを使用するだけです。

BP Actorにせず、単なるStaticMeshActorで実現したい

 ナンバーランダム化の柔軟性が落ち、手動設定機能も失われますが、以下のようにすればできます。この例では、シェーダー内でナンバーを自動で決めることによってBPで数値を変更しなくてもよくなっています。ただし、4桁全てをバラバラにするためには更なる工夫が必要です。

シェーダーコストを下げたい

以下のような対応で、多少は下がると思います。

・標準のFlipBookでは冗長なので、必要な計算だけを残したカスタムのマテリアル関数を作成しましょう。

・標準のTextureCroppingでは冗長なので、必要な計算だけを残したカスタムのマテリアル関数を作成しましょう。

・合成する部分の数が多いほどTextureSampleが増えたりとそれなりにコストも増加していきます。ナンバープレートの場合は、ナンバー4桁=4つ以上は合成する部分が必要になりますが、それ以外はある程度妥協しましょう。FlipBook内でも、TextureCropping内でもTextureSampleが使われているので注意。