読者です 読者をやめる 読者になる 読者になる

テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】モバイル向けのライトマップTipsと、ライトマップを動的に更新するHack

Lightmap まとめ Unity Android iOS 最適化・デバッグ

Unity 5はリアルタイムな光源を基本としている節がありますが、モバイル上では依然としてパフォーマンスを稼ぐためライトマップをベイクする方が良いです。

今回はそのライトマップの色々なアプローチについて紹介します。

ライトを焼く

ライトを焼く基本的なアプローチは下の記事を参考にしてください。

tsubakit1.hateblo.jp

ライトマップを焼く時間を短縮する

ライトを焼くのに時間がかかる場合、Lightmap の Default Parameterの設定を「Default Very Low Resolusion」に設定します。

ベイクにかかる時間がかなり短縮されます。

http://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20151118/20151118235101.jpg

 

tsubakit1.hateblo.jp

 

Low Resolusionに設定することで間接光(オブジェクトの馴染ませや反射光の品質)の品質が下がりますが、ライトによる直接的な影のクォリティは余り変化しません。

f:id:tsubaki_t1:20160831230546j:plain

Emissiveなオブジェクトを光源として使用している場合、影の品質は結構悪くなります。その場合はEmissiveなオブジェクトの周辺だけパラメータを少し変更します。オブジェクト毎に設定する場合は、LightingのObjectで設定します。

f:id:tsubaki_t1:20160831224737j:plain

ライトマップの品質も下がるので、最終的にはクォリティを上げたほうが綺麗になります。

リアルタイムGIをOFFに

とりあえずライトは焼けますが、モバイルで使用する場合には幾つか注意するべき点があります。

リアルタイムGIはPCで見ると割と綺麗に見れる訳ですが、この処理はGPUをそこそこ使用するらしく、モバイルで動かす際はOFFに設定しといた方が良さそうです。

まずAmbient GIをBakedに設定します。この設定でAmbient LightのGIがライトマップに統合され、ライトマップだけでもGIライクな絵が出るようになります。ついでにAOがリアルタイムライトより綺麗に出ます。

で、リアルタイムのGIを行わないようにするため、Precumputed Realtime GIをOFFにします。

f:id:tsubaki_t1:20150905222704p:plain

なお、この設定の場合はRealtimeLightのBounceIntensityやマテリアルのEmissionがライトマップに焼かれます。だいぶ見た目が変わるので、もし設定している場合は注意する必要があります。

tsubakit1.hateblo.jp

重要度の低いライトマップの解像度は下げる

ライトマップの解像度です。Unityのライトマップは標準設定ではBaked Resolusionが40と設定されていますが、これは割と高い数値です。

これが画面目いっぱい近づくようなゲームなら兎も角、モバイルゲームではここまでカメラが近づくケースは…ゲームに寄りますが無いケースも多いはずです。

なので、Baked Resolusionを気にならないレベルまで下げます。そうすることでライトマップが要求する面積が下がりライトマップが減ります。

ついでに計算時間もガッツリ減ります。

f:id:tsubaki_t1:20150905215125p:plain

f:id:tsubaki_t1:20150905215134p:plain

f:id:tsubaki_t1:20150905215143p:plain

f:id:tsubaki_t1:20150905215151p:plain

とはいえライトマップが細かくあってほしいケースも当然あります。その場合はオブジェクトのライトマップ解像度を上げます。

LightmapでObjectを選択し、Scene In Lightmapを上げると、選択中のオブジェクトのライトマップが細かくなります。

下の画像は右のモデルだけ解像度を上げました。ライトマップ用のチェック模様の解像度が全然違う事が見て取れます。

f:id:tsubaki_t1:20150905215635p:plain

f:id:tsubaki_t1:20150905215711p:plain

これで背景や遠距離のモデルはライトマップの解像度を下げて、近いモデルはライトマップの解像度をややあげておくと、無駄の少ないライトマップ構造に出来ます。

f:id:tsubaki_t1:20150905220732p:plain

ライトマップにおける各メッシュの位置は、PreviewのBaked Intensityから確認することが出来ます。青く選択されているのがライトマップの占有範囲ですが、奥と手前では大分違うことが把握できます。

f:id:tsubaki_t1:20150905221029p:plain

f:id:tsubaki_t1:20150905221039p:plain

ここで気づくかもしれませんが、ライトマップの範囲がスカスカです。Unityはライトマップの内容がスカスカであってもライトマップの解像度を自動で落としてくれません。

そのため、こんな単純なシーンしかも圧縮をかけていても1024x1024のライトマップを消費してしまいます。

※この駄目な仕様はUnity 5.2で直るっぽいです。

f:id:tsubaki_t1:20150905221508p:plain

ライトマップが余りまくっている場合は、ライトマップのサイズを小さくします。これでライトマップの解像度を1024から256に変更する事で、256x2枚の解像度となり、ファイルサイズが2700kbから341kbまで小さくなりました。

f:id:tsubaki_t1:20150905221938p:plain

f:id:tsubaki_t1:20150905221829p:plain

但し、2枚になった事で描画負荷が若干上がります。アプリサイズを選ぶならキツキツの2枚が良いですが、負荷を考えると512の1枚の方がお得かもしれません。

 Directional ModeをNone-Directionalに設定

Unity 5よりノーマルやスペキュラーの表現をマップに焼き付けるオプションが追加されました。この処理は、今までできなかったベイクしたマップへのノーマル表現やスペキュラー表現を追加します。下のモデルは全てベイクしたモデルです。

Directional Specular。ノーマルマップによる凹凸とスペキュラーによる光沢が表現されています。

 f:id:tsubaki_t1:20150906212423p:plain

Directionalのみ。ノーマルマップによる凹凸が確認出来ます。

f:id:tsubaki_t1:20150906212439p:plain

Non-Directional。ライトの陰影のみを表現します。溝がなくなり、全体的にのっぺりとした絵になります。

f:id:tsubaki_t1:20150906212456p:plain

 

このDirectionalやSpecular-Directionalは綺麗に見える代わりにメモリやフィルレートを食います。具体的には、DirectionalはNon-Directionalの倍、Specular-Directionalは4倍のメモリを消費します。

必要が無ければDirectional ModeをNone-Directionalに設定しておく事で、ライトマップ系で使用するメモリの量を削減する効果が期待できます。

f:id:tsubaki_t1:20150906211834p:plain

もう一つ、ベイク時に生成されるLightmapSnapshotのファイルサイズが減ります。None-Directinal Modeを基準として、Directional Modeは倍、Directional Specularは3倍のファイルサイズに膨れ上がります。

Unity - マニュアル: Directional ライトマッピング

Lightprobeを置くポイントに関するTips

間接光をオブジェクトに伝える役割を持つUnity 5のライトプローブですが、リアルタイムGIを切っている場合は昔ながらの「ライトの影響をオブジェクトに伝える」役割に戻ります。

f:id:tsubaki_t1:20150906002315p:plain

ライトプローブについてを書く前に、少しライトプローブについてのおさらいです。LightProbeは以下のように動作します。

  • ライトマップの設定時に、ライトの情報を使って設定する
  • ライトマップの光の輝度や色・間接光を各プローブに格納する
  • 近くのProbeと周辺のProbeを元に、ダイナミックなオブジェクトに間接光やライトマップの色を与える
  • LightProbeの影響を受けるオブジェクトはMeshRendererのUseLightprobeがONになっているオブジェクト(Unity5で標準化)
  • 直近のProbe取得は、現在のProbeからノードを辿って追跡する。

ではLightProbeの置き方ですが、個人的にはこんな感じに配置しています。

  • 光の発生源に一つ
  • 光の色が変わるポイント(影がかかっている場所)に配置
  • 壁に配置
  • 出来るだけ間隔を広げる

f:id:tsubaki_t1:20150906002821p:plain

まず「光の発生源に一つ配置」する理由ですが、これは単純です。その地点が最も輝度が高いからです。

LightProbeは複数のProbe間で輝度や色を線形補完します。なので、本来であれば「輝度が高い所」と「輝度が低い所」の二つにLightProbeを置いてしまえば良いはずです。

なので、もっとも輝度が高い処に一つ配置し、残りを周辺の暗い部分に配置します。

f:id:tsubaki_t1:20150906003934p:plain

f:id:tsubaki_t1:20150906003943g:plain

PC向けであればこのポイントのみを考えれば良いのですが、モバイル向けではLightの代わりに使用するので、色が変わっているポイントに配置します。

ここで重要なのは光と影の切り替わりポイントや、輝度の変化がはっきりみえる処です。そういった境目にはLightProbeを配置します。球は全てLightProbeです。

LightProbe間の距離が短ければ短いほど、急激に色が変わります。

f:id:tsubaki_t1:20150906005457p:plain

f:id:tsubaki_t1:20150906005917g:plain

壁の付近に配置する理由は簡単です。壁から離れていると、壁にモデルが近づいた時に壁の向こうのLightProbeの影響を受けるケースがあるからです。

下の画像では、壁際にキャラクターを配置した際に壁の向こうのライトの影響をモロに受けてモデルが青くなってしまっています。

f:id:tsubaki_t1:20150906011536p:plain

f:id:tsubaki_t1:20150906011545p:plain

LightProbeが作る三角形を意識しつつLightProbeを壁沿いに配置して、期待通りにLightProbeを選択してもらうようにします。

壁の中にいる場合は、…気にしません。

f:id:tsubaki_t1:20150906011557p:plain

f:id:tsubaki_t1:20150906011604p:plain

なお個人的には、LightProbeはモデルやライトが持ってると移動が楽だと認識しています。

Unity 5のLightProbeは最終的にシーン毎に1つに統合されるらしく、複数のLightProbeを作りつなぎを云々考える必要は無さそうです。

但し、あんまり離れていると「どのモデルが持っているLightProbeか」が分かりにくくなるので、LightProbeはモデルの面に沿った部分のみを持つのが良さそうです。

f:id:tsubaki_t1:20150906213612p:plain

PC向けの場合は面倒くさい事は考えずに、ライトはリアルタイムライトを使って、単一のLightProbeGroupで等間隔にLightProbeを並べまくるアプローチが良いと思います。リアルタイムライトがある場合はGIを影響させる・味付け用途が強そうです。

 ライトマップをAssetBundleから読み込むTisp

ライトマップはシーンにあるメッシュが保持しています。そのため、ライトマップを読みこもうと思ったらシーンごと読んでしまうのが手っ取り早いです。

余り知られていない感がありますが、UnityはシーンをAssetBundleに格納し読み込む事が出来ます。

この場合はScene In Buildに含まれていないシーンでもAssetBundleから読み込んでいる間は読めるので、ゲーム配信後にステージを追加する用途とかにも使えます。まあ当然C#スクリプトは後から追加できませんが。

 こっちの方法で呼び出したシーンには、ダイナミックGIやLightProbeといった情報が一緒についてくるので、この後書いたような面倒くさい方法は抜きにしてライトマップを取得する事が出来ます。

手順はこんな感じです。

  1. シーンにAssetBundleタグを付ける
  2. 各AssetBundleをビルド
  3. アセットバンドルを読み込み、シーンを読み込む

gist.github.com

gist.github.com

f:id:tsubaki_t1:20150906235941p:plain

f:id:tsubaki_t1:20150906235950p:plain

この例ではStreamingAssetsに格納していますが、実際にはWebやキャッシュから持ってくるかもしれません。

なおシーンをAssetBundleに格納・圧縮しStreamingAssetsに格納することでLightmapがLZMA圧縮出来るので、アプリサイズがかなり稼げます。ただしアプリ開発が色々な意味でかなり面倒くさくなるので、そのあたりは一長一短です。

LightSnapshotに関するTips

LightmapはBakeモードをAutoに設定していると、どこか(キャッシュフォルダ)に作られます。これでは操作出来ないので、原則としてAutoのチェックボックスを外しBakeを行います。

Bakeを行うとライトマップ計算の一部や結果をキャッシュするLightmapSnapshotが生成され、シーン名と同じフォルダにライトマップが生成されます。

f:id:tsubaki_t1:20150906230938p:plain

この時生成・更新されるテクスチャはUnity 4と異なり、LightmapSnapshotが参照しているテクスチャです。

つまり、ライトマップの生成結果を残したい場合は、Lightmapをファイル的に複製するか、一旦シーンからLightmapSnapshotへの参照を外し、LightmapSnapshotを新しく作ってもらいます。

  1. 「Demo」シーンのライトマップをベイク
  2. Demo Copyと改名
  3. Lightmap SnapshotをNoneに変更
  4. 再度ライトを焼く

f:id:tsubaki_t1:20150906231633p:plain

f:id:tsubaki_t1:20150906231638p:plain

f:id:tsubaki_t1:20150906231645p:plain

ベイクしたライトを更新するHack

ライトマップを更新するアプローチについてです。本来ベイクしたライトは動かすことは出来ないのですが、ライトマップ自体を更新してしまうことでライトの情報を動かします。

また影を動かしただけでは光情報とモデルの受ける光が一致しないので、LightProbeとSkyboxのCubemapも合わせて動かします。

f:id:tsubaki_t1:20150906204938g:plain

ライトマップの設定は以前に紹介したように、LightmapSettings.lightmapsの中身を差し替える事で、ライトマップを更新出来ます。

なおライトマップはUV値をメッシュ側に格納しているため、同じライトマップ設定でしか上書きできません。

またDirectionalModeにNon-Directionalを設定している場合はDirは生成されません。

gist.github.com

LightProbeは少々厄介です。というのも、LightProbeがLightmapSnapshotに格納されるようになった為です。

なので、LightProbeの部分はシーンで読み込んでいる物から拝借します。あとは出力したLightProbeをLightmapSettings.lightProbesに入れてやれば、LightProbeが更新されます。

gist.github.com

CubemapはSkyboxの環境マップです。Unity5のプロシージャルなSkyboxは太陽の向きや設定によってダイナミックに色が変わるのですが、その情報はReflectionProbeに格納されています。

ライトの向きを変更してもこの設定を更新しなければ、Dynamicに動くオブジェクトとライトに焼いたオブジェクトの色が完全に一致しません。

f:id:tsubaki_t1:20150906225551p:plain

この項目は、RenderSettings.customReflectionに格納されています。

Cubemapを取得しRenderSettings.customReflectionにぶち込めば、光沢のあるオブジェクトの映り込み部分も完全に一致します。

そのあたり全部込めたコードはこんな感じです。

gist.github.com

f:id:tsubaki_t1:20150906230420p:plain

 ライトマップの増やし方はLightSnapshotの項目を参照。また実際LightSnapshotを増やす際に現在のLightmap情報が分からないケースがあるので、このアプローチで確認すると良いです。上手く動作しない場合は互換性が無いです。

 ライトマップを分割するTips

ライトの分割は上記の通りなのですが、そうなると全体ではなく一部のパーツのみを更新したいケースがあります。

つまりライトマップを幾つかのパーツに意図的に分割し、アニメーションするパーツのみを差し替えるアプローチです。

 ライトマップの分割は、Advanced Parametersを使用します。

f:id:tsubaki_t1:20150906233347p:plain

この項目はライトマップやダイナミックGIの詳細なパラメータを設定する項目です。ダイナミックGIの解像度(マテリアルのEmission等から生成する影の精度)やAOのアンチエイリアス制度等はここで設定します。

この設定の一つに、System Tagの項目があります。ライトマップはこの項目が同じ物でまとまるので、ここをユニークな値に設定しておけば、ライトマップが分割出来ます。

f:id:tsubaki_t1:20150906233739p:plain

ライトマップのSystem TagはシーンビューのSystemsより確認出来ます。同じ色のモデルは概ね同一のライトマップに格納されています。

f:id:tsubaki_t1:20150906233917p:plain

 あとは上の「ライトマップを更新」を指定のライトマップに適応してやれば、対象のライトマップのみをぐいぐい更新する事が可能です。

Unity - マニュアル: ライティングウインドウ

LightmapをPrefabに適応するHack

ライトマップのUV情報はシーン内にあるメッシュが保持しています。

このパラメータは「他のシーンで使用する事」を目的としたPrefabからはストリップされるため、Prefabをシーンに配置してもライトマップは付いてきません。

なので、Prefabから動的に生成したオブジェクトにライトマップを適応したい場合は、LightmapのUVやテクスチャインデックスを実行時に設定します。

gist.github.com

使い方は以下の感じです。

  1. ライトをベイクする
  2. シーンにあるプレハブのRootにLightmapPrefabをアタッチ
    (シーンから外すと、ライトマップ情報は全てストリップされます)
  3. ContextMenuからSetupを選択
  4. Applyで全Prefabでライト情報を共有
    (Applyする際はstaticフラグをoffにしておくと色々と楽)

これは上手く使うと、Lightmapの節約に使えます。

例えば下の図には4つのオブジェクトを配置しますが、実行時には左のオブジェクトのライトマップを使いまわします。

下の画像は、上が実行前・下が実行時です。同一のライトマップが適応されています。

f:id:tsubaki_t1:20150907003113p:plain

f:id:tsubaki_t1:20150907003119p:plain

この節約方法は、ライトマップを使いまわす関係上「自身のライトマップが他のオブジェクトに影響を与えていない事」が大前提となります。

配置に多少の工夫が必要になりますが、その代わりライトマップのテクスチャサイズはかなり小さくなります。

f:id:tsubaki_t1:20150907003846p:plain

f:id:tsubaki_t1:20150907003852p:plain

Lightmapの動的な追加も可能です。renderer.lightmapIndexとは即ちLightmapSettings.lightmapsの番号なので、このリストに新しく独自のLightmapを追加し、LightmapのUVを書き換えてやることで、別のシーンのライトマップもぶち込めます。

これを上手く使えば別のシステムのライトマップもぶち込めるかもしれません。Unity 4で使ってたBeastとか。

 

なおライトマップを動的に上書き出来るのは「Batching staticではないオブジェクト」のみです。Batching staticなオブジェクトにライトマップ上書きを行うと上手くいきません。とはいえ、実行時にInstantiateする目的であれば問答無用でstaticが剥がされるので気にしなくても良いかもしれません。事前に配置する場合は注意が必要です。

またLightmapがベイクされていないシーンではLightmapの機能が動作しないのか、UVとLightmapを指定しても動作しません。UVの崩れた一番上のオブジェクトはstaticです。

またBatchingが効かなくなります。

f:id:tsubaki_t1:20150907005016p:plain

関連

Unity5の Lighting ウインドウの見かた ~基本編~ - いんでぃーづ

環境マップとIBLとReflectionProbeについて少し整理 - テラシュールブログ

【Unity】Unity5のリアルタイムGI と LightProbe - テラシュールブログ

【Unity】Unity 5 で「光モノ系」を表現するあれこれ - テラシュールブログ

Unity5の世界を暗黒の闇で覆う方法 - テラシュールブログ

Unity5 で画面を綺麗にするライティングに挑戦してみるメモ - テラシュールブログ

Upgrading a Unity project to Unity 5: Shaders and lighting | Blog | Union Assets - Dev Assets Marketplace

【Unity】Unite 2015「Unity 5グラフィックス機能使いこなしガイド」レポート - Qiita

Problems with instantiating baked prefabs | Unity Community