Search Unity

Resolved Problem with DrawMeshInstanced when rendering multiple materials

Discussion in 'Scripting' started by hojjat-reyhane, Nov 15, 2020.

Thread Status:
Not open for further replies.
  1. hojjat-reyhane

    hojjat-reyhane

    Joined:
    Feb 4, 2014
    Posts:
    49
    Hi,
    I want to use Graphics.DrawMeshInstanced for showing thousands of 2d objects with different materials.
    The problem is that I can't handle rendering order of overlapping objects which have different materials.
    I have no problem if I use DrawMesh, but it has serious performance issues.
    I implemented this tutorial but it only use one material.

     
  2. hojjat-reyhane

    hojjat-reyhane

    Joined:
    Feb 4, 2014
    Posts:
    49
    OK guys, I'm struggling with this problem about 10 days.
    So I try to explain more about the issue.
    I want to render thousands of objects with different sheet and materials.
    There is two things I'm trying to handle: Rendering the overlapping objects properly and keep the "SetPass calls" as low as possible.

    If I use Graphics.DrawMesh() the rendering is ok, but the "SetPass calls" will differ based on objects positions.
    Here is my code and the result for that:

    Code (CSharp):
    1.         Mesh mesh = Utils.CreateMesh(1f, 1f);
    2.         MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
    3.         Camera camera = Camera.main;
    4.         Vector4[] uv = new Vector4[1];
    5.         int shaderPropertyId = Shader.PropertyToID("_MainTex_UV");
    6.  
    7.         Entities.ForEach((ref SpriteSheetRendererComponent sprite) =>
    8.         {
    9.  
    10.             uv[0] = sprite.uv;
    11.             materialPropertyBlock.SetVectorArray(shaderPropertyId, uv);
    12.  
    13.             Graphics.DrawMesh
    14.             (
    15.                 mesh,
    16.                 sprite.matrix,
    17.                 LevelManager.instance.spriteSheetMaterials[sprite.materialID],
    18.                 0,
    19.                 camera,
    20.                 0,
    21.                 materialPropertyBlock
    22.             );
    23.         }).WithoutBurst().Run();
    The result:

    Annotation 2020-11-17 122744.png Annotation 2020-11-17 12274324.png

    As you can see the "SetPass calls" is high. And if I change the objects position, the "SetPass calls" changes.

    And if I want to use Graphics.DrawMeshInstanced() I have two options: rendering objects of same material first and then rendering another material, or render objects based on their position which has lots of calculation and same resulat as using DrawMesh().

    Here is the code when I rendering the objects based on their materials:

    Code (CSharp):
    1. EntityQuery entityQuery = GetEntityQuery(typeof(Translation), typeof(SpriteSheetRendererComponent));
    2.  
    3.         if (entityQuery.CalculateEntityCount() == 0) return;
    4.  
    5.         NativeArray<SpriteSheetRendererComponent> sheetDataArray = entityQuery.ToComponentDataArray<SpriteSheetRendererComponent>(Allocator.TempJob);
    6.         NativeArray<Translation> translationArray = entityQuery.ToComponentDataArray<Translation>(Allocator.TempJob);
    7.  
    8.         // Sorting objects from top to bottom
    9.         for (int i = 0; i < translationArray.Length; i++)
    10.         {
    11.             for (int j = i + 1; j < translationArray.Length; j++)
    12.             {
    13.                 if (translationArray[i].Value.y < translationArray[j].Value.y)
    14.                 {
    15.                     Translation tmp = translationArray[i];
    16.                     translationArray[i] = translationArray[j];
    17.                     translationArray[j] = tmp;
    18.  
    19.                     SpriteSheetRendererComponent tmpSheet = sheetDataArray[i];
    20.                     sheetDataArray[i] = sheetDataArray[j];
    21.                     sheetDataArray[j] = tmpSheet;
    22.                 }
    23.             }
    24.         }
    25.  
    26.         Mesh mesh = Utils.CreateMesh(1f, 1f);
    27.  
    28.         MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
    29.  
    30.         for(int x = 0; x < LevelManager.instance.spriteSheetMaterials.Length; x++)
    31.         {
    32.             List<Matrix4x4> matrixList = new List<Matrix4x4>();
    33.             List<Vector4> uvList = new List<Vector4>();
    34.  
    35.             for(int i = 0; i < sheetDataArray.Length; i++)
    36.             {
    37.                 if(sheetDataArray[i].materialID == x)
    38.                 {
    39.                     matrixList.Add(sheetDataArray[i].matrix);
    40.                     uvList.Add(sheetDataArray[i].uv);
    41.                 }
    42.             }
    43.  
    44.             if(uvList.Count > 0)
    45.             {
    46.                 materialPropertyBlock.SetVectorArray("_MainTex_UV", uvList);
    47.  
    48.                 Graphics.DrawMeshInstanced(
    49.                     mesh, 0, LevelManager.instance.spriteSheetMaterials[x], matrixList, materialPropertyBlock
    50.                 );
    51.             }
    52.         }
    53.  
    54.         sheetDataArray.Dispose();
    55.         translationArray.Dispose();
    And the result:

    111.png 222.png

    The "SetPass calls" is 2 but objects of different materials not rendering properly. Each object which is higher will render in back.

    Here is my shader if you think it's related:

    Code (JavaScript):
    1. Shader "Custom/InstancedShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Color ("Color", Color) = (1,1,1,1)
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    12.         LOD 100
    13.         Blend SrcAlpha OneMinusSrcAlpha
    14.  
    15.         Pass
    16.         {
    17.             //ZWrite Off
    18.  
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.             #pragma multi_compile_instancing
    23.             #include "UnityCG.cginc"
    24.  
    25.             struct appdata
    26.             {
    27.                 float4 vertex : POSITION;
    28.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    29.                 float2 uv : TEXCOORD0;
    30.             };
    31.  
    32.             struct v2f
    33.             {
    34.                 float2 uv : TEXCOORD0;
    35.                 float4 vertex : SV_POSITION;
    36.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    37.             };
    38.  
    39.             sampler2D _MainTex;
    40.             //float4 _MainTex_UV;
    41.  
    42.             // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    43.             // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    44.             // #pragma instancing_options assumeuniformscaling
    45.             UNITY_INSTANCING_BUFFER_START(Props)
    46.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    47.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _MainTex_UV)
    48.             UNITY_INSTANCING_BUFFER_END(Props)
    49.  
    50.             v2f vert (appdata v)
    51.             {
    52.                 v2f o;
    53.  
    54.                 UNITY_SETUP_INSTANCE_ID(v);
    55.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    56.  
    57.                 o.vertex = UnityObjectToClipPos(v.vertex);
    58.                 //o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    59.                 o.uv = (v.uv * UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_UV).xy) + UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_UV).zw;
    60.  
    61.                 return o;
    62.             }
    63.  
    64.             fixed4 frag (v2f i) : SV_Target
    65.             {
    66.                 UNITY_SETUP_INSTANCE_ID(i);
    67.  
    68.                 // sample the texture
    69.                 fixed4 c = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    70.  
    71.                 return c;
    72.             }
    73.             ENDCG
    74.         }
    75.     }
    76. }
     
  3. hojjat-reyhane

    hojjat-reyhane

    Joined:
    Feb 4, 2014
    Posts:
    49
    After about 20 days I finally succeeded to solve the issue.
    The problem was actually from the shader.
    It seems the youtuber in tutorial had no knowledge about shaders as I.
    With fixes to shader there is no need to sorting entities based on their Y positions.

    Here is the shader:
    Code (JavaScript):
    1. Shader "Custom/InstancedShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Color ("Color", Color) = (1,1,1,1)
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    12.         LOD 100
    13.         Cull Back
    14.         ZWrite On
    15.         Blend SrcAlpha OneMinusSrcAlpha
    16.         //Blend One OneMinusSrcAlpha
    17.  
    18.         Pass
    19.         {
    20.             //ZWrite Off
    21.  
    22.             CGPROGRAM
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #pragma multi_compile_instancing
    26.             #include "UnityCG.cginc"
    27.  
    28.             struct appdata
    29.             {
    30.                 float4 vertex : POSITION;
    31.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    32.                 float2 uv : TEXCOORD0;
    33.             };
    34.  
    35.             struct v2f
    36.             {
    37.                 float2 uv : TEXCOORD0;
    38.                 float4 vertex : SV_POSITION;
    39.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    40.             };
    41.  
    42.             sampler2D _MainTex;
    43.             //float4 _MainTex_UV;
    44.  
    45.             // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    46.             // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    47.             // #pragma instancing_options assumeuniformscaling
    48.             UNITY_INSTANCING_BUFFER_START(Props)
    49.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    50.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _MainTex_UV)
    51.             UNITY_INSTANCING_BUFFER_END(Props)
    52.  
    53.             v2f vert (appdata v)
    54.             {
    55.                 v2f o;
    56.  
    57.                 UNITY_SETUP_INSTANCE_ID(v);
    58.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    59.  
    60.                 float3 worldPosition = float3(v.vertex.x, v.vertex.y, -v.vertex.y / 100);
    61.  
    62.                 // o.vertex = UnityObjectToClipPos(v.vertex);
    63.                 o.vertex = UnityObjectToClipPos(float4(worldPosition, 1.0f));
    64.                 //o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    65.                 o.uv = (v.uv * UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_UV).xy) + UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_UV).zw;
    66.  
    67.                 return o;
    68.             }
    69.  
    70.             fixed4 frag (v2f i) : SV_Target
    71.             {
    72.                 UNITY_SETUP_INSTANCE_ID(i);
    73.  
    74.                 // sample the texture
    75.                 fixed4 col = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    76.                 clip(col.a - 30.0 / 255.0);
    77.                 col.rgb *= col.a;
    78.  
    79.                 return col;
    80.             }
    81.             ENDCG
    82.         }
    83.     }
    84. }
    And here is the system for rendering which is not completely performant and is just for demo:
    (it needs slicing for more than 1023 objects of same material)

    Code (CSharp):
    1. EntityQuery entityQuery = GetEntityQuery(typeof(Translation), typeof(SpriteSheetRendererComponent));
    2.  
    3.         if (entityQuery.CalculateEntityCount() == 0) return;
    4.  
    5.         NativeArray<SpriteSheetRendererComponent> sheetDataArray = entityQuery.ToComponentDataArray<SpriteSheetRendererComponent>(Allocator.TempJob);
    6.         NativeArray<Translation> translationArray = entityQuery.ToComponentDataArray<Translation>(Allocator.TempJob);
    7.  
    8.         Mesh mesh = Utils.CreateMesh(1f, 1f);
    9.  
    10.  
    11.         for(int x = 0; x < LevelManager.instance.spriteSheetMaterials.Length; x++)
    12.         {
    13.             MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
    14.             List<Matrix4x4> matrixList = new List<Matrix4x4>();
    15.             List<Vector4> uvList = new List<Vector4>();
    16.  
    17.             for(int i = 0; i < sheetDataArray.Length; i++)
    18.             {
    19.                 if(sheetDataArray[i].materialID == x)
    20.                 {
    21.                     matrixList.Add(sheetDataArray[i].matrix);
    22.                     uvList.Add(sheetDataArray[i].uv);
    23.                 }
    24.             }
    25.  
    26.             if(uvList.Count > 0)
    27.             {
    28.                 materialPropertyBlock.SetVectorArray("_MainTex_UV", uvList);
    29.  
    30.                 Graphics.DrawMeshInstanced(
    31.                     mesh, 0, LevelManager.instance.spriteSheetMaterials[x], matrixList, materialPropertyBlock
    32.                 );
    33.             }
    34.         }
    35.  
    36.         sheetDataArray.Dispose();
    37.         translationArray.Dispose();
    I also tested this approach:
    https://forum.unity.com/threads/1-million-animated-sprites-at-60-fps.811116/

    It has some issues when you have lots of spriteSheets, and also didn't work on some mobile devices.
    So I just grabbed the things I needed from its shader to fix my problem.
     
  4. Supergeek

    Supergeek

    Joined:
    Aug 13, 2010
    Posts:
    103
    How is your project going? Would you consider releasing a full example project? Most of the high performance sprite rendering solutions seem to require ECS/DOTS, which I am unfamiliar with.
     
  5. hojjat-reyhane

    hojjat-reyhane

    Joined:
    Feb 4, 2014
    Posts:
    49
    I released the project but it has serious issues on some android devices as shader instancing is not supported with some GPUs.
    I'm not sure about performance if you don't use ECS.
    As long as the objects are static there is no problem. But if you have thousands of objects and they're moving I think you have to use ECS.
    Start ECS today, it's scary but it's really great.
     
  6. scorpiopl

    scorpiopl

    Joined:
    Aug 9, 2012
    Posts:
    8
    Hey!
    I updated the original CodeMonkey code to the newest syntax and I'm trying to add multiple material support. No joy. I've pulled what little hair I have left. If you guys got any tips, examples I would be overjoyed. Honestly, I will take divination at this point, as long as it gets me going.
    I'm trying to wrap my head around ECS. After OOP it's a hard road but I'm doing my best.
    Cheers!
     
  7. ZhangTianHou

    ZhangTianHou

    Joined:
    Dec 26, 2016
    Posts:
    2
    good job,lt working on my project, thank you a lot
     
  8. mehmetselmansavas

    mehmetselmansavas

    Joined:
    Sep 7, 2022
    Posts:
    1
    I just logged in to comment on this. THIS SAVED MY LIFE! The shader solution is just perfect for my case. I was going up and down on the internet to have solution about having different materials for different entities animations by their sprite sheets, and this just saved my life. Thank you, i really appreciate it <3
     
    SkyWalkerMK likes this.
Thread Status:
Not open for further replies.