Search Unity

Graphics.DrawMeshInstancedIndirect - multiple submeshes

Discussion in 'General Graphics' started by LouskRad, Jan 23, 2018.

  1. LouskRad

    LouskRad

    Joined:
    Feb 18, 2014
    Posts:
    904
    Hi,

    I'm trying to get the DrawMeshInstancedIndirect work with multiple submeshes on a mesh (one call for each submesh index). I have succeeded with a single submesh mesh, but I'm having difficulty understanding the parameters for the argsbuffer (buffer with arguments) for the submeshes.

    Here is the argsbuffer I'm trying to use for each submesh (where j is the submesh index)

    Code (CSharp):
    1.                  
    2. uint[] args = new uint[5]
    3. {
    4.      mesh.GetIndexCount(j), // index count per instance
    5.      (uint)instanceCount,
    6.      mesh.GetIndexStart(j),   // start index location
    7.      mesh.GetBaseVertex(j), // base vertex location
    8.      0  // start instance location ???
    9. }
    10.  
    The actual implementation is a bit too complex to post here, as I use a compute shader for visibility calculations, append to an append buffer and copycount the append buffer instance count to the args buffer. This works all fine when I use a single submesh mesh. For the multiple submesh version, I have corresponding append buffers, args buffers and materials (created in code) but I think I can't figure out the arguments for the argsbuffers. The result is only one of the submeshes being rendered no matter what I do.

    Any help would be appreciated. (Shouting out to @richardkettlewell :))
     
  2. LouskRad

    LouskRad

    Joined:
    Feb 18, 2014
    Posts:
    904
    Ok, so I figured this out. If anyone is interested, the way args is constructed above is working as intended. The problem in my case was the way in which I set the append buffers for the materials.
     
    richardkettlewell likes this.
  3. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Hi,

    I was half-way through testing this out, when you posted that you'd fixed it :)

    To help other users, I have updated the script example here:
    https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html

    It now includes a new subMeshIndex variable, demonstrating how to draw each sub-mesh of a mesh, using this API.

    Until the script example is updated on the public website, here is the new C# class that will be in the updated docs:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class DrawIndirect : MonoBehaviour
    5. {
    6.     public int instanceCount = 100000;
    7.     public Mesh instanceMesh;
    8.     public Material instanceMaterial;
    9.     public int subMeshIndex = 0;
    10.  
    11.     private int cachedInstanceCount = -1;
    12.     private int cachedSubMeshIndex = -1;
    13.     private ComputeBuffer positionBuffer;
    14.     private ComputeBuffer argsBuffer;
    15.     private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
    16.  
    17.     void Start()
    18.     {
    19.         argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
    20.         UpdateBuffers();
    21.     }
    22.  
    23.     void Update()
    24.     {
    25.         // Update starting position buffer
    26.         if (cachedInstanceCount != instanceCount || cachedSubMeshIndex != subMeshIndex)
    27.             UpdateBuffers();
    28.  
    29.         // Pad input
    30.         if (Input.GetAxisRaw("Horizontal") != 0.0f)
    31.             instanceCount = (int)Mathf.Clamp(instanceCount + Input.GetAxis("Horizontal") * 40000, 1.0f, 5000000.0f);
    32.  
    33.         // Render
    34.         Graphics.DrawMeshInstancedIndirect(instanceMesh, subMeshIndex, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer);
    35.     }
    36.  
    37.     void OnGUI()
    38.     {
    39.         GUI.Label(new Rect(265, 25, 200, 30), "Instance Count: " + instanceCount.ToString());
    40.         instanceCount = (int)GUI.HorizontalSlider(new Rect(25, 20, 200, 30), (float)instanceCount, 1.0f, 5000000.0f);
    41.     }
    42.  
    43.     void UpdateBuffers()
    44.     {
    45.         // Ensure submesh index is in range
    46.         if (instanceMesh != null)
    47.             subMeshIndex = Mathf.Clamp(subMeshIndex, 0, instanceMesh.subMeshCount - 1);
    48.  
    49.         // Positions
    50.         if (positionBuffer != null)
    51.             positionBuffer.Release();
    52.         positionBuffer = new ComputeBuffer(instanceCount, 16);
    53.         Vector4[] positions = new Vector4[instanceCount];
    54.         for (int i = 0; i < instanceCount; i++)
    55.         {
    56.             float angle = Random.Range(0.0f, Mathf.PI * 2.0f);
    57.             float distance = Random.Range(20.0f, 100.0f);
    58.             float height = Random.Range(-2.0f, 2.0f);
    59.             float size = Random.Range(0.05f, 0.25f);
    60.             positions[i] = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
    61.         }
    62.         positionBuffer.SetData(positions);
    63.         instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
    64.  
    65.         // Indirect args
    66.         if (instanceMesh != null)
    67.         {
    68.             args[0] = (uint)instanceMesh.GetIndexCount(subMeshIndex);
    69.             args[1] = (uint)instanceCount;
    70.             args[2] = (uint)instanceMesh.GetIndexStart(subMeshIndex);
    71.             args[3] = (uint)instanceMesh.GetBaseVertex(subMeshIndex);
    72.         }
    73.         else
    74.         {
    75.             args[0] = args[1] = args[2] = args[3] = 0;
    76.         }
    77.         argsBuffer.SetData(args);
    78.  
    79.         cachedInstanceCount = instanceCount;
    80.         cachedSubMeshIndex = subMeshIndex;
    81.     }
    82.  
    83.     void OnDisable()
    84.     {
    85.         if (positionBuffer != null)
    86.             positionBuffer.Release();
    87.         positionBuffer = null;
    88.  
    89.         if (argsBuffer != null)
    90.             argsBuffer.Release();
    91.         argsBuffer = null;
    92.     }
    93. }
    94.  
     
    ekakiya and LouskRad like this.
  4. JackieDevelops

    JackieDevelops

    Joined:
    May 11, 2017
    Posts:
    27
    If I may revive this thread, using Unity 5.6 when I use DrawMeshInstancedIndirect and use an argsOffset as one of the arguments to draw my submeshes, the performance tanks horribly. The profiler typically adds this overhead under GBuffer or ForwardObjectsToDepth, but the performance loss is several orders of magnitude.

    In contrast, if I just make a seperate args buffer for every single submesh, and just change which buffer I pass to the DrawMeshInstancedIndirect and keep argsOffset to 0, then my performance is as we would expect from the function, aka phenomenal.

    Has this bug been resolved in a newer version, and is there any insight into what the reason for this is?
    It was reported here:
    https://forum.unity.com/threads/drawmeshinstancedindirect-example-comments-and-questions.446080/
    by XaneFeather, but he didnt elaborate much on it and he was ignored, but what he reports in terms of performance loss is exactly what I notice as well.

    Also, if I may ask, what is the purpose of "int submeshIndex" in the draw call? I set this to the correct value (the index of my submesh, or 0 if I have no submeshes), and then for fun I changed the value and saw it had absolutely no impact, I can set it to any number thats within my submesh range and there is 0 visual impact. What does this actually do under the hood?
     
  5. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    I’m not aware of it having been reported as a bug in our bug reporting system, so perhaps not. Sounds like a very strange bug.

    To choose which submesh of a mesh to draw. (I.e multi sub materials, in 3ds max).

    I recall bugs in 5.6 with submeshes, which may well be fixed in newer versions, though I can’t recall specifically.

    You may test on a newer version or submit bug reports to have our QA investigate these problems you’re having with submeshes and argsOffset.
     
  6. JackieDevelops

    JackieDevelops

    Joined:
    May 11, 2017
    Posts:
    27
    Well, I was aware of what the intent of the argument was, but it doesnt seem to actually do that. I took your own script you posted above, changed submeshIndexto a hard coded 0 in the draw call. Then issued drawcalls for any of the 4 submeshes of my tree, and they all worked. That is to say that the value of submeshIndexis having absolutely no noticeable impact on the draw call.

    The selected submesh seems to only be described by the argsBuffer by changing the starting index of the triangle in the mesh as well as the number of indexes. Those 2 arguments are sufficient to describe the submesh you wish to draw call, so then what exactly is submeshIndex doing under the hood?

    To clarify, I dont mean the submeshIndex in your script, I mean in the actual draw call functions:

    public static void DrawMeshInstancedIndirect(Mesh mesh, int submeshIndex, Material material, Bounds bounds, ComputeBuffer bufferWithArgs, int argsOffset = 0,MaterialPropertyBlock properties = null, Rendering.ShadowCastingMode castShadows = ShadowCastingMode.On, bool receiveShadows = true, int layer = 0, Camera camera = null);

    Feel free to try it out with your script, at the very least for me on unity 5.6 the int submeshIndex argument does nothing discernable.
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I seem to be getting the same issue. I will probably try splitting my meshes with submeshes into multiple meshes with no submeshes to get it working...
    The submesh index seems to work fine for
    Graphics.DrawMeshInstanced
    , but does not work at all for
    Graphics.DrawMeshInstancedIndirect
     
  8. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    The docs page shows you how to use it for different submeshes. It’s not as simple as changing the parameter.

    https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html
     
  9. DogF

    DogF

    Joined:
    Dec 31, 2013
    Posts:
    29
    I tried hard with Graphics.DrawMeshInstancedIndirect() function to renderer mesh with multiple submeshes.Can not get it work.Meshes work fine with standed shader,but will crack in DrawMeshInstancedIndirect().Vertices is deranged.Will this will fixed in next version?
     
  10. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    If you submit a bug report and QA agree it’s a bug in unity, then maybe yes, but otherwise no :)

    (FWIW, from the very limited info you shared, it sounds like maybe you set the wrong index start/count for the indirect call. The docs page shows an example of how to do this correctly)
     
  11. DogF

    DogF

    Joined:
    Dec 31, 2013
    Posts:
    29
    Look at my code here, I am sure there is nothing wrong with this. At first I just use fbx files, and it have two materials.It does not work.Then I use two meshes and combine them with code below, I found somthing fun.The submesh0 works fine, but submesh1 was wrong. Look at the image, submesh0 is the cube, submesh1 is the sphere.

    微信截图_20211011110542.png

    I also tried DrawMeshInstanced(), it also can't work well, but I can switch the index param make it work. With DrawMeshInstancedIndirect(), it works with the way in the image.I want submit a bug,but I don't know where can I do this.


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Runtime.InteropServices;
    4. using Unity.Mathematics;
    5. using UnityEngine;
    6. using UnityEngine.Rendering;
    7.  
    8. public class SubMeshTest : MonoBehaviour {
    9.     public Transform target;
    10.     public Mesh mesh0;
    11.     public Mesh mesh1;
    12.  
    13.     private Mesh combinedMesh;
    14.  
    15.     public Material mat0;
    16.     public Material mat1;
    17.  
    18.     public Vector3 pos;
    19.  
    20.  
    21.     private ComputeBuffer argsBuffer0;
    22.     private ComputeBuffer argsBuffer1;
    23.  
    24.     private ComputeBuffer trsBuffer0;
    25.     private ComputeBuffer trsBuffer1;
    26.  
    27.     uint[] args0 = new uint[5];
    28.     uint[] args1 = new uint[5];
    29.  
    30.     Matrix4x4[] drawData0 = new Matrix4x4[1];
    31.     Matrix4x4[] drawData1 = new Matrix4x4[1];
    32.  
    33.     bool inited = false;
    34.     // Start is called before the first frame update
    35.     void Start()
    36.     {
    37.         CombineInstance[] inses = new CombineInstance[2];
    38.         CombineInstance CI0 = new CombineInstance() {
    39.             mesh = mesh0,
    40.             //subMeshIndex = 0,
    41.             transform = target.localToWorldMatrix
    42.         };
    43.         CombineInstance CI1 = new CombineInstance() {
    44.             mesh = mesh1,
    45.             //subMeshIndex = 1,
    46.             transform = target.localToWorldMatrix
    47.  
    48.         };
    49.         inses[0] = CI0;
    50.         inses[1] = CI1;
    51.  
    52.         combinedMesh = new Mesh();
    53.         combinedMesh.CombineMeshes(inses, false);
    54.         inited = true;
    55.         UnityEngine.Debug.Log("Combined    " + combinedMesh.subMeshCount);
    56.     }
    57.  
    58.     // Update is called once per frame
    59.     void Update() {
    60.         if (!inited) {
    61.             return;
    62.         }
    63.  
    64.         if (argsBuffer0 != null) {
    65.             argsBuffer0.Release();
    66.             argsBuffer0.Dispose();
    67.         }
    68.         if (argsBuffer1 != null) {
    69.             argsBuffer1.Release();
    70.             argsBuffer1.Dispose();
    71.         }
    72.  
    73.         args0[0] = combinedMesh.GetIndexCount(0);
    74.         args0[1] = 1;
    75.         args1[0] = combinedMesh.GetIndexCount(1);
    76.         args1[1] = 1;
    77.         SubMeshDescriptor sub0 = combinedMesh.GetSubMesh(0);
    78.         SubMeshDescriptor sub1 = combinedMesh.GetSubMesh(1);
    79.        
    80.         UnityEngine.Debug.Log($" indexCount:{combinedMesh.vertexCount}");
    81.         UnityEngine.Debug.Log($"--A  indexStart:{sub0.indexStart} indexCount:{sub0.indexCount}");
    82.         UnityEngine.Debug.Log($"--B  indexStart:{sub1.indexStart} indexCount:{sub1.indexCount}");
    83.  
    84.         drawData0[0] = Matrix4x4.Translate(pos) * Matrix4x4.Rotate(Quaternion.Euler(0, 0,0)) * Matrix4x4.TRS(-sub0.bounds.center + Vector3.up, Quaternion.identity, Vector3.one) ;
    85.         drawData1[0] = Matrix4x4.Translate(pos) * Matrix4x4.Rotate(Quaternion.Euler(0, 0, 0)) * Matrix4x4.TRS(-sub1.bounds.center + Vector3.down, Quaternion.identity, Vector3.one) ;
    86.  
    87.  
    88.  
    89.         argsBuffer0 = new ComputeBuffer(5, Marshal.SizeOf(typeof(uint)), ComputeBufferType.IndirectArguments);
    90.         argsBuffer1 = new ComputeBuffer(5, Marshal.SizeOf(typeof(uint)), ComputeBufferType.IndirectArguments);
    91.  
    92.         trsBuffer0 = new ComputeBuffer(1, Marshal.SizeOf(typeof(float4x4)));
    93.         trsBuffer1 = new ComputeBuffer(1, Marshal.SizeOf(typeof(float4x4)));
    94.  
    95.         argsBuffer0.SetData(args0);
    96.         argsBuffer1.SetData(args1);
    97.  
    98.         trsBuffer0.SetData(drawData0);
    99.         trsBuffer1.SetData(drawData1);
    100.  
    101.         mat0.SetBuffer("classifyResult", trsBuffer0);
    102.         mat1.SetBuffer("classifyResult", trsBuffer1);
    103.  
    104.         Graphics.DrawMeshInstancedIndirect(
    105.             combinedMesh,
    106.             0,
    107.             mat0,
    108.             new Bounds(Vector3.zero, new Vector3(2000000, 2000000, 2000000)),
    109.             argsBuffer0);
    110.  
    111.         Graphics.DrawMeshInstancedIndirect(
    112.             combinedMesh,
    113.             1,
    114.             mat1,
    115.             new Bounds(Vector3.zero, new Vector3(2000000, 2000000, 2000000)),
    116.             argsBuffer1);
    117.     }
    118. }
    119.  
     
  12. DogF

    DogF

    Joined:
    Dec 31, 2013
    Posts:
    29
    Thank you for reply,I fixed this by looking at others codes.There is something wrong with my codes.It's the argsBuffer data.
    the right one is below:
    Code (CSharp):
    1.         args1[0] = combinedMesh.GetIndexCount(1);
    2.         args1[1] = 1;
    3.         args1[2] = combinedMesh.GetIndexStart(1);
     
    richardkettlewell likes this.