MRが楽しい

MRやVRについて学習したことを書き残す

MRTK v2のドキュメントを少しずつ読み解く マテリアルインスタンス

本日は MRTKv2 の調査枠です。
MRTKv2 の Guides ドキュメントを少しずつ読み進めていきます。

MRTKv2のGuidesドキュメント

以下のドキュメントを読み進めていきます。
microsoft.github.io

以下のページでは有志による本ドキュメントの日本語翻訳が行われています。
投稿時点でこちらで未翻訳、または著者が興味のある部分について記事にしていきます。
hololabinc.github.io

本記事では以下のページを読み進めます。
microsoft.github.io
f:id:bluebirdofoz:20200323030413j:plain

マテリアルインスタンス

MaterialInstance の動作は、インスタンスマテリアルの有効期間を追跡し、ユーザーのインスタンス化されたマテリアルを自動的に破棄します。
このユーティリティコンポーネントは Renderer.material または Renderer.materials の代わりとして使用できます。

MaterialPropertyBlocksはマテリアルのインスタンス化よりも優先されますが、全てのシナリオで常に使用できるとは限りません。

Renderer.material を使用することが問題になるのは何故か。
以下のコードをUnityシーンに追加してプレイを実行すると、メモリ使用量が上昇し続けます。

public class Leak : MonoBehaviour
{
    private void Update()
    {
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        // Memory leak, the material allocated is not tracked & destroyed.
        // メモリリーク。割り当てられたマテリアルは追跡および破棄されません。
        cube.GetComponent<Renderer>().material.color = Color.red;
        ...
        Destroy(cube);
    }
}

このリーク動作は、実行時間が長すぎるとUnityをクラッシュさせます!

別の方法として、MaterialInstance 動作を使用してみてください。

public class NoLeak : MonoBehaviour
{
    private void Update()
    {
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        // No memory leak, the material allocated is tracked & destroyed by MaterialInstance.
        // メモリリークはありません。割り当てられたマテリアルは、MaterialInstanceによって追跡および破棄されます。
        cube.EnsureComponent<MaterialInstance>().Material.color = Color.red;
        ...
        Destroy(cube);
    }
}

使用法

Unity の Renderer.material(s) を呼び出すと、Unity は新しいマテリアルを自動的にインスタンス化します。
マテリアルが不要になったとき、またはゲームオブジェクトが破棄されたとき、マテリアルを破棄するのは呼び出し側の作業です。
MaterialInstance の動作は、マテリアルリークの回避に役立ち、編集時および実行時のマテリアル割り当てパスの一貫性を維持します。

MaterialPropertyBlock を使用できず、マテリアルをインスタンス化する必要がある場合、MaterialInstance は次のように使用できます。

public class MyBehaviour : MonoBehaviour
{
    // Assigned via the inspector.
    // インスペクターを介して割り当てられます。
    public Renderer targetRenderer;

    private void OnEnable()
    {
        Material material = targetRenderer.EnsureComponent<MaterialInstance>().Material;
        material.color = Color.red;
        // ...
    }
}

f:id:bluebirdofoz:20200323030431j:plain

複数のオブジェクトがマテリアルインスタンスを必要とする場合、参照追跡のために明示的な所有権を取得するのが最善です。
所有権を支援するために、IMaterialInstanceOwner と呼ばれるオプションのインターフェースが存在します。

以下は使用例です。

public class MyBehaviour : MonoBehaviour,  IMaterialInstanceOwner
{
    // Assigned via the inspector.
    // インスペクターを介して割り当てられます。
    public Renderer targetRenderer;

    private void OnEnable()
    {
        Material material = targetRenderer.EnsureComponent<MaterialInstance>().AcquireMaterial(this);
        material.color = Color.red;
        // ...
    }

    private void OnDisable()
    {
        targetRenderer.GetComponent<MaterialInstance>()?.ReleaseMaterial(this);
    }

    public void OnMaterialChanged(MaterialInstance materialInstance)
    {
        // Optional method for when materials change outside of the MaterialInstance.
        // マテリアルがMaterialInstanceの外部で変更される場合のオプションメソッド。
        // ...
    }
}

f:id:bluebirdofoz:20200323030441j:plain