夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Blenderでサーバーを立てMessagePackでメッシュを送信する その⑧ Unityでマテリアルを認識してマテリアルスロットに登録する

本日はMixedRealityModelingTools枠です。

現在Blenderでサーバーを立てUnityと相互に通信を行うアプリケーションを作成しています。

昨日はヘッダー情報を送信データに埋め込み、Blenderから送信するデータをメッシュと、マテリアルで区別することを実装しました。

今回はマテリアルをUnity側で受け取った後にUnityのマテリアルとして使用する点について紹介します。

〇MaterialDataの定義

今回はMessagePackを使用しており、これではバイナリデータをJsonのような形式にして送信する機能です。

これをデシリアライズするためにUnity側でクラスとしてデータのフォーマットを定義しています。

メッシュの場合は次のように定義しています。 

 [DataContract]
    public class MeshData
    {
        [DataMember]
        public string header;
        
        [DataMember]
        public string objectname;
        
        [DataMember]
        public List<float> vertices;
    
        [DataMember]
        public List<int> triangles;
    
        [DataMember]
        public List<float> normals;
    }

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

同様にマテリアルのデータについても形式を定義する必要があります。

これはBlenderのデータに合わせる必要があり次のようになります。

   [DataContract]
    public class MaterialData
    {
        [DataMember]
        public string header;
        
        [DataMember]
        public List<string> materialname;
        
        [DataMember]
        public List<float> rgba;
    }

〇カスタムリゾルバーの定義

MaterialDataクラスによってデータのフォーマットは定義されました。

次にカスタムリゾルバーを定義します。 カスタムリゾルバーとはMessagePackでデータをデシリアライズする際に今回のようにフォーマットがデフォルトと異なる場合などに使用するリゾルバーです。

    public class CustomMaterialResolver : IFormatterResolver
    {
        public static readonly IFormatterResolver Instance = CompositeResolver.Create(
            new IMessagePackFormatter[] { new MaterialDataFormatter() }, // MaterialDataFormatterを追加
            new IFormatterResolver[] {
                MaterialDataResolver.Instance, // MaterialDataResolverを追加
                StandardResolver.Instance,
                UnityResolver.Instance
            }
        ); 
        public IMessagePackFormatter<T> GetFormatter<T>()
        {
            return Instance.GetFormatter<T>();
        }
    }

これを定義することでデシリアライズを行う際に独自の形式で行うことができます。

〇デシリアライズ

次にTCPクライアントにデシリアライズの処理を定義します。

これは先ほど作成したMaterialDataクラスとCustomMaterialResolverを使用していきます。

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using MessagePack;


namespace MixedRealityModelingTools.Core
{
    [RequireComponent(typeof(ObjectBuilder))]
    public class TCPClient : MonoBehaviour
    {
       ・・・
        public void StartConnection()
        {
            _client = new TcpClient(_ipAddress, _port);
            _stream = _client.GetStream();

            _objectBuilder = GetComponent<ObjectBuilder>();
            // Start a new thread to listen for incoming messages
            new Thread(() =>
            {
                ・・・
                }
            }).Start();
            UpdateConnectionStatus();
        }

        private void ProcessReceivedData(byte[] data, int length)
        {
            // Get the header from the received data
            string header = Encoding.ASCII.GetString(data, 9, 4);
            Debug.Log(header);
            if (header == "MESH")
            {
    ・・・
            }
            else if (header == "MATE")
            {
                var materialData = DeserializeMatData(data, length);
                _objectBuilder._materialData = materialData;
                _objectBuilder._isGetMaterialData = true;
                Debug.Log($"Received material data: {materialData.materialname}");
            }
            else
            {
                Debug.Log($"Received unknown data with header: {header}");
            }
        }

        void Update()
        {
            // Send a message when the H key is pressed
            if (Input.GetKeyDown(KeyCode.H))
            {
                var bytes = Encoding.ASCII.GetBytes("Hello, Blender!");
                _stream.Write(bytes, 0, bytes.Length);
            }

            UpdateConnectionStatus();
        }

   ・・・

        //////////////////ConvertData//////////////////
  ・・・
        MaterialData DeserializeMatData(byte[] data ,int length)
        {
            var option = MessagePackSerializerOptions.Standard.WithResolver(CustomMaterialResolver.Instance);
            return MessagePackSerializer.Deserialize<MaterialData>(data, option);
        }
    }
}

DeserializeMatDataではCustomMaterialResolverをオプションとしてMaterialData形式にデシリアライズして返します。

こうして得られたマテリアルデータを

                var materialData = DeserializeMatData(data, length);
                _objectBuilder._materialData = materialData;
                _objectBuilder._isGetMaterialData = true;
                Debug.Log($"Received material data: {materialData.materialname}");

ObjectBuilderクラスのMaterialDataとして代入します。

〇マテリアルの作成

ObjectBuilderクラスではMaterialDataをもとに次のような処理でマテリアルを構築しています。

   public void CreateMaterial(MaterialData materialData)
        {
            for (int i = 0; i < materialData.materialname.Count; i++)
            {
                if (!_blenderMat.Exists(x => x.name == materialData.materialname[i]))//マテリアル名が既存のものでない場合
                {

                    Material mat = new Material(Shader.Find("Standard")); //マテリアルを作成※URPの場合シェーダーに注意                       
                   
                    mat.name = materialData.materialname[i];//マテリアル名を更新
                    mat.color = new Color(materialData.rgba[0], materialData.rgba[1], materialData.rgba[2]);//カラーを更新
                    _blenderMat.Add(mat);//リストに追加
                }
                else//既存のマテリアルを更新する場合
                {
                    //UpdateMaterial
                    Material mat = _blenderMat.Find(x => x.name == materialData.materialname[i]);
                    mat.color = new Color(materialData.rgba[0], materialData.rgba[1], materialData.rgba[2]);  //色を最新データに更新                  
                }
            }
        }

以上でマテリアルを認識してUnity側でマテリアルとして構築、更新する仕組みができました。