【Unity】Material(Shader)のPropertyに別値を設定!

第2開発部エンジニアのi.J.です。
Unity開発でMaterial(Shader)について実際におこなったことを共有したいと思います。

  • Unity 2022.3.19

点群を構成している各点に対して標高に応じて色を設定するヒートマップ表示に対応することになりました。
点群オブジェクトが複数あり、それらに同じMaterial(Shader)がアタッチされています。点の色はShader内で点の標高値によって設定する方針となりました。
ところが、点群オブジェクトごとに標高の補正値を渡さないといけないことなりました。Shaderに標高補正値のPropertyを作成したものの、それぞれの点群オブジェクトには同じMaterialがアタッチされています。そのため普通にそのPropertyに値を設定すると、すべての点群オブジェクトには同じ標高補正値が渡ってしまいます。
これを解決する方法を探す必要がありました。

「MaterialPropertyBlock」を使用すると解決できることがわかりました!

解決方法

  1. 点群オブジェクトにアタッチされているRenderer(今回はMeshRenderer)を取得
  2. MaterialPropertyBlockのインスタンスを作成して、それに対してShaderに作成した標高補正値のProperty名(変数名)を指定して標高補正値をセット
  3. RendererのSetPropertyBlockメソッドを使用し、その引数に上記のMaterialPropertyBlockのインスタンスをセット
  4. 1~3を点群オブジェクトごとに実施

これで同じMaterial(Shader)がアタッチされている点群オブジェクトごとに異なる標高補正値を渡すことができるようになりました。

2つの3Dオブジェクトに同じMaterial(Shader)をアタッチして、異なる色になるようなサンプルを作成したいと思います。

1. 3DオブジェクトにアタッチするためのShaderを作成してMaterialにアタッチ

Shader "Unlit/SampleShader"
{
    Properties
    {
        _Color("Color", Color) = (255, 255, 255, 255)
    }        

    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
            };

            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.color = _Color;
                return o;
            }

            fixed4 frag (v2f i) : COLOR
            {
                return i.color;
            }
            ENDCG
        }
    }
}

2. SceneにCubeとSphereを配置して、作成したMaterialをアタッチ

CubeとSphereに同じMaterial(Shader)をアタッチ

ここで設定方法の比較のためにHierarchyでCubeを選択して、Materialの色を赤に変更。
普通に色を変更すると、Sphereの方も同じ色に変わってしまいます。

通常の色設定ではCubeとSphereの色が同じになる

3. CubeとSphereを異なる色に設定するためのScriptを作成
CubeとSphere用のMaterialPropertyBlockを作成して異なる色を設定、それぞれのMeshRendererのSetPropertyBlockメソッドの引数にセット

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

public class ColorSetterScript : MonoBehaviour
{
    [SerializeField]
    private GameObject _cube;

    [SerializeField]
    private GameObject _sphere;

    private void Start()
    {
        var cubeMeshRenderer = _cube.GetComponent<MeshRenderer>();
        var cubePropertyBlock = CreatePropertyBlock(Color.green);
        cubeMeshRenderer.SetPropertyBlock(cubePropertyBlock);

        var sphereMeshRenderer = _sphere.GetComponent<MeshRenderer>();
        var spherePropertyBlock = CreatePropertyBlock(Color.cyan);
        sphereMeshRenderer.SetPropertyBlock(spherePropertyBlock);
    }

    private MaterialPropertyBlock CreatePropertyBlock(Color col)
    {
        var block = new MaterialPropertyBlock();
        block.SetColor("_Color", col);
        return block;
    }
}

4. 空のGameObjectを作成して、作成したScriptをアタッチ(ScriptにCubeとSphereをアタッチ)

空のGameObjectにColorSetterScriptをアタッチ

5. 実行

実行した様子
同じMaterial(Shader)がアタッチされているにもかかわらず、CubeとSphereが異なる色で表示されるようになりました!

同じMaterial(Shader)がアタッチされているオブジェクトごとに色等を異なる値に設定したい場合、それぞれのオブジェクト用のMaterial(Shader)を用意しても実現することはできますが、あまりにたくさんの場合、同じ内容のMaterial(Shader)を複数用意するのもあまりスマートな感じがしないと思いますので、「MaterialPropertyBlock」利用する価値は十分にあると思います。

KENTEMでは、様々な拠点でエンジニアを大募集しています!
建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。
hrmos.co