Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to set diffrent material property values for entityes with same RenderMesh?

Discussion in 'Graphics for ECS' started by SubPixelPerfect, Jun 11, 2019.

  1. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    I'm using
    Unity.Rendering.Hybrid.RenderMeshSystemV2.cs
    for rendering

    I have 1000 cubes on the screen ( RenderMesh, Translation, Rotation, LocalToWorld )
    All use the same model and material.

    I want to create a system that will set a unique color for each cube and change those colors each frame.

    1000 different RenderMeshes will mean 1000 chunks with 1 entity per chunk.
    It should be possible without creating 1000 RenderMeshes... But how?
     
  2. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    Not supported yet :/ We are also waiting on per-instance data like being able to pass color to each instance.
     
  3. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    so the only way to do this is to write my own rendering system...?
     
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Yes, it’s fairly easy though if you trade in LOD, culling, etc.

    You could use the V1 render system and leverage some of its features.

    I am not aware of anyone how customized the V2 system to support this MPB for colors


    Edit:
    Joachim posted an animation system, I have not looked at how it renders - it could also be a starting point

    Or just wait until it’s supported and go with the different render meshes for now
     
    SubPixelPerfect likes this.
  5. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    yea this gpu animation system source code is what i'm trying to understand right now, it is indeed useful as a good starting point here
     
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    If you want I can post a very rudimentary system for you later + the amended shader.

    Will be a few hours though...

    It’s effectively just batching 1023 blocks and drawing it with drawmeshinstanced - if performance is not of the essence you can just copy 1:1 or use unsafe code and mem copy
     
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    You can go around it with ComputeBuffer, fill it in C# side and read as StructuredBuffer in shader and use Instance ID. Same logic used in animation sample from Nordeus. It's not depend on rendering system. But be careful it's not so simple as looks first time.
    Here is fast, simple and ugly sample:
    Code (CSharp):
    1. Shader "SampleShaderWithCB"
    2. {
    3.     Properties
    4.     {
    5.         _MyColor ("Color", Color) = (1,1,1,1)
    6.     }
    7.     SubShader
    8.     {
    9.         Tags {"RenderType"="Opaque"}
    10.  
    11.         Pass
    12.         {
    13.             CGPROGRAM
    14.  
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #pragma multi_compile_instancing
    18.             #include "UnityCG.cginc"
    19.             #pragma target 2.0
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 pos : SV_POSITION;
    30.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    31.             };
    32.  
    33.             StructuredBuffer<float4> _myColorBuffer;
    34.          
    35.  
    36.             UNITY_INSTANCING_BUFFER_START(Props)
    37.                 UNITY_DEFINE_INSTANCED_PROP(float4, _MyColor)
    38.             UNITY_INSTANCING_BUFFER_END(Props)
    39.          
    40.             v2f vert (appdata v, uint vid : SV_VertexID)
    41.             {
    42.                 v2f o;
    43.  
    44.                 UNITY_SETUP_INSTANCE_ID(v);
    45.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    46.  
    47.                 o.pos = UnityObjectToClipPos(v.vertex);
    48.                 return o;
    49.             }
    50.          
    51.             fixed4 frag (v2f i, uint instanceID : SV_InstanceID) : SV_Target
    52.             {
    53.                 UNITY_SETUP_INSTANCE_ID(i);
    54.                 fixed4 col = UNITY_ACCESS_INSTANCED_PROP(Props, _MyColor);
    55.                 col = _myColorBuffer[instanceID]; //Just override for sample
    56.                 return col;
    57.             }
    58.             ENDCG
    59.         }
    60.     }
    61.     FallBack "Diffuse"
    62. }
    63.  
    Code (CSharp):
    1. public class TestSystem : MonoBehaviour
    2. {
    3.     public static TestSystem instance;
    4.     public RenderMesh view;
    5.     private void Awake()
    6.     {
    7.         instance = this;
    8.     }
    9. }
    10.  
    11. [AlwaysUpdateSystem]
    12. public class SomeSystem : ComponentSystem
    13. {
    14.     private EntityQuery _query;
    15.     private ComputeBuffer _buffer;
    16.  
    17.     protected override void OnCreateManager()
    18.     {
    19.         _buffer = new ComputeBuffer(3, 4 * sizeof(float));
    20.         _query = GetEntityQuery(typeof(RenderMesh));
    21.     }
    22.  
    23.     protected override void OnUpdate()
    24.     {
    25.         if (_query.CalculateLength() > 0)
    26.         {
    27.             var ents = _query.ToEntityArray(Allocator.TempJob);
    28.             var rm = EntityManager.GetSharedComponentData<RenderMesh>(ents[0]);
    29.          
    30.             ents.Dispose();
    31.          
    32.             if (Input.GetKeyDown(KeyCode.Keypad0))
    33.             {
    34.                 Debug.Log(rm.material.name);
    35.                 Debug.Log(1);
    36.                 var arr = new NativeArray<Color>(3, Allocator.TempJob);
    37.                 arr[0] = Color.green; //First instantiated cube now green
    38.                 arr[1] = Color.red;
    39.                 arr[2] = Color.red;
    40.                 _buffer.SetData(arr);
    41.                 rm.material.SetBuffer("_myColorBuffer", _buffer);
    42.                 arr.Dispose();
    43.             }
    44.             if (Input.GetKeyDown(KeyCode.Keypad1))
    45.             {
    46.                 Debug.Log(2);
    47.                 var arr = new NativeArray<Color>(3, Allocator.TempJob);
    48.                 arr[0] = Color.red;
    49.                 arr[1] = Color.green; //Second instantiated cube now green
    50.                 arr[2] = Color.red;
    51.                 _buffer.SetData(arr);
    52.                 rm.material.SetBuffer("_myColorBuffer", _buffer);
    53.                 arr.Dispose();
    54.             }
    55.             if (Input.GetKeyDown(KeyCode.Keypad2))
    56.             {
    57.                 Debug.Log(3);
    58.                 var arr = new NativeArray<Color>(3, Allocator.TempJob);
    59.                 arr[0] = Color.red;
    60.                 arr[1] = Color.red;
    61.                 arr[2] = Color.green; //Third instantiated cube now green
    62.                 _buffer.SetData(arr);
    63.                 rm.material.SetBuffer("_myColorBuffer", _buffer);
    64.                 arr.Dispose();
    65.             }
    66.         }
    67.         else
    68.         {
    69.             for (int i = 0; i < 3; i++)
    70.             {
    71.                 var e = EntityManager.CreateEntity(typeof(RenderMesh), typeof(LocalToWorld), typeof(Translation));
    72.                 EntityManager.SetComponentData(e, new Translation()
    73.                 {
    74.                     Value = new float3(0, 0, 5 * i)
    75.                 });
    76.                 World.Active.EntityManager.SetSharedComponentData(e, TestSystem.instance.view);
    77.             }
    78.      
    79.             var arr = new NativeArray<Color>(3, Allocator.TempJob);
    80.             arr[0] = Color.red;
    81.             arr[1] = Color.red;
    82.             arr[2] = Color.red;
    83.             _buffer.SetData(arr);
    84.             TestSystem.instance.view.material.SetBuffer("_myColorBuffer", _buffer);
    85.             arr.Dispose();
    86.         }
    87.     }
    88. }
     
    Last edited: Jun 11, 2019
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Haha, I guess you can ignore my previous post. I will try @eizenhorn solution- looks neat!!!
     
  9. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    Thank you, going to give it a try!
    Looks simple and straightforward form the first glance.
    The only thing that is not clear for me is how to get instance id on c# side if entities order get mixed (I'll need to add/remove cubes at runtime) is it going always match for-loop index?
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    In this case they packed in batches by Render System in order in which they spawned and manage them is on you, from this point starts hardest part :)
     
  11. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    For what it's worth, here the the DrawMeshInstanced version. It contains both the safe and unsafe version. Start with the "ForEachRenderSystem". You dont need the Sprite Tag.

    Edit: Worth noting that I did a little dance here to pull the mesh & material from Mono Prefab in the scene. No real reason to do this....other than I think it was an easy solution in my project to avoid the standard render system to process the entities. This is however with the Unity V1 renderer and I think it has changed so much that there are better solutions now.

    Code (CSharp):
    1.  
    2.  
    3.     public struct SimpleRenderColor: IComponentData
    4.     {
    5.         public Vector4 Value;
    6.     }
    7. -------------------------------
    8.   [UpdateInGroup(typeof(PresentationSystemGroup))]
    9.  
    10.     public class CustomRenderMeshSystem : ComponentSystem
    11.     {
    12.         private EntityQuery instanceRendererGroup;
    13.      
    14.         // INPUT for Instance Renderer
    15.         Material material;
    16.         Mesh mesh;
    17.         MaterialPropertyBlock mpb = new MaterialPropertyBlock();
    18.         private readonly Matrix4x4[] matricesArray = new Matrix4x4[1023]; // Instance renderer takes only batches of 1023
    19.         private readonly Vector4[] colorVectorArray = new Vector4[1023]; // Instance renderer takes only batches of 1023
    20.      
    21.         // Variable for ForEachRenderSystem
    22.         int forEachCounter;
    23.              
    24.         protected override void OnCreateManager()
    25.         {
    26.             instanceRendererGroup = GetEntityQuery(typeof(SpriteTag), typeof(SimpleRenderColor), typeof(LocalToWorld));
    27.          
    28.             if (GameObject.FindObjectOfType<SetupMono>() == null) return;
    29.             var mic = GameObject.FindObjectOfType<SetupMono>().prefabCustomRenderer.GetComponent<Unity.Rendering.RenderMeshProxy>().Value;
    30.             material = mic.material;
    31.             mesh = mic.mesh;
    32.         }
    33.      
    34.         protected override void OnUpdate()
    35.         {
    36.             if (instanceRendererGroup == null) return;
    37.          
    38.             ToArrayRenderSystem();
    39.             //ForEachRenderSystem();
    40.         }
    41.      
    42.      
    43.         void ForEachRenderSystem ()
    44.         {
    45.             forEachCounter = 0;
    46.          
    47.             Entities.WithAllReadOnly<SpriteTag, SimpleRenderColor, LocalToWorld>().ForEach ( (ref SimpleRenderColor col, ref LocalToWorld loc) =>
    48.             {
    49.                 if (forEachCounter < 1023)
    50.                 {
    51.                     matricesArray[forEachCounter] = loc.Value;
    52.                     colorVectorArray[forEachCounter] = col.Value;
    53.                     forEachCounter++;
    54.                 }
    55.                 else
    56.                 {
    57.                     mpb.SetVectorArray("_Color", colorVectorArray);
    58.                     Graphics.DrawMeshInstanced(mesh, 0, material, matricesArray, forEachCounter, mpb);
    59.                     forEachCounter = 0;
    60.                 }
    61.             });
    62.          
    63.             mpb.SetVectorArray("_Color", colorVectorArray);
    64.             Graphics.DrawMeshInstanced(mesh, 0, material, matricesArray, forEachCounter, mpb);
    65.         }
    66.      
    67.      
    68.         void ToArrayRenderSystem()
    69.         {
    70.             var transformsArray = instanceRendererGroup.ToComponentDataArray<LocalToWorld>(Allocator.TempJob);
    71.             var colorArray = instanceRendererGroup.ToComponentDataArray<SimpleRenderColor>(Allocator.TempJob);
    72.  
    73.             int beginIndex = 0;
    74.             while (beginIndex < transformsArray.Length)
    75.             {
    76.                 int length = math.min(matricesArray.Length, transformsArray.Length - beginIndex);
    77.              
    78.                 CopyToTransforms(transformsArray, length, matricesArray, beginIndex);
    79.                 GetMaterialPropertyBlock(colorArray, beginIndex, length, ref mpb);
    80.  
    81.                 Graphics.DrawMeshInstanced(mesh, 0, material, matricesArray, length, mpb);
    82.  
    83.                 beginIndex += length;
    84.             }
    85.          
    86.             colorArray.Dispose();
    87.             transformsArray.Dispose();
    88.         }
    89.  
    90.      
    91.         void GetMaterialPropertyBlock(NativeArray<SimpleRenderColor> colorArray, int beginIndex, int length, ref MaterialPropertyBlock mpb)
    92.         {
    93.             CopyToColors(colorArray, length, colorVectorArray, beginIndex);  
    94.             mpb.SetVectorArray("_Color", colorVectorArray);
    95.         }
    96.  
    97.         static unsafe void CopyToTransforms(NativeSlice<LocalToWorld> transforms, int count, Matrix4x4[] outTransformsArray, int offset)
    98.         {
    99.             // @TODO: This is using unsafe code because the Unity DrawInstances API takes a Matrix4x4[] instead of NativeArray.
    100.             Assert.AreEqual(sizeof(Matrix4x4), sizeof(LocalToWorld));
    101.             fixed (Matrix4x4* resultArray = outTransformsArray)
    102.             {
    103.                 LocalToWorld* sourceArray = (LocalToWorld*) transforms.GetUnsafeReadOnlyPtr();
    104.                 UnsafeUtility.MemCpy(resultArray, sourceArray + offset, UnsafeUtility.SizeOf<LocalToWorld>() * count);
    105.             }
    106.         }
    107.  
    108.         static unsafe void CopyToColors(NativeSlice<SimpleRenderColor> inColorArray, int count, Vector4[] outColorArray, int offset)
    109.         {
    110.             // @TODO: This is using unsafe code because the Unity DrawInstances API takes a Matrix4x4[] instead of NativeArray.
    111.             Assert.AreEqual(sizeof(Vector4), sizeof(SimpleRenderColor));
    112.             fixed (Vector4* resultArray = outColorArray)
    113.             {
    114.                 SimpleRenderColor* sourceArray = (SimpleRenderColor*) inColorArray.GetUnsafeReadOnlyPtr();
    115.                 UnsafeUtility.MemCpy(resultArray, sourceArray + offset, UnsafeUtility.SizeOf<Vector4>() * count);
    116.             }
    117.         }      
    118.     }

    Code (CSharp):
    1. Shader "Custom/InstancedColorSurfaceShader" {
    2.     Properties {
    3.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    4.         _Color ("Color", Color) = (1,1,1,1)
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.      
    10.         CGPROGRAM
    11.  
    12.         #pragma surface surf Standard
    13.  
    14.         sampler2D _MainTex;
    15.  
    16.         struct Input {
    17.             float2 uv_MainTex;
    18.         };
    19.  
    20.         UNITY_INSTANCING_BUFFER_START(Props)
    21.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)  
    22.         UNITY_INSTANCING_BUFFER_END(Props)
    23.  
    24.         void surf (Input IN, inout SurfaceOutputStandard o) {
    25.  
    26.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    27.             o.Albedo = c.rgb;
    28.             o.Alpha = c.a;
    29.         }
    30.         ENDCG
    31.     }
    32.     FallBack "Diffuse"
    33. }
     
    Last edited: Jun 11, 2019
    SubPixelPerfect likes this.