最終更新:2020 年 2 月(読み終わるまでの時間:15 分)

ライトベイク済みプレハブと、ローエンドの携帯電話で 60fps を実現するためのさまざまなヒント

このページで学ぶ内容:ゲームをすそ野の広いモバイル端末向けに最適化して、潜在的プレイヤーへのリーチ範囲を最大限に広げる方法についての、Michelle Martin 氏(MetalPop Games 社のソフトウェアエンジニア)からのヒント。 

モバイル戦略ゲーム『Galactic Colonies』を開発していた MetalPop Games は、フレームレートが落ちたりデバイスが過熱することなく、プレイヤーがローエンドデバイスで巨大都市を構築できるようにするという課題に取り組んでいました。優美なビジュアルと安定したパフォーマンスのバランスをどのように取ったのかをご覧ください。

Unity で制作されたゲーム『Galactic Colonies』

ローエンドデバイスで街づくり

最新のモバイルデバイスは高い処理能力を備えていますが、大規模で美しいゲーム環境を安定したフレームレートで実行することはいまだに困難です。より古いモバイルデバイス上で、大規模な 3D 環境で安定して 60 fps を達成するのは難題です。

開発者としては、ハイエンドのスマートフォンのみをターゲットとし、大多数のプレイヤーがゲームを滑らかに実行するのに十分なハードウェアを持っていると見なして開発を進めることもできます。ただし、いまだに数多くの古いデバイスが使用されている現状において、これは潜在的なプレイヤーを大量に締め出す結果となります。古いデバイスのユーザーはすべて、できれば排除したくない潜在的な顧客です。

当社のゲーム『Galactic Colonies』では、プレイヤーが異星人の住む星をコロニーとして、数多くの建物が建ち並ぶ大規模なコロニーを構築します。小さなコロニーの建物の数は 1 ダース程度ですが、大きなコロニーでは優に数百を超えます。

パイプラインの構築を開始したときの目標リストは次のとおりです。

  • 多数の建物が建ち並ぶ大きなマップがほしい
  • 安価で古いモバイル端末でも早い実行速度で動作するようにしたい
  • 光と陰影を美しく見せたい
  • 簡単で保守性に優れた制作パイプラインがほしい

モバイルでのライティングの課題

ゲームの巧妙なライティングは、3D モデルの見栄えを良くする鍵になります。Unity なら、手間なく巧みなライティングができます。ゲーム面を作って、ダイナミックライトを配置すれば、すぐ見栄え良く使えます。パフォーマンスを注視するなら、全ライトをベイクしてから、ポストプロセッシングスタックを通じて SSAO をいくつか追加し、その他の装飾を散りばめましょう。そうしたらもう、出荷するだけです!

モバイルゲームでは、ライティングの設定時にテクニックを駆使したり、何らかの回避策を講じたりする必要があります。例えば、ハイエンドデバイスを対象としている場合を除けば、ポストプロセッシングエフェクトには一切触れない方が良いでしょう。同様に、大きなシーンでダイナミックライトをふんだんに使ってしまうと、フレームレートが格段に低下してしまいます。

リアルタイムのライティングは、デスクトップ PC でも負荷が高くなりうるものです。モバイル端末ではリソースがさらに限られているので、こういった魅力的な華々しい機能をすべて盛り込む余裕は必ずしもありません。

Unity - モバイル向けライトベイク済みプレハブ - MetalPop Games

ライトベイキングという助け舟

必要以上にシーンへ妙にこだわったライトを数多く設定して、ユーザーのスマートフォンのバッテリーを無駄に消耗させることは避けたいものです。

ハードウェアを限界まで酷使し続けると、スマートフォンが過熱し、その結果、端末保護のために処理能力がダウンします。これを回避するには、リアルタイムで影を描写する必要のないライトをすべてベイクするという方法があります。

(静的)シーンのハイライトと影を事前に計算しておいて、その情報をライトマップに保存するというのが、ライトベイキングのプロセスです。その後、どこでモデルを明るく(または暗く)するのかをレンダラーに伝え、ライトがあるような錯覚を作り出します。

この方法でレンダリングを高速化できます。負荷が高く時間もかかるライト計算がすべてオフラインで完了していて、レンダラー(シェーダー)は実行時にテクスチャーに格納された結果を見るだけで済むからです。

これには代償があって、ライトマップテクスチャーを余分に積み込む必要があります。これによりビルドのサイズが大きくなり、実行時に余分なテクスチャーメモリが必要になります。また、メッシュにライトマップ UV が必要になり、若干サイズが大きくなるので、ある程度スペースのロスが生じます。しかし全体的に見れば、圧倒的な高速化を実現できます。

しかし、私たちのゲームの場合、これは選択肢には入りませんでした。私たちのゲームの世界は、プレイヤーによってリアルタイムで構築されるものだからです。常にプレーヤーにより新しいエリアが発見され、新しい建物が追加され、既存の建物が改築されることから、どうしても効率的なライトベイキングは不可能です。プレイヤーによって常に変化する動的世界を相手にしている場合は、単に「Bake」ボタンを押しただけでは意味がありません。

そのため、私たちは高度にモジュール化したシーンのライトベイキングにおいて、さまざまな問題に直面しました。

ライトベイク済みプレハブ用パイプライン

Unity ではライトベイキングデータが、シーンデータに直接関連付けられるかたちで保存されます。これは、各ゲームステージが独立していてシーンが構築済みで、動的オブジェクトの数が限られている場合であれば問題になりません。ライティングを事前にベイクしておけば、それで十分です。

ところがゲームステージを動的に作成する場合、これではうまくいかないのは自明です。都市建築ゲームの世界は、創られていないのが前提です。世界の大部分はプレイヤーが何を、どこに建築するかによって動的に、その場で組み立てられていきます。これは通常、プレイヤーが何かを建築しようとするたびにプレハブをインスタンス化することで実現されます。

この問題の唯一の解決策は、関連するすべてのライトベイキングデータをシーンではなく、プレハブ内に格納することです。残念ながら、使用するライトマップのデータ、その座標、スケールをプレハブにコピーする簡単な方法はありません。

ライトベイクしたプレハブを処理するための安定したパイプラインを実現するには、個々のシーンごと(事実上複数のシーン)にプレハブを作成して、必要になったときにメインゲームにロードするのが、最善のアプローチにです。各モジュラーがライトベイクされ、必要になったらゲームにロードされます。

Unity でのライトベイキングの仕組みをよく見ると、実際にはライトベイクされたメッシュに他のテクスチャーが適用され、メッシュの明度が若干調整される(彩色されることもあります)だけのレンダリングであることがわかります。必要なのはライトマップテクスチャーと UV 座標だけです。そしてそのどちらも、ライトベイキングプロセスで Unity により作成されます。

ライトベイキングのプロセスで、Unity は新しい UV 座標(ライトマップテクスチャーの場所を示すもの)と、個々のメッシュのオフセットとスケールをワンセットで作成します。ライトを再ベイクするたびに、座標は変化します。

UV チャンネルの活用方法

この問題の解決策を練るうえで、UV チャンネルの仕組みと、それを最大限に活用する方法についての知識が役に立ちます。

各メッシュには複数の UV 座標セット(Unity では UV チャンネルと呼びます)を持つことができます。それぞれのテクスチャー(拡散、鏡面、バンプなど)がすべて画像内の同じ場所に情報を格納するので、たいていの場合 UV セットは 1 つで十分です。

しかしオブジェクトがライトマップのようなテクスチャーを共有していて、1 つの大きなテクスチャー内の限定的な場所に関する情報を参照する必要がある場合は、この共有テクスチャーで使用する別の UV 座標セットを追加する以外の方法がないこともあります。

複数の UV 座標を使うことの欠点は、メモリの消費量が増えることです。UV セットを 1 つではなく 2 つ使うと、メッシュの 1 つ 1 つの頂点で UV 座標が 2 倍になります。すべての頂点に 2 つの浮動小数点数が格納され、レンダリング時にそれらが GPU にアップロードされます。

プレハブの作成

Unity では、通常のライトベイキング機能を使用して座標とライトマップが生成されます。エンジンによってライトマップの UV 座標がモデルの 2 番目の UV チャンネルに書き込まれます。重要なのは、この場合は 1 番目の UV 座標セットが使えないことに注意することです。なぜなら、モデルをアンラップする必要があるからです。

すべての面に同じテクスチャーを使っている箱を想像してみてください。同じテクスチャーを再利用するので、箱の各面がすべて同じ UV 座標を持ちます。しかし、箱の各面にはそれぞれの光の当たり方と影のつき方があるため、これはライトマップされたオブジェクトでは機能しません。各面について、それぞれのライティングデータに基づくライトマップ内の独自のスペースが必要なのです。そこで、新しい UV セットが必要になります。

新しいライトベイクしたプレハブの設定に必要なのは、行方不明にならないようにテクスチャーとその座標を保存して、それをプレハブにコピーすることだけです。 

ライトベイキングが完了したら、シーン内のすべてのメッシュを対象にしたスクリプトを実行し、オフセットとスケールの値が適用された状態で UV 座標をメッシュの UV2 チャンネルに書き込みます。 

メッシュを更新するためのコードは、次のように比較的単純です(以下に例を示します)。

少し説明を加えると、これはメッシュのオリジナルではなくコピーに対して実行されます。ベイキングプロセス中のメッシュの最適化が目的だからです。 

コピーが自動的に生成されてプレハブに保存され、新しいマテリアルにはカスタムシェーダーと新しく作成されたライトマップが割り当てられます。これでオリジナルのメッシュには触れないまま、ライトベイクしたプレハブはすぐに使用できる状態になります。

modify Meshes.cs (C#)
Mesh meshToModify = GetComponent<MeshFilter>().sharedMesh;
Vector4 lightmapOffsetAndScale = GetComponent<Renderer>().lightmapScaleOffset;

Vector2[] modifiedUV2s = meshToModify.uv2;
for (int i = 0; i < meshToModify.uv2.Length; i++)
{
    modifiedUV2s[i] = new Vector2(meshToModify.uv2[i].x * lightmapOffsetAndScale.x + 
    lightmapOffsetAndScale.z, meshToModify.uv2[i].y * lightmapOffsetAndScale.y + 
    lightmapOffsetAndScale.w);
}
meshToModify.uv2 = modifiedUV2s;
Unity - ライトベイク済みプレハブ - マテリアルのセットアップ

カスタムライトマップシェーダー

これでワークフローがとてもシンプルになります。グラフィックスのスタイルや見た目を更新するには、該当のシーンを開いて納得いくまで変更を加えたらコードを実行して、自動化されたベイクとコピーのプロセスを開始します。このプロセスが終了すると、ゲームでは更新されたプレハブとメッシュが、更新されたライティングで使用されるようになります。

実際のライトマップテクスチャーは、カスタムシェーダーにより追加されます。カスタムシェーダーは、レンダリング中にライトマップを 2 番目のライトテクスチャーとしてモデルに適用します。シェーダーはとてもシンプルで短く、カラーとライトマップを適用するだけでなく、低負荷のフェイクスペキュラー/グロスエフェクトの計算を行います。

シェーダーコードを次に示します。上記の画像は、このシェーダーを使用したマテリアル設定の画像です。 

custom shader.cs (C#)
Shader "Custom/LightmappedPrefabWithSpec"
{
  Properties
  {
    _MainTex("Base (RGB)", 2D) = "white" {}
    _Lightmap("Lightmap", 2D) = "white" {}
    _Specmap("Specmap", 2D) = "white" {}
    _SpecularAtt("Glossiness", Range(0.1, 2)) = 0.5
    _SpecularAmt("Specular", Range(0, 1)) = 0.5
  }
	
  SubShader
  {
    Tags{ "Queue" = "Geometry+1" }
    Pass
    {
      CGPROGRAM
 
      // Defining the name of the vertex shader
      #pragma vertex vert
 
      // Defining the name of the fragment shader
      #pragma fragment frag

      // Include some common helper functions,
      // specifically UnityObjectToClipPos and DecodeLightmap.
      #include "UnityCG.cginc"
 
      // Color Diffuse Map
      sampler2D _MainTex;
      // Tiling/Offset for _MainTex, used by TRANSFORM_TEX in vertex shader
      float4 _MainTex_ST;

      // Lightmap (created via Unity Lightbaking)
      sampler2D _Lightmap;
      // Tiling/Offset for _Lightmap, used by TRANSFORM_TEX in vertex shader
      float4 _Lightmap_ST;
 
      // Grayscale Map indicating which parts of the models have specular
      // Note: _Specmap_ST is not needed, as this map is using the same 
      // UVs as for the _MainTex.
      sampler2D _Specmap;
 
      // This is the vertex shader input: position, UV0, UV1, normal
      // UV1 (= second UV channel) needed for the lightmap texture coordinates
      struct appdata
      {
        float4 vertex   : POSITION;
        float2 texcoord : TEXCOORD0;
        float2 texcoord1: TEXCOORD1;
        float3 normal: NORMAL;
      };
 
      // This is the data passed from the vertex to fragment shader
      struct v2f 
      {
        float4 pos  : SV_POSITION; // position of the pixel
        float2 txuv : TEXCOORD0; // for accessing the diffuse color map
        float2 lmuv : TEXCOORD1; // for accessing the light map
        float3 normalDir : TEXCOORD2; // for fake specular
      };
 
      // This is the vertext shader, doing nothing special at all.
      // Most notably it is calculating the surface normal, because that
      // is needed for the fake specular lighting in the fragment shader.
      v2f vert(appdata v)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.txuv = TRANSFORM_TEX(v.texcoord.xy, _MainTex); // using _MainTex_ST
        o.lmuv = TRANSFORM_TEX(v.texcoord1.xy, _Lightmap); // using _Lightmap_ST
 
        // Calculating the normal of the vertex for the fragment shader
        float4x4 modelMatrixInverse = unity_WorldToObject;
        o.normalDir = normalize(mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
 
        return o;
      }
 
      uniform float _SpecularAtt;
      uniform float _SpecularAmt;
 
      // Fragment Shader
      half4 frag(v2f i) : COLOR
      {
        // Reading color directly from the diffuse texture, using first UV channel
        half4 col = tex2D(_MainTex, i.txuv.xy);
        // Reading specular (on/off) value from spec map texture
        half4 specVal = tex2D(_Specmap, i.txuv.xy);
        // Reading lightmap value from the lightmap texture
        half4 lm = tex2D(_Lightmap, i.lmuv.xy);
 
        // Fake specular light angle calculation with a hard-coded light direction
        half3 th = normalize(half3(0, 1, -0.25));
        float spec = max(0, dot(i.normalDir, th));
 
        // Adjusting by overall specular amount and glossyness (material parameters)
        spec = _SpecularAmt * pow(spec, 40.0 * _SpecularAtt);
        // We're just using red value of the specular texture, like a grayscale map,
        // although technically spec could be colored.
        // Example: float3 specCol = specVal * spec;
        spec = spec * specVal.r;
 
        // Calculating the final color of the pixel by bringing it all together
        col.rgb = min(half4(1,1,1,1), col.rgb * DecodeLightmap(lm) + col.rgb * spec);
        return col;
      }
      ENDCG
    }
  }
  Fallback "Diffuse"
}
Unity - ライトベイク済みプレハブ - ベイクレベル - MetalPop Games

セットアップとスタティックバッチング

このケースでは、すべてのプレハブが設定されている 4 つの異なるシーンを使います。私たちのゲームには熱帯、氷原、砂漠など、さまざまな生物群系があり、それに従ってシーンを分割しています。 

特定のシーンで使われるすべてのプレハブで 1 つのライトマップを共有します。つまり、マテリアルを 1 つだけ共有しているプレハブに、テクスチャーが 1 つだけ加わることになります。その結果、すべてのモデルの静的レンダリングと、ほぼ世界全体のバッチレンダリングを、たった 1 つのドローコールで実行できるようになりました。

すべてのタイルや建物が設定されているライトベイキングシーンには、ローカライズしたハイライトを生成するために光源を追加しています。いずれにしてもベイクダウンすることになるので、セットアップシーンには必要と思った分だけいくつでもライトを配置しても構いません。

Unity - ライトベイク済みプレハブ - カスタマーシェーダーを使用したマテリアルのセットアップ - MetalPop Games

カスタム UI ダイアログ

ベイクプロセスは、必要なすべてのステップを示してくれるカスタム UI ダイアログで進めます。これにより、以下のことが確認できます。

  • 正しいマテリアルがすべてのメッシュに割り当てられている
  • プロセス中にベイクする必要がないものがすべて非表示になっている
  • メッシュが結合/ベイクされている 
  • UVがコピーされ、プレハブが作成されている
  • すべてに正しく名前が付けられ、バージョン管理システムの必要なファイルがチェックアウトされている 

適切に命名されたプレハブがメッシュから作成されるので、ゲームコードでそれらを直接ロードして使うことができます。このプロセス中にメタファイルも変更されるので、プレハブのメッシュへの参照が失われることもありません。

このワークフローのおかげで、好きなだけ建物を調整し、好きなようにライティングして、スクリプトですべてのことを処理することができます。

メインシーンに戻ってゲームを実行すると、何ら問題なく動作します。手作業で何か行ったり、他の部分を更新したりする必要はありません。

Unity - ライトベイク済みプレハブ - 『Galactic Colonies』 - MetalPop Games

フェイクライトと動的ビット

ライティングを 100% 事前にベイクしたシーンの欠点の 1 つは、動的オブジェクトやモーションを使用しにくくなることです。影を投影する要素はすべて、リアルタイムでライトや影を計算する必要があります。言うまでもなく、これは何としても避けたいものです。

しかし、動くオブジェクトがまったくないと、3D 環境は静的で活気のない世界に見えてしまいます。

見栄えするビジュアルを高速レンダリングで実現することが最優先なので、ある程度の制限は仕方ありません。生き生きとした動きのあるスペースコロニーや都市という印象を生み出すためには、大量のオブジェクトをすべて実際に動き回らせる必要はありません。また、動かすものの大部分を影がなくてもよい、または少なくとも影がなくても気にならないものにします。

私たちは、まずすべての建物のブロックを 2 つのプレハブに分けました。1 つは静的プレハブで、頂点の大部分とメッシュの複雑なビットがすべて含まれます。もう 1 つが動的プレハブで、含まれる頂点はできるだけ少なくなるようにします。

プレハブの動的な部分は、アニメーション化されたビットで、静的な部分の上に配置されます。これらはライトベイクされません。私たちは、非常に高速で負荷が軽いフェイクライティングシェーダーを使って、オブジェクトが動的にライティングされているかのような錯覚を作り出しました。

また、オブジェクトには影を持たせないか、動的ビットの一部としてフェイクシャドウを作成します。私たちのケースではサーフェスの大部分がフラットなので、これが大きな障害になることはありませんでした。

動的ビットに影はありませんが、ないことを知ったうえでよく見てみれば、かろうじて気付く程度です。動的プレハブのライティングもフェイクです。リアルタイムのライティングはまったく使われていません。

最初に私たちは負荷を軽くするための近道として、光源(太陽)の位置をフェイクライティングシェーダーにハードコーディングしました。これはシェーダーがルックアップして世界から動的に入力する必要がある、一種の less 変数です。

動的な値を使うより定数を使った方が、当然処理は早くなります。これにより基本的なライティング、メッシュの明るい面と暗い面が得られました。

fake lighting shader.cs (C#)
Shader "Custom/FakeLighting" 
{
  Properties 
  {
    _Color ("Color", Color) = (1,1,1,1)
    _Brightness ("Brightness", Range(0,1)) = 0.4
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
  }
 
  SubShader 
  {
    Tags { "RenderType" = "Opaque" }
    LOD 200
    Pass
    {
      CGPROGRAM
 
      // Define name of vertex shader
      #pragma vertex vert
      // Define name of fragment shader
      #pragma fragment frag
 
      // Include some common helper functions, such as UnityObjectToClipPos
      #include "UnityCG.cginc"
 
      float4 _Color;
      float _Brightness;
 
      // Color Diffuse Map
      sampler2D _MainTex;
      // Tiling/Offset for _MainTex, used by TRANSFORM_TEX in vertex shader
      float4 _MainTex_ST; 
			
      // This is the vertex shader input: position, UV0, UV1, normal
      struct appdata
      {
        float4 vertex   : POSITION;
        float2 texcoord : TEXCOORD0;
        float3 normal: NORMAL;
      };
 
      // This is the data passed from the vertex to fragment shader
      struct v2f 
      {
        float4 pos  : SV_POSITION;
        float2 txuv : TEXCOORD0;
        float3 normalDir : TEXCOORD2;
      };
 
      // This is the vertex shader
      v2f vert(appdata v) 
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.txuv = TRANSFORM_TEX(v.texcoord.xy,_MainTex);
 
        // Calculating normal so it can be used for fake lighting
        // in the fragment shader
        float4x4 modelMatrixInverse = unity_WorldToObject; 
        o.normalDir = normalize(mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
		
        return o;
      }
 
      // This is the fragment shader
      half4 frag(v2f i) : COLOR
      {
        // Reading color from diffuse texture
        half4 col = tex2D(_MainTex, i.txuv.xy);
			
        // Using hard-coded light direction for fake lighting
        half3 th = normalize(half3(0.25, 1, -0.25));
        // Using hard-coded light direction for fake specular
        // This matches the value inside the LightmappedPrefabWithSpec shader
        half3 sth = normalize(half3(0, 1, -0.25));
 
        // Fake lighting
        float lightVal = max(0, dot (i.normalDir, th));
        float lightScale = 0.75;
        lightVal = lightVal * lightScale;
 
        // Fake spec
        float spec = max(0, dot(i.normalDir, sth));
        float specScale = 0.65;
        float specAtt = 0.65;
        spec = specScale * pow (spec, 40.0 * specAtt);
 
        // Add in a general brightness (similar to ambient/gamma) and then
        // calculate the final color of the pixel
        col.rgb = min(half4(1,1,1,1), col.rgb * _Brightness + 
                      col.rgb * lightVal * _Color + col.rgb * spec);
        return col;
      }
 
      ENDCG
    }
  }
  FallBack "Diffuse"
}
Unity - ライトベイク済みプレハブ - スペキュラー/グロス - MetalPop Games

スペキュラー/グロス

もう少し光沢を加えるため、私たちは動的オブジェクトと静的オブジェクトの両方のシェーダーにフェイクのスペキュラー/グロス計算を追加しました。スペキュラー反射はメタリックな見た目を演出する効果がありますが、表面のカーブも表現されます。

スペキュラーハイライトは反射の一種なので、カメラと光源の相対的な角度を正しく計算する必要があります。カメラが移動したり回転したりすると、スペキュラーは変化します。計算を行うすべてのシェーダーで、シーン内のカメラの位置とすべての光源へのアクセスが必要になります。

ただし、私たちのゲームではスペキュラーのために必要な光源は 1 つだけ、太陽しかありません。ゲーム内では太陽は動かず、また、ディレクショナルライトとみなすことができます。使うライトを 1 つだけにして、ポジションと入射角が固定されていると想定したことで、シェーダーを大幅に簡略化できました。

加えて『Galactic Colonies』のカメラが、多くの都市建築ゲームと同じく見下ろし視点でシーンを映すタイプのものであることも功を奏しました。カメラは若干傾けたりズームインやズームアウトしたりできますが、上向き軸を中心に回転することはできません。

全体的に見れば、カメラは常に環境を上から見下ろしている状態です。スペキュラーの見た目を低負荷で偽装するため、私たちはカメラが完全に固定されていて、カメラとライトの角度が常に一定であるということにしました。

この方法で、改めて固定値をシェーダーにハードコーディングして、低負荷のスペキュラー/グロスエフェクトを実現できました。

スペキュラーに固定角を使うのは、もちろん技術的には正しくありませんが、実際にはカメラの角度がほぼ固定の場合と見分けはつきません。

プレイヤーにはあいからわずシーンは問題なく見えるわけで、リアルタイムライティングではそれだけが重要なのです。 

リアルタイムビデオゲームでの環境のライティングは、物理的に正しくシミュレートされていることよりも、正しく見えていることの方が常に重要なのです。 

ほとんどすべてのメッシュで 1 つのマテリアルを(ライトマップと頂点に由来する多くのディテールと合わせて)共有していたので、私たちはシェーダーにスペキュラーの値をいつどこに、どのくらいの強度で適用すればよいのかを伝えるためにスペキュラーテクスチャーマップを追加しました。テクスチャーには 1 番目の UV チャンネルを使ってアクセスするので、座標セットを追加する必要はありません。また、それほど詳細がないので解像度が非常に低く、ほとんどと言っていいほどスペースを使いません。

頂点数が少ない小さな動的ビットの一部については、Unity の自動ダイナミックバッチング機能を利用して、レンダリングをさらにスピードアップできました。

Unity - ライトベイク済みプレハブ - スタックされたプレハブ - MetalPop Games

ライトベイク済みプレハブのスタック

ベイクしたこれらの影は、どれも新しい問題を引き起こす可能性を秘めています。特に比較的モジュール化の度合いが高い建造物に使う場合です。ある時私たちは、プレイヤーが建築できる倉庫の前に、保管されている商品の種類を表示させようとしました。

これが問題を引き起こしました。ライトベイクしたオブジェクトにライトベイクしたオブジェクトを重ねたからです。いわば、ライトベイクの例外が発生したのです!

別の負荷軽減テクニックを使ってこの問題に取り組むことにしました。

  • 追加オブジェクトが追加される予定であったサーフェスはフラットにし、特定のグレーカラーを使用してベースの建物と調和させる必要があった
  • そのトレードオフとして、より小さなフラットサーフェス上のオブジェクトをベイクし、それをわずかなオフセットのみでその領域の上に配置できた
  • ライト、ハイライト、色付きの光と陰影がすべてタイルにベイクされた
Unity - ライトベイク済みプレハブ - MetalPop Games

制約はあります

この方法でプレハブを構築してベイキングしたことで、ドローコール数を驚くほど少ない水準に抑えつつ、数百もの建造物がある巨大なマップを実現できました。私たちのゲームでは、世界のほぼ全部がたった 1 つのマテリアルでレンダリングされ、ゲームの世界よりも UI の方がたくさんのドローコールを使うほどです。Unity がレンダリングしなければならないマテリアルが少なければ少ないほど、ゲームのパフォーマンスは向上します。

これにより、パーティクル、天候効果、その他の装飾的な要素を追加する余地が生まれます。

この方法なら、比較的古いデバイスを使っているプレイヤーでも、安定した 60fps を維持したまま数百もの建造物が立ち並ぶ巨大な都市を作ることができます。

弊社のウェブサイトは最善のユーザー体験をお届けするためにクッキーを使用しています。詳細については、クッキーポリシーのページをご覧ください。

OK