Dynamic Material Instanceを使う

前回、UE4のマテリアルの解説とマテリアルインスタンスを使用したパラメータの固定をやってみました。

今回はここからもう少し話を先に進めてDynamic Material Instance (動的マテリアルインスタンス) をやってみようと思います。

UE4においてマテリアルはマテリアルのロジックを記述するための代物といえます。

この数値とこの数値をどう計算するか、とか、どの結果をどこに入力するか、といった部分をマテリアルで記述します。

マテリアルの変更はシェーダのコンパイルが走るため、決して軽くはありません。

そこでマテリアルインスタンスが登場しました。

マテリアルのロジックではなく、数値や色、テクスチャといったパラメータをマテリアル側で変更可能な状態とし、そのパラメータだけを変更して固定するのがマテリアルインスタンスでした。

ロジックは同じだけどテクスチャだけが違う2つのメッシュが存在する場合、テクスチャだけが変更されているマテリアルインスタンスを2つ作ってそれぞれのメッシュに設定してやるのが基本です。

しかし、これだけでは対応できない場面というものも存在します。

例えば、こんな場面を想像してください。

3Dモデルのクリボーが2体存在します。どちらも同じマテリアル、同じメッシュが使われている全く同じものですが、Actorのインスタンスは別に2つ存在しています。

このクリボーはファイヤーボール1発では倒せず、何発か撃ち込んで倒すことができます。

ファイヤーボールが当たるとクリボーは白く点滅します。ヒットフラッシュとかダメージフラッシュとか呼ばれる手法ですね。

もちろん2体のクリボーは別々のActorなのでダメージ処理も別々です。片方がダメージを受けてももう片方はダメージを受けていなかったりします。

これをマテリアルインスタンスで対応しようとすると非常に面倒なことになります。

マテリアルインスタンスはアセットに1つだけ存在するものなので、これを変更してしまうとすべてのクリボーがフラッシュしてしまいます。

対応するためにはマテリアルインスタンスをActorの数だけ作るという方法もあります。

しかし、Actorの数があらかじめ決まっているのならともかく、そうでない場合は数えきれないくらいのマテリアルインスタンスを作ることになってしまいます。

そこで登場するのが今回の動的マテリアルインスタンスです。

動的マテリアルインスタンスはメッシュアセットに設定するものではなく、ActorのコンストラクションスクリプトなどBPかC++で設定します。

特にコンストラクションスクリプトで生成できるようであれば話は簡単ですが、今回はLevelBPのBeginPlayイベントで特定Actorのみ生成という形をとりました。

本来はこのような使い方はしないと思います。

では、実装方法を見ていきましょう。

まず、いつも通りにBlankプロジェクトを作成してイスとテーブルが表示されるところまで進みましょう。

今回はマテリアルを一から作成しませんので適当なマテリアルをコピーして編集することにします。

コンテンツブラウザの /Game/Materials フォルダ内の "M_Brick_Clay_New" を右クリックし、[Create Copy] を選択しましょう。

コピーが作成されたら名前を適当に設定し、ダブルクリックしてマテリアル編集ウィンドウを表示します。

さて、今回はこのマテリアルを雨が降って濡れた状態に動的に変更できるようにします。

濡れた素材の動的変更については『Remember Me』を制作したDONTNODの技術者によるブログを参考にさせていただきました。

http://seblagarde.wordpress.com/2013/04/14/water-drop-3b-physically-based-wet-surfaces/

本格的な実装ではなく、ゲーム向けに簡略化された実装を用いています。

基本的な考え方としては、水に濡れたマテリアルはアルベドが暗くなり、ラフネスが小さく(グロッシネスが大きく)なります。

リフレクタンスは変化しない、というのが基本理論らしいのですが、濡れていることを強調するために今回はスペキュラも利用しています。

このマテリアルに追加するパラメータは"Porosity"という濡れやすさのパラメータと、"WetLevel"という濡れてる度合いを示すパラメータとなります。

今回はどちらも単一の数値を用いていますが、Porosityについてはテクスチャを用いて細かな違いを出していくべきでしょう。

実際、前述のブログではPorosityはテクスチャから取得しています。

WetLevelは雨が降る前は0.0で、雨が降り始めたら1.0に向けて大きくなっていきます。

乾く場合は逆に0.0に向けて小さくしていきます。

では、コピーしたマテリアルを次のように編集してください。(クリックで拡大)

ue074.jpg 

(a)と書かれているノードが元マテリアルのBase Colorへの入力で、(b)と書かれているノードがRoughnessへの入力です。

変更しているのはこれ以降の部分だけですので、この画像より左の部分は何も手をつけなくてOKです。

若干わかりにくいかもしれませんが、基本的には前述ブログのシェーダコードをUE4のマテリアルで表現しています。

スペキュラへの入力は私が独自に行っています。

マテリアルの編集が終わったらこのマテリアルからインスタンスを作成します。

編集したマテリアルをコンテンツブラウザで右クリックし、[Create Material Instance] を選択するとこのマテリアルをベースとしたマテリアルインスタンスが生成されます。

適当に名前をつけたらこのインスタンスをテーブル等を乗せている床(Floorという名前のActor)に設定しましょう。

この状態でインスタンスのPorosityやWetLevelをいじればどのようにマテリアルが変化していくかわかるはずです。

BlueprintではWetLevelしか変更しない予定ですので、Porosityは適当な数値を入れておいてください。

1.0に近いほど濡れやすくなるので、わかりやすさのために1.0にしておきましょうか。

ちなみに、Porosityの値は1.0までにしておく方がよいでしょう。それより大きな値はかなり不自然になります。

同様にWetLevelも1.0くらいまでに抑えておく方がよいですが、こちらは2.0くらいまでは許容範囲かと思います。

まあ、1.0を超えてからの変化はちょっと胡散臭くなってしまうのですが…

次にマテリアルを設定した床Actorをコピーしてもう1つ作っておきましょう。

これは、同一メッシュ・同一マテリアルにもかかわらず動的マテリアルインスタンスによって片方だけが変化するという状況を確認するためです。

そして、適当な場所にTrigger Volumeを2つ作成してください。床Actorが見やすい位置がいいですね。

このTrigger VolumeにPlayer Pawnが接触すると濡れたり乾いたりするようにします。

では、LevelBPを開いてください。

まずは2つのFloatの変数を追加します。"WetLevel" と "WetOrDry" というパラメータです。

ue075.png

追加後にコンパイルし、それから初期値を両方とも 0.0 にしてください。

WetOrDryはWetLevelへの加算値を指定します。0.0 なら変化なし、正の数なら濡れていき、負の数なら乾いていきます。

先に作成したTrigger VolumeにPlayer Pawnが接触したらこの変数に数値を設定します。

ue076.png

BeginPlayイベントをつかまえて、ここで動的マテリアルインスタンスを設定します。

これはBPのコンストラクションスクリプトでも設定できますが、基本的にはそちらでやるようにした方がよいでしょう。

ue077.jpg

画像はクリックで拡大します。

動的マテリアルインスタンスの生成はStaticMeshComponentの "Create Dynamic Material Instance" を用います。

TargetはStaticMeshComponentで、Element Indexは動的マテリアルインスタンスを生成したい要素番号を指定します。

今回はマテリアルが1つしかないので、0番固定で問題ありません。

Source Materialは動的マテリアルインスタンスの元になるマテリアルを指定します。

マテリアルインスタンスも利用できるので、先ほど作成したマテリアルインスタンスを指定します。

こうすることで、ゲームプレイ開始時に床Actorに動的マテリアルインスタンスが適用されることになります。

最後にTickイベントをつかまえて、設定されたWetOrDryのパラメータをWetLevelに加算していきます。

ue078.jpg

画像はクリックで拡大します。

最初の方はWetLevelへのWetOrDry変数の適用です。WetLevelは0.0~1.5まで変化するようにしています。

WetLevelを変更した後、床ActorのStaticMeshComponentで要素番号0番のマテリアルを動的マテリアルインスタンスにキャストします。

そして、この動的マテリアルインスタンスの機能である "Set Scalar Parameter Value" を追加します。

これが動的マテリアルインスタンスのパラメータを変化させる命令です。

Targetは動的マテリアルインスタンスValueにはWetLevelを入力します。

Parameter Nameはパラメータ名ですが、ここには変化させたいマテリアルパラメータを直接指定します。

今回は "WetLevel" というパラメータを変化させるので、”WetLevel"と入力しましょう。

これでBPの作成は完了です。

ゲームを開始し、Trigger Volumeに接触してみましょう。床が濡れたり乾いたりするはずです。

しかも、変化があるのは一部のActorだけで、同じものをコピーして作成した床は変化しないはずです。

ue079.jpg

最後に水濡れマテリアルの調整について。

先にも説明しましたが、Porosityについてはテクスチャを用いるべきでしょう。

今回のようなレンガのマテリアルでも石畳のようなものでもすべての場所が一律の浸透性を持っているというのは考えづらいです。

石畳の場合、石と石の間は砂や土でしょうから、石の部分とは別のPorosityを持っているべきです。

WetLevelをテクスチャ化するのは可能ではあるのですがゲームに実装するのは難しいと思います。

ライトマップのようにテクスチャアトラスを用い、カラーや法線マップとは別のUVで張り付ける必要があるでしょう。

しかし、これを用いると、屋根のある部分は濡れない(もしくは濡れにくい)、また、日の当たる部分は乾きやすいというような調整ができるようになります。

オープンワールドなゲームの場合はWet Map(とでも言いましょうか?)の解像度などに問題が出てきそうです。

デモのような映像作品であれば目立つ部分だけWet Mapを使うとかの選択は出来そうですね。

前回で解説しましたが、UE4はベースカラーとメタリックパラメータによってアルベドとリフレクタンスを求めています。

しかしながら、元のブログではリフレクタンスは変化させないのが基本でした。

今回のサンプルではメタリックパラメータが0、つまりベースカラー=アルベドだったのでベースカラーを変更するだけで対応できました。

これが金属マテリアルの場合は、ベースカラーを落とすとリフレクタンスも落ちることになってしまいます。

これを避けるためにはマテリアルごとに調整をしてやる必要があるわけですが、すべての調整が難しいのであればメタリック1.0とメタリック0.0、それと中間の値をいくつかという状態で計算方法を変えてやるべきでしょう。

GBufferに格納するパラメータやライティング計算を簡単に変更できるのであればGBufferにPorosityを格納することもできるのでしょうけど、UE4でそれをやろうとするとかなり大がかりな修正になるかと思います。

最後に法線マップですが、WetLevelに応じて元の法線マップと水用の法線マップをブレンドしてやるといいかもしれません。

完全に水の法線マップになるようにはせず、元の法線マップを残すようにした方がいいんじゃないでしょうか。

完全な水たまりなら元の法線マップは無視してもよさそうですがね…

というわけで今回はここまで。