ランタイムで環境光の影響を反映させる方法

f:id:gounsx:20180607162417p:plain

キーワード

  • 環境光
  • 環境マップ
  • Cubemap
  • Reflection Probe

skyboxを動的に変更すると見た目がおかしくなる!?

ランタイムでskyboxを変更したところ、環境光が適切に反映されず、一部見た目がおかしい結果となる症状に遭遇しました。

今回はSkyboxによる環境光の影響を適切に反映してあげることで見た目をよくしたいと思います。

Unityにおける環境マップ

UnityではSkyboxにSkybox用のシェーダーを割り当てたマテリアルを設定してあげることで特に追加の設定は必要なく、環境光をオブジェクトに反映させてげることができます。

変化がわかりやすいように新規でStandardShaderを割り当てたマテリアルを作成し、MetallicSmoothness1にしてパチンコ玉のような見た目にします。

f:id:gounsx:20180607163612p:plain

DefaultのSkyboxではこのようにパチンコ玉にSkyboxが映り込んでいるのを確認することができます。

こちらのskyboxを違うskyboxに変更するとこういった結果が得られます。

f:id:gounsx:20180607163944p:plain

これは全天球画像から作られたskyboxで、劇場の椅子がしっかりと映り込んでいます。

このようにEditor停止中では適切な結果を得ることができるのですが、skyboxから行われる環境光の計算はランタイムでは動的に切り替わりません。

youtu.be

このskyboxの切り替え時にどういった処理が走っているかというと、環境マップとしてcubemapの生成を行います。

ランタイムで更新(cubemapの再生成)するにはそれなりのコストがかかるため、エディタ停止中には動作し、ランタイムでは動作しないというのはUnityエディタでそのコストを吸収しているためだと思われます。

試しにLighting ウィンドウのAuto Generateをオフにすると、環境光の影響が反映されなくなります。

そして、その右側にあるGenerate Lightingをクリックすると、シーン名フォルダが生成され、その中にcubemapが生成されます。 つまりこれをEditor停止中に内部的に生成しているということです。

この時生成したcubemapをskybox変更時にEnviroment ReflectionsSourceCustomに変更し、設定することで環境光の影響を受けるようにすることができます。

動的にcubemapを生成し、環境光の影響を適切に反映されるようにするには

今回はskybox変更時に、cubemapを生成し環境マップとして設定してあげる方向で修正を行いたいと思います。

cube mapの生成するにはいくつかの方法があります。

1つ目は、Reflection Probeを配置する。2つ目は、Camera.RenderToCubemap()を使用する方法です。

Reflection Probe

前者は非常に簡単でHierarchyからCreateLightReflection Probeと選択し、シーンに配置するだけです。

いくつかパラメータがあるのですが、TypeRealtimeReflesh ModeEvery frameにするとマイフレーム更新することができます。

f:id:gounsx:20180608181425p:plain

マイフレームではかなりのコストがかかるので、ReflectionProbe.RenderProbe()を呼ぶことで任意のタイミングで更新をかけることができます。

Camera.RenderToCubemap()

こちらはスクリプトを作成する必要があります。

参考までにcube mapを生成するスクリプト、生成したcube mapを設定するスクリプトを載せておきます。

RenderCubemap.cs

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

public class RenderCubemap : MonoBehaviour
{
    static public Cubemap Generate()
    {
        GameObject cubeMapCamera = new GameObject("CubemapCamera");
        Cubemap cubemap = new Cubemap(64, TextureFormat.RGBA32, true);
        var camera = cubeMapCamera.AddComponent<Camera>();
        cubeMapCamera.transform.position = Vector3.zero;
        cubeMapCamera.transform.rotation = Quaternion.identity;
        int layerMask = 0;
        camera.cullingMask = layerMask;
        camera.allowHDR = true;
        camera.RenderToCubemap(cubemap);
        DestroyImmediate(cubeMapCamera);
        RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom;
        RenderSettings.customReflection = cubemap;

        return cubemap;
    }
}

CubemapTester.cs

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

public class CubemapTester : MonoBehaviour
{
    [ContextMenu("Change Cubemap")]
    public void ChangeCubemap()
    {
        RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom;
        RenderSettings.customReflection = RenderCubemap.Generate();
    }
}

使い方はCubemapTester.csのChangeCubemap()を実行します。

f:id:gounsx:20180608181122p:plain

このように、Reflection ProbeもしくはCamera.RenderToCubemap()を使用し、任意のタイミングで更新をかけてあげることで動的に環境光を反映させることができるようになりました。

まとめ

cubemapの解像度や、HDRの有効無効の設定が異なるので上記の2つの方法での結果が少しずつことなっていますが、自分のプロジェクトに最適な方法を使用することをお勧めします。

見た目にかかわる部分なので、各種パラメータいじってみて理想の絵作りを追及してください!