スマホ向け3Dゲームにおける「とりあえず」のレンダリング設定

画像をクリックするとWebGLビルドに飛びます。

こんにちは。技術部平山です。

この記事は、スマホ向けに3Dゲームを作る際に 「とりあえず」やっておく描画設定について考えたり試したりしたことの記録です。 設定の項目は主に、

  • ライトマップ
  • ライトプローブ

といったあたりになります。

そして、これらの設定について考えようと思うと、 何をどうやっても照明計算の知識が必要になりますので、 それについても触れることにします。

動機

最近3Dゲームの試作をやる機会があるのですが、 テキトーに立方体や球をいくつか置いただけでも、 私のスマホ(京セラS2)では満足なフレームレートが出ません。

DrawCallもマテリアル設定も少ないし、 大して重いコードを書いたつもりもないので、CPUが遅いわけはありません。 実際プロファイラをつなぐと1フレームあたり4msで終わったりしています。

となると、犯人はGPUです。 GPUと言えば、頂点処理とピクセル処理ですが、 頂点が多いはずはないので、完全にピクセルです。 S2の画面解像度は1280x720もあり、GPU性能を考えれば この全画素に3Dの照明計算をするのはかなりキツいでしょう。 10年以上前の話とは言え、 PLAYSTATION3のような据置きゲームですら耐え難かった解像度です。 当時は2DのUIだけ1280x720で描画して、3D部分はもっと低い解像度で描画して合成する 「解像度詐欺」と(一部で)呼ばれる手法が普通に行われていました。

Unityは勝手にいろいろやってくれるわけですが、 さすがに性能が下の方のスマホにはあまり配慮してくれていません。 普通に作ると結構豪華なシェーダがバンバン走ってしまいます。 「こりゃちょっと真面目に考えないとダメだな」と思ったのです。

照明計算の概略

ここでは、3DCGにおける照明計算を、ひどく乱暴に説明しましょう。

ゲームのようにリアルタイムで動かさないといけない場合、 照明計算は粗く言って以下の6要素の計算結果を足して行われます。

  • 直接光の拡散反射
  • 直接光の鏡面反射
  • 間接光の拡散反射
  • 間接光の鏡面反射
  • 環境光の拡散反射
  • 環境光の鏡面反射

「直接」「間接」「環境」の3つの種別と、「拡散」「鏡面」の2つの種別の組み合わせで6通りです。 直接であろうが間接であろうが光は光ですし、 表面の性質が千差万別なので反射の仕方を2つに分類するのは乱暴なのですが、 少し前までのゲーム開発において 「耐えられる計算量でマシな絵を出そうと工夫した結果」がこの分類だ、 とお考えください。

光の経路による分類

「直接(Direct)」というのは、光源から直接光が飛んできた時のことです。 太陽があって、何にも遮られずに地面に当たれば、これが「直接」ということになります。

一方「間接(Indirect)」というのは、 一回どこかで反射したり屈折した光に照らされた時のことです。 太陽の光が赤い壁に当たって反射し、それが地面に当たって赤く染める、 みたいな奴ですね。

そして、「環境(Environmental)」というのは、空や風景など、 「遠く」の「全方位」から来る光のことです。「十分遠い」ので、 シーンの中で動き回っても変化しないものとみなせます。

反射の分類

反射には、「拡散反射(Diffuse)」と「鏡面反射(Specular)」があります。

拡散反射

「拡散」というのは、粘土みたいにテカテカしていない時の計算方法です。 表面がザラザラなので当たった光がいろんな方向に反射し、 どこから見ても同じに見える、とします。

なお、真面目にやる場合は、金属でないものでだけ計算します。 金属に拡散反射はありません。

鏡面反射

一方「鏡面」というのは、テカッキラッとした感じの奴です。 マンホールに夕日が当たってすごい反射したけど、 少し頭を動かすとまぶしくない、というアレです。 どこから見ているかで見え方が大きく変わります。

そして、物の色と関係なく、光の色を反射します。 黒っぽいマンホールも、太陽を反射すれば黄色く光ります。 真面目にやる場合、金属はこの鏡面反射しかしません。

なお繰り返しますが、この分け方は便宜上のもので、 実装の都合、機械の性能、やりたい表現などなどによって、 全然違ってきたりもします。 ただ、UnityのStandard Shaderを理解する上では、 まあまあ役に立つ分類と言えるでしょう。

6種類の特性

ではこの6種類の特性をそれぞれ見ていきましょう。

1. 直接光の拡散反射

f:id:hirasho0:20190719135922p:plain

斜め上にある太陽を向いているあたりが明るく、反対は暗くなっています。

直接光なので、経路は一つしかありません。 どこかで反射したり曲がったりすることは考えないからです。 処理負荷は光源の数に比例します。

また、「どこから見るか」によって色が変わらないので、 光源が動かず、物も動かないのであれば、「前もってテクスチャに保存しておいて (「ベイクする」「焼く」と言う)実行時はそれを使うだけ」という最適化ができます。 これがライトマップ です。

なお、光源からの経路が何かに遮られていれば、そこは影になります。 これを計算するのは結構重い処理です。シャドウマップという処理が必要になり、 全てのものを、余計に一回づつDrawCallすることになります。

しかしこれも「光源が動かず」「物が動かない」のであれば、 影もろともライトマップに焼くことができます。 この場合、実行時の負荷は影があろうがなかろうが変わりません。

2. 直接光の鏡面反射

f:id:hirasho0:20190719135925p:plain

テカテカと太陽の光が映り込んでいますね。

直接光なので経路は一つしかないのですが、 拡散反射と違って「どこから見るかで大きく変わる」という特性があります。 普通3Dゲームで視点が固定ということは滅多にないので、 基本的にはライトマップに焼けないと考えて良いでしょう。 ただし、「少しくらいなら動いても気にしない」という割り切りをして 焼いてしまう選択もありえます。

また、キラッといい感じに光らせるための式が結構複雑なので、 それなりに重くなります。StandardShaderの場合、 マテリアルにこれを有効化するかどうかのフラグがあります。 "Specular Highlights"というのがそれです。 あんまりテカテカしてない材質で、画面に写る面積が大きなものは、 切ってしまってもいいかもしれません。

それと、鏡面反射で出てくる色は「物の色とは関係ない」という特性があります。 赤い物だろうが黒い物だろうが、光が白ければ白です。

なお、影に関しては拡散反射と同じです。普通は同じシェーダで一緒にやります。

3. 間接光の拡散反射

f:id:hirasho0:20190719135935p:plain

左に緑の壁があるため、左を向いた面は緑に染まっています。 逆に、右に赤い壁があるため、右を向いた面は赤く染まっていますね。

さて、赤い壁や緑の壁、といった光源から光が届く経路は無限にあります。 そもそも、壁には面積がありますから、壁のどこから来た光か? というだけでも選択肢は無限です。 実際には数々の近似を駆使して高速化するとしても、 スマホで実行中にこの計算を毎フレームやるのは無理です。

ただし、拡散反射ですので、どこから見るかで色が変わりません。 ということは、「光が動かず」「物も動かない」ならば、 ライトマップに焼くことができます

さてこれにも影があります。 正確に言えば「陰」です。 太陽や蛍光灯のようなはっきりした光源ではなく、 全方位からいろんな光が来るので、はっきりした影ではなく、 陰影といった感じになります。 例えば、奥まった所は暗いですよね?鼻の穴の中は黒く見えます。こういうのも、 ライトマップに焼くことができます。

また、動くものでもこの計算をしたい! という場合には「このへんにいる時には周囲から来る光はこんな感じ」 というのを計算してたくさん保存することで、代わりにすることもできます。 UnityではこれをLightProbe と呼びます。

動く物に対して間接拡散反射の計算をするのが、LightProbeです。 場所によってLightProbeのデータが明るかったり暗かったりすれば、 奥まった所に入った人が暗く見え、外に出たら明るく見える、 というようなことができるわけです。

4. 間接光の鏡面反射

f:id:hirasho0:20190719135938p:plain

ところどころ、赤い所、緑の所、白っぽい所がありますね。 赤い所は左の赤い壁からキラリと反射したもので、 緑は右の壁から来たものです。白は空でしょう。 このように、キラリと周囲が映り込みます。

これも光源は無数にあり、経路も無数にあり、 さらにどこから見るかで色が激しく変わるので、 ライトマップに焼くことはできません。

ただ、間接拡散反射の時と同じく、 「このへんにいる時は周囲から来る光はこんな感じ」 というデータを前もって計算しておけば、 それなりにそれっぽいことができます。 UnityではこれをReflectionProbe と呼びます。

ただし、一個あたりのデータ量がLightProbeより格段に大きいので、 あんまりたくさんは置けないでしょう。 テカッテカの金属オブジェに、周囲の風景が写り込む、 みたいなことをやりたい時に使います。

5. 環境光の拡散反射

f:id:hirasho0:20190719135930p:plain

全体に青っぽいですね。ほぼ空の色です。

これは、「十分遠く」の「全方位」からやってくる光による、拡散反射です。 光源になるのは空や地面ですね。 地面は空の光を反射しているので、地面が光っているわけではありませんが、 地面が光っていることにしてしまうと楽です。

例えば、空からは青い光が来て、太陽があるあたりからは黄色い明るいのが来て、 地面は草なので暗い緑が来る、といった感じになります。 どこから見るかに依存しないので、「物が動かないのであれば」 これもライトマップに焼くことができます。

欠点としては、影が出せない、ということがあります。 もし何もケアしていなければ、「屋根の下にいるのに空の光を受ける」 といったことが起きてしまいますが、 そういう場合はLightProbeを置いて代わりに使えば良いでしょう。

たぶん環境拡散反射は 「デフォルトで世界に一個だけ置いてあってOffにできないLightProbe」 として実装されており、 LightProbeがある時にはこのデータを使っていない気がします(厳密な確認はしてない)。 逆に、ステージ全体が開けていて、 色のついた発光物が置いてあったりしないのであれば、 LightProbeなしでも良い気がします(スポーツのスタジアムとかはいい例です)。

なお、「十分遠いとみなせる光源」は全部ここにつっこめるので、 太陽や室内の蛍光灯、街灯なんかも入れてしまえば、 直接光の計算を環境光の計算に含めてしまうこともできます。 ただし影は出せないので、太陽のように明らかに明るい光源を含めるのはおすすめできません。 たくさん光源がある複雑な場面(夜の街とか)で、 くっきりした影がなくても気にならない場合には良いでしょう。

Lighting SettingsのEnvironmentの項目に、 Environment Lightingというものがあり、これで設定します。

6. 環境光の鏡面反射

f:id:hirasho0:20190719135933p:plain

かすかに青白い所があります。これは空の映り込みです。 もしSkyboxのマテリアルでパノラマ写真みたいなものを空として返すものを用意すれば、 周囲がテッカテカに映った絵が見られるでしょう。

これは、「十分遠く」の「全方位」からやってくる光による鏡面反射で、 拡散環境反射と同じく、光源になるとのは空や地面です。 ただし拡散反射と違って、物体の色に関係なく光の色が出ますし、 視線や位置によってどこが映り込むかが変わります。 ライトマップには焼けません。

なお、たぶんですが、 実装は「デフォルトで一個置かれてOffにできないReflectionProbe」 ではないかと思います。 MeshRenderer側でReflectionProbeがOnになっていて、 実際にReflectionProbeが置いてあれば、 そちらが代わりに使われるようです。計算の中身は同じなのでしょう。

特徴をまとめてみる

直接光の拡散反射 直接光の鏡面反射 間接光の拡散反射 間接光の鏡面反射 環境光の拡散反射 環境光の鏡面反射
焼ける? Yes No Yes No Yes No
重い? 軽い×光源数 重め×光源数 無理 無理 軽い 軽い
実行時に影出せる? Yes Yes No No No No
実行時に光変化できる? Yes Yes No No 重いがYes 重いがYes

雑ですが、だいたいこんな感じでしょうか。

設定の方針

動かないものは焼け

一番大事なことはこれじゃないでしょうか。

普通のゲームでは、画面の大半は風景が写っているはずで、 人のように動くものの占める面積はそれほど大きくありません。

フラグメントシェーダの負荷は面積に比例しますから、 地面や建物、山、空、といった風景を焼けるかどうかで 大きく負荷が変わります。 動くものが占める面積が小さいゲームほど、焼くことの効果は大きいわけです。 逆に、画面の真ん中にデカいドラゴンが陣取って動き続けるようなゲームだと、 あまり効きません。

さて、Unityでライトマップ焼きによる負荷軽減を行うには、 gameObject側の設定をstaticにする必要があります。

f:id:hirasho0:20190719135957p:plain

staticにしておけば、あとはLightingSettingsの設定次第で ライトマップが作られて、照明計算が軽くなるわけです。

焼くことの欠点

焼くことで、到底実行時には計算できない複雑な間接光の計算ができるようになり、 できる絵は格段にリアルになるのですが、欠点も多数あります。

まず、容量が増えます。 できるデータはテクスチャであり、ステージが広ければ広いほど、 ステージに物がたくさんあればあるほど、 そして、品質を上げれば上げるほど容量を食います。 さらに、光源の設定ごとに容量が増えます。 「同じステージで太陽の位置と色を変えたデータを8種類焼く」 となれば、8倍の容量となり、実質無理です。 「光の具合だけ変えて朝、昼、夕方、夜のステージを安く作ろう」 なんて考えると、途端に容量がヤバいことになります。

次に、焼きに時間がかかります。 品質の高い焼きデータを作るには時間がかかり、 開発効率を落とします。 開発時は低品質で焼いて感じを掴み、 完成前に本番焼きをする、といった運用になるでしょう。

そして、最大の欠点が、実行時に物や光源を動かせなくなることです。 太陽がじわじわ動いて、夕方になると赤くなって影がのびてくる、 みたいな仕様があるなら、この手は取れません。 建物が壊れる、建物を動かす、実行時に建物をランダムに配置する、 といったこともできません。

そういったことがどうしても必要であれば、 焼くのは諦めた方が良いでしょう。 細かな陰影を焼なくても良い絵柄(アニメ調とか)を模索するのも良いかもしれません。

あるいは、Ambient Occlusion のような手法で、多少なりとも焼きに近い品質に近づける手もあります。

とはいえ、スマホ向けで多少なりとも背景にリアルさが欲しいのであれば、 「とりあえず焼く」という方針が無難なのではないでしょうか。

影設計

焼けるものは焼く、と決めても、 焼けるのは動かないものだけです。 加えて、動くものと動かないものの相互作用の問題があります。 その最たるものが影です。

動くものと動かないものの間の関係は、以下の4つです。 なお、わかりやすさのために、「動かないもの」を「建物」か「地面」、 「動くもの」を「人」と言い換えて説明します。 その方がイメージが伝わりやすいでしょう。

1. 人が、人に、影を落とす

シャドウマップ法を使って、毎フレーム計算するのが標準的ですが、 DrawCallが倍になる上に、影判定のシェーダ負荷も結構キツいです。

シャドウマップ法に耐えられない機械を対象にする場合は、 この要素は捨てることになります。 そういう場合は、直接光を弱くして曇りの日のような感じにすると、 影がないことをゴマかしやすくなります。

2. 人が、地面や建物に、影を落とす

地面だけで良く、地面が平面であれば、 モデルの頂点を地面に射影して黒く塗ることで、比較的軽く影を描けます。 それすら耐えられない場合は、丸いぼやけた板を地面に置く「丸影」が良いでしょう。 地面に影が落ちないと接地感がなくなってゲームに支障を来すので、 何もやらないわけには行かないのです。 これらの手法を使う場合、QualitySettingsのShadowsの項目で、 影をDisableにしてしまい、自力でどうにかすることになるのでしょう。

地面以外にも影を落としたい、あるいは地面が平面でない、 という場合には、標準のシャドウマップ法で行うことになります。 DrawCallが増える上に、影判定のシェーダ負荷が 動かないものにもかかってくるので、かなり負荷は上がります。

さらに、正確性の問題もあります。 動かないものの照明計算はもうライトマップに焼いてあるのです。 すでに建物の影になっているのか、そうでなく日向なのかが、 ライトマップからはわかりません。すでに影なら、そこに人の影が落ちても それ以上は暗くならないはずですが、そういう区別ができないのです。

しかし、余計な計算を増やしたくないのであれば我慢するしかなく、 動くものの影になった部分は、すでに影であろうがなかろうが、 「一定値を引く」ということになります。 Unityの場合Lighting SettingsのMixed Lightingの設定を、 subtractiveにしておけばそうなります。 影がどんな色になるかは固定で設定します。

f:id:hirasho0:20190719135949p:plain

影の中に影がある例です。わかりやすいように影の色を緑にしておきました。 ゲーム的に、キャラの位置がわかりやすくなるので、 絵としてはおかしくてもゲームとしてはアリ、ということもあります。 もうちょっと大人しい色にすれば気にならないかもしれません。

f:id:hirasho0:20190719135951p:plain

影の色をUnityのデフォルト値にしてみました。どうでしょう? ...すみません、私はかなり気になります...

もちろん追加の負荷を払えば、人の影が日陰では落ちないようにできます。 UnityではMixed Lightingの項目をshadowmaskにするだけです。 おそらく「元々日陰かどうか」を追加情報として持つのでしょう。 ただし、もちろん重くなります。

3. 建物が、人に、影を落とす

LightProbeで多少はやれます。 それで足りないとなれば、建物その他をシャドウマップに 描くしかなく、かなり計算が増えます。 「とりあえず」の設定としてはナシでいいでしょう。 製品としてどうしてもということであれば、何か考えることになります。

4. 建物が、建物や地面に、影を落とす

ライトマップに焼かれているので問題ありません。 ただし、くっきりした影を焼こうとすると解像度の高い焼きデータが必要になり、 容量と計算時間の両面で大変ですので、 基本的にはボヤけた影で我慢することになります。

ではどうするか?

建物が地面に落とす影が焼いてありさえすれば、 あとゲームとして重要なのは、人が地面に落とす影です。 これがないと物の位置関係を把握できません。 丸影などの古代の手法はUnityでやると面倒くさいので、 スイッチ一つで行けるシャドウマップ法でどうにかしたいところです。

絵がウリのゲームであればもっと上を目指したいところですが、 そういう製品はたぶん安物のスマホを切り捨てると思うので、 SSAOでも使えば良いかと思いますし、 Realtime Global illuminationといういかにもすごそうなチェックボックス がありますので、Onにしてみるのが良いのでしょう。

ただ、「影の中に影」はちょっと悲しいですね。 負荷を見て耐えられるならShadowmaskにしてしまうのが良いのでしょう。

地面や建物の鏡面反射は悩み所

人間の視覚はコントラストが強い部分で形を把握します。 だから、鏡面反射による高いコントラストがあるかないかは、 かなり印象を左右するのです。 しかし、鏡面反射は視線に依存するので焼けません。 そして結構重いのです。

Lighting Settings - Mixed Lighting - Lighting Modeを Subtractiveにすると、影の中に影が落ちるのみならず、 staticなものの直接鏡面反射が一切計算されなくなります。 結構軽くなるはずですが、見た目は相当寂しくなります。

実際製品を作るとなればかなり考えるとは思いますが、 今回の話は「何か試作する時にとりあえずやっとく設定」 という程度なので、Subtractiveにして鏡面反射ナシ、 というのが良いかなと思います。 重い物を作ってしまって後で軽くするよりも、軽くしておいて要素を足していく方が 大抵は良いのです。

重い良い絵に慣れてしまうと、削りたくなくなってしまいますし、 良い絵にするにあたって調整その他で結構手間をかけてしまっているはずです。 それを削っていくのは無駄も多いし、心理的にも苦しい作業になります。

LightProbeの配置

まず、ステージが開けたゲームならLightProbeなしで始めていい気がします。 スタジアム状の場所でキャラが動くゲームであれば、 障害物があったり入り組んだ場所があったりはあまりしないでしょう。 LightProbeを置くのは結構面倒くさいので、ナシでいい気がします。

ただ、キャラに建物の影が落ちるとか、奥まった所にキャラが入っていけるとか、 ステージに焚き火や街灯がいくつもあって、キャラがその近くに寄っていけるとか、 そういう話になると、場所によって明るさが変わる要素がないと 寂しくなりすぎます。

初期配置用ツール

今回は、初期配置用として、雑にLightProbeを置くスクリプトを用意しました。

f:id:hirasho0:20190719135941p:plain

LightProbePlacer.csというエディタスクリプトから実行でき、 等間隔でLightProbeを置くだけです。 本当は、光が急激に変わる所に密に置き、そうでもない所はまばらに置く、 というケアが必要なのですが、どうせ最終的に手調整になるなら そこまでしなくてもいいでしょう。雑に置いても感じはわかります。

LightProbeの注意点

なお、公式のドキュメントにもありますが、 LightProbeはあまり密に配置しすぎると、結果がおかしくなります。 「動く物の大きさより大きな間隔で置く」が基本です。 例えば人が半径1メートル、高さ2メートルの円柱くらいの大きさなのであれば、 水平間隔を1メートルよりは大きく取らないとおかしくなります。

f:id:hirasho0:20190719140430p:plain

中央にやけに暗い球がありますが、これ何が起こってるかわかりますか?

このシーンは真ん中に円柱があります。そして、 たまたまテキトーに置いたLightProbeの一つが、 この円柱の中に配置されてしまいました。 円柱の中ですから、周囲から光は全く来ず、真っ暗になります。

この大きな球の中心座標に一番近いLightProbeはその真っ暗なものでして、 それが球全体に適用されます。すると、こういうことになるわけです。 これをわずか1m動かして円柱から出してやると、こうなります。

f:id:hirasho0:20190719140423g:plain

そういうわけで、LightProbeの置き方は動く物の大きさとも関係するので、 簡単ではないわけです。今回の例のように「立体の中に入って真っ暗になってしまう」 のも避けないといけません。

巨大なモンスターと戦うゲーム、なんてものを作る場合はかなり考えないといけないでしょう (私だったらLightProbeが不要な開けた空間でしかやらないと決めてしまうでしょうね)。

設定の実際

では設定の実際です。

Lighting Settings

  • RealTime Lightings
    • RealTime Global Illumination
      • どう考えても重そうなので、とりあえずOffです。 私のスマホで測ったらそれなりに重くなりました。
  • Mixed Lighting
    • Baked Global Illumination
      • onにするとライトマップが焼かれます。 動かないものはこれでできるだけどうにかしてもらいましょう。
    • LighitngMode
      • とりあえずSubtractiveにして様子を見ています。影の中に影が落ちて辛い、となればshadowmaskにすることもあるでしょうが、やはり重くなります。絵を作り込む段階になったら考えるでしょう。
  • Lightmapping Settings
    • Direct Samples
      • 少ないほど焼きが速いので、開発中は下げておきたいところです。2でも感じはわかります。シーンに置く物が出来上がって、ライトの角度や色が決まったら増やして本番焼きをしましょう。
    • Indirect Samples
      • 8が最低です。マニュアルには10とありましたが、8にできますね...
    • Environment Samples
      • 8が最低です。コードで設定する方法が見当たりません...
    • Lightmap Resolution
      • シーンが広くて複雑なほど下げないとテクスチャ容量が大きくなります。 Unity単位1あたりのピクセル数なので、例えば1を1mとしているゲームで、 2に設定すれば、50cm精度で焼く、ということになります。 開発中は2や4でも感じがわかると思いあすし、 本番焼きの際にも、容量の問題があるのであまり大きくはできません。 スマホのゲームでライトマップに100MBも食わせるわけには行かないでしょう。 シーンの数だけ焼くわけですし、ステージあたり1024x1024を1枚、 という程度に抑えたい気分でいます。
  • Debug Settings
    • AutoGenerate
      • offにしています。勝手に焼かれなくなるので、ライトを動かしたり、モノを動かしたり した時にはちゃんとライティングがおかしくなり、「焼いている」ということを 忘れないで済みます。勝手に焼かれた方が便利であればonでも良いのでしょう。

Quality Settings

スマホ系のプラットホームではMediumが標準なので、それをいじっておくのが良さそうです。

  • Pixel Light Count
    • 1のままにしておきました。直接光の個数が増えるとシェーダが比例して重くなるので、 1個しかライトは当てない、と割り切ってしまった方が良いかと思います。 炎の近くで明るくなるとか、打撃が当たった所が光るとか、 そういうことはやりたくなるわけですが、加算合成のエフェクトとLightProbe の合わせ技でごまかす方が無難ではないでしょうか。
  • Anti Aliasing
    • ポリゴンの輪郭が綺麗になりますが、下の機種に合わせるならoffでしょう。 スマホの場合元々ドットが小さいので、 ポリゴン境界が多少ジャギッてもそんなに気にならない気がします。 これによる負荷は完全に機種依存なので、どの機種でどう重くなるかが読めない、 というのも厄介なところです。 ただ、「解像度を下げる代わりにアンチエイリアスはOn」という駆け引きも あるかとは思います。
  • Realtime Reflection Probes
    • offのままが良いでしょう。間接鏡面反射のデータをゲーム実行中に生成更新する、 というのは結構大変です。ただ、車ゲームは例外です。自分の車に動く風景が写り こまないとどうにも寂しいので、そういう場合はonにしたくなります。
  • Shadows
    • まずはHard Shadow Onlyで良いでしょう。影の輪郭を柔くする処理は重いのです。 曇りや屋内などで影がクッキリしてると変、という場合には考えないといけませんが、 「そもそもシャドウマップをやめて丸影にする」という思い切った手の方が 良い気もします。なお、Disableにするとかなり軽くなります。
  • Shadow Distance
    • 小さすぎると遠くで影がなくなってしまうので、必要に応じて大きくしましょう。 画面端での歪みを軽減するために、画角(FieldOfView)を小さくして 遠くから写す(望遠に寄せる)場合は、ここが足りなくなりやすいので注意が必要です。 小さいほど影のガビガビ感が減って綺麗になる傾向はありますが、 次の項目をClose Fitにしていればそんなにひどいことにはならないようです。
  • Shadow Projection
    • Close Fitが良さそうです。カメラの状況に合わせてシャドウマップの描画範囲を できるだけ狭くしてくれるので、画質が上がります。 ただし、影の輪郭のガビガビがジラジラと動き続けるので、それが気になるなら Stable Fitにすることになるのでしょうが、品質と負荷にはお気をつけください。 解像度不足を補うためにShadow Resolutionを上げてしまえば、 メモリ量とGPU負荷を押し上げることになります。

測定

雑ですが多少は測定しておきました。京セラS2にて、 フル解像度1280x720での測定です。ミリ秒とfpsを用意しましたので、 どちらでも慣れている方でご覧ください。

設定 フレーム時間(ms) フレーム毎秒(fps)
static焼き+シャドウマップ無効 21 47
static焼き+subtractive
LightProbe/ReflectionProbe無効
26 38
static焼き+subtractive 25 40
static焼き+shadowmask 29 34
static焼き+shadowmask+RealTimeGI 39 25
static不使用 31 32

一応shadowMaskや、RealTimeGIが遅いことの確認ができました。 また、staticにせず焼かないでいるとそれなりに重い、ということも確認できています。 LightProbeやReflectionProbeのGameObjectを無効化したのに 速くならなかったのは、 たぶん環境光の処理がLightProbeやReflectionProbeの処理と 同じ処理だからでしょう。 LightProbeを無効にしても環境光のデータがつっこまれて同じ計算がされる、 ということかと思います。

なお、この過程でスクショを取ったので貼っておきます。

f:id:hirasho0:20190719140538p:plainf:id:hirasho0:20190719140536p:plainf:id:hirasho0:20190719140531p:plainf:id:hirasho0:20190719140534p:plain

順に、Subtractive、Shadowmask、static不使用、Subtractive影なし、です。

Shadowmaskにすると「影の中に影」問題が起こらなくて実に良いですね。 しかも地面が全体に明るく、これは直接光の鏡面反射計算によってその分明るくなっているからです。 その分だけ重くなっていますから、悩むことになるのでしょう。

staticフラグを全部外すと、緑や赤の壁の影響は全然わからなくなってしまいます。 しかも軽くもないという状態です。 しかし影は全部くっきりで一貫性があります。この方が望ましい製品もあるでしょう。 ただ、処理は結構重いです。

そして、影をなくすと結構速くなります。 丸影にしてしまえば、ほぼこの速度でゲームが動くわけです。

しかしそれでもなお60fpsには到達せず、 Standard Shaderを使う限り、この解像度で60fpsを出すのは無理そうだ、 ということがわかります。 そこまで軽くしたければ、そもそも3Dゲームにするな、とか、 照明計算をするな、とか、さらには「シェーダ全部自分で書け」ということになるかと思います。

しかしまあ、実際には解像度を下げれば良いでしょう。1280x720は高すぎるからです。 解像度を下げた分高度な手法を入れる、という方が総合的な質は高くなるかもしれません。

まとめ

簡単に書くつもりが、また長くなってしまいました。

Unityを使えば最新のテクニックをスイッチ一個で有効化できてうれしいわけですが、 最新のテクニックは重いのです。 たぶんUnityがメインターゲットにしているのはPCなので、 スマホ用、とりわけ低性能機種もケアするならそれなりに中身を理解して設定する必要がある ように思えます。

せっかく2010年代後半の技術が山盛りになっているのに、 2000年代中盤にやっていたレベルに落とす設定をして回る、 というのは悲しいですね。 LWRPが標準になる気配はありますが、たぶんLWRPにすると 今回の比でないくらい私のスマホに厳しい結果になると思います。

それにしても60fps出ないのは悔しいですね。 RenderPipelineごと置き換えたい衝動に駆られます。 到底割に合わないのでやらないつもりですが、 もしやってしまったら、たぶんそれも記事にすると思います。