Categories: Unity

Unity:ライトマップデータをゲーム実行中に切り替えてライティングを変える

今回はライトマッピングを使用した室内シーンの照明でゲーム実行中にライトマップデータを差し替えてライティングを変更する方法を試してみました。

シーンのライティング

ライトマップをベイクする Area Light のみを使用して明るい照明と暗い照明を配置したテスト用のシーンを用意します。ライトプローブとリフレクションプローブを配置しています。

明るい照明はややオレンジ色に調整してこんな感じになりました。

暗い照明は床に配置して色をグリーン調整。こんな感じになりました。

Lighting 設定について

ライティング設定は WebGLビルドに合わせて調整。
メニュー:Window > Rendering > Lighting Settings を選択。[Lighting]ウィンドウ > [Scene]タブを選択。シーンは室内のみでライトプローブとリフレクションプローブを配置しているので環境ライティングは影響を失くし、WebGLビルドでサポートされていない Realtime Lighting のチェックは外します。

Lightmapping Settings > Directional Mode もWebGLビルドでサポートされていないので通常は Non-Directional に設定しますが。今回はスクリプト検証のために Directional に設定しました。

WebGL グラフィックス – Unity マニュアル
Unity WebGL は Baked GI のみをサポートしています。 Realtime GI は WebGL では今のところサポートされていません。なお、Non-Directional ライトマップのみがサポートされています。

ライトマップ – Directional Mode – Unity マニュアル
Non-Directional ライトマップは平らな拡散を生成します。このモードはただ 1 つのライトマップを使用しており、光が純粋に拡散性であると仮定して、どれだけ多くの光を表面から放出するかという情報を保存します。オブジェクトはこの方法で照らされると、平らに見えます (法線マップは使用されません)。

ライトマップデータの出力

明るい照明の状態から暗い照明に切り替えるにはライトマップデータを書き出し、ファイルを差し替える処理を行います。そこでシーンを暗い照明の状態にしておき、ライトマップデータをファイルに書き出します。

ライトマップファイルの出力

[Lighting]ウィンドウ > [Scene]タブ > Auto Generate チェックを外し、[Generate Lighting]ボタンをクリック。

シーン名と同じフォルダが作成されて、ライトマップファイルが出力されました。

ライトプローブファイルの出力

先程出力したライトマップデータにはキャラクターのように動くオブジェクトにライティングを反映するライトプローブファイル含まれていません。調べてみると、ライトプローブファイルはアセットファイルとして現在のシーンから個別に出力する必要があるということなので、エディタ用スクリプトを作成します。

ビルドの際にエディタ用スクリプトを除外するために新しく Editor フォルダを作成します。 [Project]ウィンドウ > [Create] > Folder を選択。名前をEditor に変更。フォルダをダブルクリックして階層を移動してから [Project]ウィンドウ > [Create] > C# Script を選択。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// シーン管理のライブラリを使用する宣言
using UnityEngine.SceneManagement;
// エディタ関連ライブラリを使用する宣言
using UnityEditor;
// システム入出力のライブラリを使用する宣言
using System.IO;

public class ExportLightprobe
{
    // エディタのメニューに項目を追加
    [MenuItem("Export/Export LightProbe")]

    // ライトプローブファイルの出力処理
    static void Export()
    {
        // 変数 scene を作成して現在開いているシーンを格納
        Scene scene = SceneManager.GetActiveScene();
        // 変数 path を作成してシーン名を含む階層情報を格納
        string path = "Assets/" + Path.GetFileNameWithoutExtension(scene.name);
        // 変数 path の場所にシーン名のフォルダを作成
        Directory.CreateDirectory(path);
        // ライトマップ設定のライトプローブファイルのアセットを作成して変数 path の場所に出力
        AssetDatabase.CreateAsset(GameObject.Instantiate
            (LightmapSettings.lightProbes), path + "/Lightprobe.asset");
    }
}

メニュー項目に Export が追加されました。スクリプトファイルを作成するだけでメニューを拡張できるとは、ちょっと不思議な感じです。

追加されたメニュー:Export > ExportLightprobe を選択すると現在開いているシーンのライトプローブがシーン名と同じフォルダにアセット(拡張子 asset )として書き出されます。

ライトマップデータの複製とリネーム

出力したライトマップデータから法線ライトマップ、ライトマップ、リフレクションプローブを複製(Ctrl キー + D キー)し、別ファイルにしてからリネームします。

このようにリネームしました。

  • ライトマップ:Lightmap-0_comp_light 1 → Lightmap-Dark_comp_light
  • 法線ライトマップ:Lightmap-0_comp_dir 1 → Lightmap-Dark_comp_dir
  • リフレクションプローブ:ReflectionProbe-1 → ReflectionProbe-Dark
  • ライトプローブ:Lightprobe → Lightprobe-Dark
ライトマップデータを複製せずにそのままリネームすると [Lighting]ウィンドウ > [Scene]タブ > Auto Generate チェックを入れたとたんにライティング計算が始まりファイルが削除されてしまいます。

明るい照明のライトマップデータを出力

暗い照明のライトマップデータの出力ができたので、シーンを明るい照明に戻し、 [Lighting]ウィンドウ > [Scene]タブ > [Generate Lighting]ボタンをクリック。元のライトマップデータは上書きされます。リフレクションプローブだけ複製します。
メニュー:Export > ExportLightprobe を選択して明るい照明のライトプローブを出力します。

このようにリネームしました。

  • リフレクションプローブ:ReflectionProbe-1 → ReflectionProbe-Bright
  • ライトプローブ:Lightprobe → Lightprobe-Bright

出力したすべてのライトマップデータを新しいフォルダ LightmapData を作成して移動します。必要なライトマップデータが揃いました。

ライトマップデータを切り替えるスクリプトの作成

ライトマップデータを切り替えるオブジェクトとスクリプトを作成します。

スクリプトを割り当てるオブジェクトの作成

[Hierarchy]ウィンドウ > [Create] > Create Empty を選択。名前を Corevale_SwapLightmap に変更。[Inspector]ウィンドウ > [Add]Component]ボタンをクリック。新しいスクリプトファイル SwapLightmap を作成します。

スクリプトの編集

スクリプトの内容はこのようになっています。UIのトグルボタンでライトマップデータを切り替えるための処理を入れています。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Corevale_SwapLightmap : MonoBehaviour
{
    // ライトマップファイルと法線ライトマップを格納する配列
    [SerializeField] Texture2D[] lightMap;
    // 法線ライトマップを格納する配列
    [SerializeField] Texture2D[] dir;
    // ライトプローブファイルを格納する配列
    [SerializeField] LightProbes[] lightProbe;
    // ReflectionProbeファイルを格納する配列
    [SerializeField] Cubemap[] reflectionProbe;
    // リフレクションプローブを格納する変数
    [SerializeField] ReflectionProbe probeComponent;
    // 明るい照明のライトマップデータ
    LightmapData[] brightDatas;
    // 暗い照明のライトマップデータ
    LightmapData[] darkDatas;
    // 現在のライトマップデータが暗い照明か判定するフラグ
    public bool swapLight = false;

    // ゲームスタート時の処理
    void Start()
    {
        // 現在のシーンのライトマップデータを変数 brightDatas に格納
        //(明るい照明のライトマップデータに格納)
        brightDatas = LightmapSettings.lightmaps;
        // ライトマップファイル lightMap の長さを取得
        //(暗い照明のライトマップデータ darkDatas に格納)
        darkDatas = new LightmapData[lightMap.Length];
        // 繰り返し処理:暗いライトマップデータ、法線ライトマップのファイル数分
        for (int i = 0; i < darkDatas.Length; i++)
        {
            // ライトマップデータ lightingData を作成
            LightmapData lightingData = new LightmapData();
            // 配列 light に格納したライトマップファイルをライトマップデータに格納
            lightingData.lightmapColor = lightMap[i];
            // 配列 dir に格納した法線ライトマップファイルをライトマップデータに格納
            lightingData.lightmapDir = dir[i];
            // ライトマップデータ lightingData に格納された
            // ライトマップファイルと法線ライトマップファイルを配列 lightingDatas に格納
            darkDatas[i] = lightingData;
        }
    }

    // ライトマップデータの切替処理
    public void SwapLightmapData()
    {
        // 変数 swapLight が false の場合
        if (!swapLight)
        {
            // DarkLightmap 処理を実行
            DarkLightmap();
            // 変数 swapLight を true に変更
            swapLight = true;
        }
        // 変数 swapLight が true の場合
        else if (swapLight)
        {
            // BrightLightmap 処理を実行
            BrightLightmap();
            // 変数 swapLight を false に変更
            swapLight = false;
        }
    }

    // 暗い照明のライトマップデータを現在のシーンに適用する処理
    void DarkLightmap()
    {
        // 暗い照明のライトマップファイルを現在のライトマップ設定に適用
        LightmapSettings.lightmaps = darkDatas;
        // 暗い照明のライトプローブファイルを現在のライトマップ設定に適用
        LightmapSettings.lightProbes = lightProbe[1];
        // 暗い照明のリフレクションプローブファイルを現在のリフレクションプローブに適用
        probeComponent.customBakedTexture = reflectionProbe[1];
    }

    // 明るい照明のライトマップ設定を現在のシーンに適用する処理
    void BrightLightmap()
    {
        // 明るい照明のライトマップファイルを現在のライトマップ設定に適用
        LightmapSettings.lightmaps = brightDatas;
        // 明るい照明のライトプローブファイルを現在のライトマップ設定に適用
        LightmapSettings.lightProbes = lightProbe[0];
        // 明るい照明のリフレクションプローブファイルを現在のリフレクションプローブに適用
        probeComponent.customBakedTexture = reflectionProbe[0];
    }
}

コンポーネントの設定

スクリプトが完成したので設定を行います。

SwapLightmap (Script) コンポーネントの設定

スクリプトを割り当てたオブジェクト SwapLightmap を選択して[Inspector]ウィンドウで確認するとこのように変数と配列のフィールドが表示されています。

配列数を変更して表示されたフィールドにそれぞれライトマップデータのファイルを割り当てます。Probe Component フィールドにはシーンに配置してあるライトプローブオブジェクトを割り当てます。

ReflectionProbe コンポーネントの設定

リフレクションプローブのコンポーネントの設定はデフォルトでType > [Bake]になっています。(シーンの静的オブジェクトをベイクしたキューブマップを使用)
スクリプトからリフレクションプローブのキューブマップファイルを切り替えるために設定を[Custom]に切り換え、明るい照明のリフレクションプローブ ReflectionProbe-Bright を割り当てておきます。

UIオブジェクトの作成

ライトマップデータを切り替えるためのトグルボタンを作成します。[Hierarchy]ウィンドウ > UI > Toggle を選択。
[Inspector]ウィンドウ > Toggle (Script) > On Value Changed (Boolean) にボタンの値が変わったときに実行される処理を設定。オブジェクトに SwapLightmap を割り当て、関数 Corevale_SwapLightmap > SwapLightmapData() を選択。

動作テスト

テスト用にmetallic の値を 1 に設定したマテリアルを割り当てた Sphere オブジェクトをシーンに配置して再生。トグルボタンを切り替えるとライトマップとリフレクションプローブのキューブマップが切り替わりました!(画像クリックで拡大)

WebGLでビルド

動作確認したシーンにライトプローブの切り換えが確認できるようにプレイヤーキャラクター Robot Kyle を配置。リフレクションプローブの切り換えが分かりやすいように Capsule オブジェクトを頭に追加してみました。

以前の記事 プレイヤーキャラクターをマウスクリックした位置に移動させる の内容と同じ方法を使用してマウスクリックで操作できるようにしました。
画像クリックで再生(ファイルサイズ:約18MB)

参考記事

今回の記事はこちらのWebサイトの記事を参考にしました。感謝!
【Unity】モバイル向けのライトマップTipsと、ライトマップを動的に更新するHack – テラシュールブログ

Unity 5はリアルタイムな光源を基本としている節がありますが、モバイル上では依然としてパフォーマンスを稼ぐためライトマップをベイクする方が良いです。 今回はそのライトマップの色々なアプローチについて紹介します。 ライトを焼く ライトマップを焼く時間を短縮する リアルタイムGIをOFFに 重要度の低いライトマップの解...

How to create lightmap for day and for night and switch them in runtime. – Unity Forum

Hi everyone! I am newby in unity5 and right now i am working on some project for mobile platforms, that should switch to Day and Night mode. I did setup scene...
corevale

View Comments