■概要
EntityのMaterialのプロパティーをコードから変更する。
(DOTS初学者なのでコードとか諸々かも)
■やること
- HybridRendererの導入
- ShaderGraphでMaterialを作成
- PrefabからEntityを何個か作成して、それらのMaterialのプロパティーをSystemクラスからいじる
- 猫の腹を撫でる
■サンップル
■環境
UnityEngine : 6000.0.23f1
Entities : 1.3.2
Entities Graphics : 1.3.2
Hybrid Renderer : 0.5.2-preview.4
猫 : 3歳
■リファレンス
↓ECSで変更したいMaterialの設定
↓コードでMaterial作る場合に必要そうなやつ。
ShaderGraphで作るならいらない。
■導入
Entities、Entities Graphicsはパッケージマネージャーから入れられる。
Entitiesより先にEntities Graphicsを入れたほうが良いです。
Entities Graphicsを入れると、同時に同バージョンのEititiesも追加されます。
しかし、現状HybridRendererはパッケージマネージャーから検索しても出てこない。
なので直接manifest.jsonに書き込む。
(プロジェクト名/Packages/manifest.json)
"com.unity.rendering.hybrid": "0.5.2-preview.4",
(この時点で赤文字がいっぱいでてきたりするけど怖いからClear。レッチュゴー)
■マテリアルの作成
今回はMaterialをShaderGraphで作成してます。
コードで作る場合は、ShaderGraphではデフォルトで設定されている部分も書く必要があるらしい。
今回は適当にColor,float,Vector4(*1)のプロパティーを触れるマテリアルを作ってみる。
(*1)実際はvector2,vector3で良いプロパティーも現状エラーが出るのでvector4にしている。
EntityのMaterialプロパティーを変更するための設定
1. GraphSettingsAllow material Overrideにチェック
2. プロパティーを選択した状態で、NodeSettingsのScopeをHybrid Per Instanceに設定する
MaterialOverrideAssetを作成する
- ProjectWindowの中でCreate/Shader/MaterialOverrideAssetからMaterialOverrideAssetを作成する。
- MaterialOverrideAssetに作ったMaterialをアタッチュする。
ここでいきなり、AddPropertyOverrideからコードから変更したいプロパティーにチェックを入れてもいいのですが、それだとプロパティー毎に新規.csがアタッチしているMaterialと同じフォルダ内にできて気持ちが悪いので、今回は先にプロパティー用のコードを書いて自動で.csが作成されないようにする。
でも1.csに複数Propertyをまとめたら、それはそれで同じ名前で同じタイプのプロパティーを書いてしまうことになりかねないので、MaterialOverrideProperties的なフォルダの中にプロパティー1個=1.csで管理する方が良い?かもわかめ
コード
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
namespace ECS_Material_Sample
{
/// <summary>
/// TestMaterialのSystem側から変更できるプロパティーをまとめている場所
/// </summary>
/// <remarks>
/// - ↓これを自分で書かなくても、MaterialOverrideAssetでoverrideするプロパティー
/// - にチェックを入れたら自動で同じコードが作られる。
/// - けど以下の理由で↓みたいな感じで書いてみている。
/// - 1. 一個のMaterialのプロパティーを一ファイルにまとめておいたほうが見やすい
/// - 2. 自動作成だとプロパティー毎に出来た.csがMaterialのあるファイルにできるので気持ち悪い
/// </remarks>
[MaterialProperty("_MainColor")]
public struct MaterialPropertyMainColor : IComponentData
{
public float4 color;
}
[MaterialProperty("_Scale")]
public struct MaterialPropertyScale : IComponentData
{
public float scale;
}
[MaterialProperty("_BoxOffset")]
public struct MaterialPropertyOffset : IComponentData
{
/// <summary>
/// _OffsetはVector2でいいのだけれども...
/// </summary>
/// <remarks>
/// - MaterialOverrideAssetでVector2,Vector3のプロパティーを
/// - overrideしてみるとわかるのですが、なぜか全部Vector4が表示される。
/// - そのまま実行するとsizeが違うよ的なエラーが出る。
/// - [MaterialProperty]アトリビュートの第二引数にsizeがあるので
/// - 8にするとか試したけどだめだったので、Materialのプロパティー自体はVector2
/// - だけど、ここではfloat4にしている。Banana
/// </remarks>
public float4 offset;
}
}
MaterialPropertyアトリビュートでプロパティーのReference名を指定する。
もしプロパティーの名前を間違って、存在しないものを書いてしまうとここで.csが作成されてしまう。
そうなった場合は作成された.csとMaterialOverrideAssetを消して、[MaterialProperty()]の名前を直してからMaterialOverrideAssetを作り直す。
Materialを設定しているプレファブにMaterialOverrideコンポーネントを追加して、作ったMaterialOverrideAssetをOverrideAssetにアタッチュ。
これでMaterialの準備はOK
■スクリプト
Authoring
サンプルだと簡易的にAuthoringクラスでCubeプレファブを10000個作る要求Entityを作っている。
コード
using Unity.Entities;
using UnityEngine;
namespace ECS_Material_Sample
{
public struct SpawnRequestData : IComponentData
{
public uint spawnCount;
public Entity prefabEntity;
}
public class SpawnPrefabAuthoring : MonoBehaviour
{
[SerializeField] private GameObject prefab = null;
[SerializeField] private uint spawnCount = 100;
private class Baker : Baker<SpawnPrefabAuthoring>
{
public override void Bake(SpawnPrefabAuthoring authoring)
{
if (authoring.prefab == null || authoring.spawnCount == 0)
{
Debug.LogWarning("プレファブとスポーン数を設定してお金を入れてね");
return;
}
//スポーンリクエストEntity作成
var requestEntity = GetEntity(TransformUsageFlags.None);
//リクエスト内容設定
AddComponent(requestEntity, new SpawnRequestData()
{
prefabEntity = GetEntity(authoring.prefab, TransformUsageFlags.Renderable),
spawnCount = authoring.spawnCount
});
}
}
}
}
SpawnSystem
ここで指定数指定のプレファブのインスタンスを作っている。
同時に作ったインスタンスにMaterialのプロパティーを変更するためのコンポーネントをくっつけている。
コード
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Random = UnityEngine.Random;
namespace ECS_Material_Sample
{
[BurstCompile]
public partial class SpawnPrefabSystem : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<SpawnRequestData>();
}
[BurstCompile]
protected override void OnUpdate()
{
var ecbs = World.GetOrCreateSystemManaged<BeginInitializationEntityCommandBufferSystem>();
var ecbSpawn = ecbs.CreateCommandBuffer();
var ecbDestroy = ecbs.CreateCommandBuffer();
foreach (var (requestData, requestEntity) in
SystemAPI.Query<SpawnRequestData>().WithEntityAccess())
{
float spacing = 1.5f;
int edgeCount = (int)math.ceil(math.pow(requestData.spawnCount, 1.0f/3.0f));
for (int i = 0; i < requestData.spawnCount; i++)
{
//プレファブのInstance作成
var instanceEntity = EntityManager.Instantiate(requestData.prefabEntity);
#region 位置を設定
// 3次元のインデックスを計算
int x = i % edgeCount;
int y = (i / edgeCount) % edgeCount;
int z = i / (edgeCount * edgeCount);
//transform設定
ecbSpawn.SetComponent(instanceEntity, new LocalTransform()
{
Position = new float3(
x * spacing, // X座標
y * spacing, // Y座標
z * spacing // Z座標
),
Rotation = quaternion.identity,
Scale = 1f
});
#endregion
#region _MainColor用コンポーネント設定
//_MainColorプロパティーをいじるためのコンポーネントを追加
ecbSpawn.AddComponent(instanceEntity, new MaterialPropertyMainColor()
{
//Systemで変更しているので初期値は0
color = float4.zero
});
#endregion
#region _Offset用コンポーネント設定
//_Offsetを更新するためのランダムな方向を設定
var dir = Random.insideUnitCircle;
ecbSpawn.AddComponent(instanceEntity, new MaterialPropertyOffset()
{
offset = new float4(dir.x, dir.y, 0, 0)
});
#endregion
#region _Scale用コンポーネント設定
//_Scaleをここで初期化するのみ
ecbSpawn.AddComponent(instanceEntity, new MaterialPropertyScale()
{
scale = Random.Range(1,7)
});
#endregion
}
//リクエスト削除
ecbDestroy.DestroyEntity(requestEntity);
}
}
}
}
Materialの色プロパティーを更新するSystem
今までのやり方だとMaterial matでmat.SetColorとかSetFloatとかでやっていたのを、MaterialPropertyMainColorをSetCoponentしている箇所でやっている。
コード
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace ECS_Material_Sample
{
[BurstCompile]
public partial class MaterialPropertyMainColorUpdateSystem : SystemBase
{
protected override void OnCreate()
{
RequireForUpdate<MaterialPropertyMainColor>();
}
[BurstCompile]
protected override void OnUpdate()
{
var world = World.DefaultGameObjectInjectionWorld;
var ecbs = world.GetOrCreateSystemManaged<EndSimulationEntityCommandBufferSystem>();
var ecb = ecbs.CreateCommandBuffer();
#region 色計算
var time = (float)world.Time.ElapsedTime;
const float speed = 0.1f;
const float delay = 0.025f;
float3 HsvToRgb(float h, float s, float v)
{
float3 rgb = math.abs(math.fmod(h * 6.0f + new float3(0.0f, 4.0f, 2.0f), 6.0f) - 3.0f) - 1.0f;
rgb = math.saturate(rgb);
return v * math.lerp(new float3(1.0f), rgb, s);
}
#endregion
foreach (var (mainColorComponent, transform, entity) in
SystemAPI.Query<RefRO<MaterialPropertyMainColor>,RefRO<LocalTransform>>().WithEntityAccess())
{
#region 色計算
float hue = math.fmod(time * speed + transform.ValueRO.Position.y * delay, 1.0f);
float saturation = 0.75f;
float value = 0.75f;
float3 rgb = HsvToRgb(hue, saturation, value);
#endregion
//ここでMaterialの色を変える要求を出している
ecb.SetComponent(entity, new MaterialPropertyMainColor()
{
color = new float4(rgb.x, rgb.y , rgb.z, 1f)
});
}
}
}
}
▲Entity関連のScriptが自作のAssemblyDefinitionに属している場合の参照設定について
大体追加する人たち
- Unity.Entities
- Unity.Burst
- Unity.Collections
今回みたいに[MaterialProperty("_Color")]とかするなら↓
- Unity.Entities.Graphics
物理挙動を使うならUnity Phisicsパッケージを入れて↓とか
- Unity.Physics
■詰みポ
Entities Graphics辺りのパッケージのバージョンでエラーが出る
- Entities GraphicsをInstallしたはずなのに[Material Property("")]なんぞねえっていうエラーが出るし、Packages/Entities Graphicsの中身を見るとフォルダの中身が消えている。
- その場合は一旦Entities、Entities GraphicsをパッケージマネージャーからRemoveして、Entities Graphicsを再度Install。
loading entity scene failed...というエラーが出る
- loading entity scene failed...というエラーが出て結果が見えない。
- 一度ProjectウィンドウからSubSceneをダブルクリックで開いた後、元のシーンを開き直してもっかい実行したら”行けたりする”。