Search Unity

Question DrawMeshInstanced / ExecuteCommandBuffer rendering issues

Discussion in 'Universal Render Pipeline' started by russnash37, Jun 8, 2021.

  1. russnash37

    russnash37

    Joined:
    Feb 22, 2017
    Posts:
    31
    I've been working on a script to add instanced grass to my generated world in Unity, using a CommandBuffer and DrawMeshInstanced. Basically, I build my list of Matrix4x4's and then run through the list in batches of 1023 to add each needed DrawMeshInstanced to my CommandBuffer. Finally, I call Graphics.ExecuteCommandBuffer, but nothing gets drawn.

    If I check the frame debugger, I can see my commands in the list even though they don't get drawn. Also, If I switch from using a CommandBuffer to just Graphics.DrawMeshInstanced, all works fine which confirms that I'm drawing everything where it should be.

    Hoping someone can point me in the right direction, I'm using the Universal Render Pipeline/Simple Lit shader with GPU Instancing enabled on my material.

    Here's my code:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Rendering;
    4.  
    5.  
    6. namespace Genesis
    7. {
    8.     public class InstancedGrass : MonoBehaviour
    9.     {
    10.         public Material material;
    11.  
    12.         private List<Matrix4x4> _matrices = new List<Matrix4x4>();
    13.         private Server _server;
    14.         private World.Vegetation[,] _grasses;
    15.         private CommandBuffer _commandBuffer;
    16.         private Renderer _lod0;
    17.         private Mesh mesh;
    18.  
    19.         void Start()
    20.         {
    21.             _server = GameObject.Find("Server").GetComponent<Server>();
    22.             _grasses = _server.world.Grasses;
    23.             _lod0 = gameObject.transform.Find("MeshHost_LOD0").GetComponent<Renderer>();
    24.             var chunkSize = _server.chunkSize;
    25.             _commandBuffer = new CommandBuffer();
    26.             _commandBuffer.name = gameObject.name;
    27.             // _commandBuffer.SetRenderTarget(RenderTargetIdentifier.AllDepthSlices);
    28.             var meshHostPos = transform.position;
    29.             mesh = CreateQuad();
    30.  
    31.             for (int z = (int)meshHostPos.z; z < (int)meshHostPos.z + chunkSize; z++)
    32.             {
    33.                 for (int x = (int)meshHostPos.x; x < (int)meshHostPos.z + chunkSize; x++)
    34.                 {
    35.                     if (_grasses[x, z] != null)
    36.                     {
    37.                         var position = new Vector3(x, _server.world.GetHeightAt(x, z) - 0.2f, z);
    38.                         var rotation = Quaternion.Euler(0f, _grasses[x, z].rotation, 0);
    39.                         var scale = new Vector3(_grasses[x, z].scale, _grasses[x, z].scale, _grasses[x, z].scale);
    40.                         var matrix = Matrix4x4.TRS(position, rotation, scale);
    41.                        
    42.                         _matrices.Add(matrix);
    43.                     }
    44.                 }
    45.             }
    46.            
    47.             for (int index = 0; index < _matrices.Count; index += 1023)
    48.             {
    49.                 int count;
    50.                 if (index + 1022 < _matrices.Count)
    51.                 {
    52.                     count = 1023;
    53.                 }
    54.                 else
    55.                 {
    56.                     count = _matrices.Count - index;
    57.                 }
    58.  
    59.                 _commandBuffer.DrawMeshInstanced(mesh, 0, material, -1, _matrices.GetRange(index, count).ToArray());
    60.             }
    61.         }
    62.  
    63.         private void LateUpdate()
    64.         {
    65.             if (_lod0.isVisible)
    66.             {
    67.                 Graphics.ExecuteCommandBuffer(_commandBuffer);
    68.             }
    69.         }
    70.  
    71.         private Mesh CreateQuad()
    72.         {
    73.             Mesh mesh = new Mesh();
    74.  
    75.             Vector3[] vertices = new Vector3[4]
    76.             {
    77.                 new Vector3(0, 0, 0),
    78.                 new Vector3(1, 0, 0),
    79.                 new Vector3(0, 1, 0),
    80.                 new Vector3(1, 1, 0)
    81.             };
    82.             mesh.vertices = vertices;
    83.  
    84.             int[] tris = new int[6]
    85.             {
    86.                 // lower left triangle
    87.                 0, 2, 1,
    88.                 // upper right triangle
    89.                 2, 3, 1
    90.             };
    91.             mesh.triangles = tris;
    92.  
    93.             Vector3[] normals = new Vector3[4]
    94.             {
    95.                 -Vector3.forward,
    96.                 -Vector3.forward,
    97.                 -Vector3.forward,
    98.                 -Vector3.forward
    99.             };
    100.             mesh.normals = normals;
    101.  
    102.             Vector2[] uv = new Vector2[4]
    103.             {
    104.                 new Vector2(0, 0),
    105.                 new Vector2(1, 0),
    106.                 new Vector2(0, 1),
    107.                 new Vector2(1, 1)
    108.             };
    109.             mesh.uv = uv;
    110.  
    111.             return mesh;
    112.         }
    113.     }
    114. }
    Thank you in advance to anyone who can point me in the right direction.
     
  2. Desoxi

    Desoxi

    Joined:
    Apr 12, 2015
    Posts:
    195
    I just had the same issue in a build on the Oculus Quest for several days now. Though I could first not figure it out because it worked in other projects. But now that I tried to use the Lit shader again, it draws everything perfectly, but obviously with horrible FPS.

    Maybe this is the issue for you as well? For me, it seems like a bug to be honest. The simple lit shader should be able to be used by drawMeshInstanced as well.
     
  3. Desoxi

    Desoxi

    Joined:
    Apr 12, 2015
    Posts:
    195
    Hey @russnash37, over here I explained why you currently can not draw objects with the simple lit shader (v2020.3.14f1)
     
  4. russnash37

    russnash37

    Joined:
    Feb 22, 2017
    Posts:
    31
    Hey Desoxi! Thanks for the input! I definitely think you are right about it being a shader related issue. At one point, I had managed to get some red and green partially drawn planes to appear in my scene (just drawing quads to render grass).

    My intention was to use a shader graph I had cobbled together from some other examples to produce a grass that reacted to wind, but could not get it to even display (even though it showed in the frame debugger). Shaders are definitely not my strong point at the moment. In your other post you mention the #pragma directives being absent as possibly being to blame, do you have any idea how these would be referenced / represented in a shader graph?

    Thanks again for taking the time to reply.
     
  5. Desoxi

    Desoxi

    Joined:
    Apr 12, 2015
    Posts:
    195
    If you are creating the shader in shader graph, it will have these pragma statements added automatically to the generated shader. In my tests I had not issues in using such a shader with DrawMeshInstanced. But you also have no support for arrays initially and you have to add the support manually by editing the generated shader and from then on you have to edit the shader always by hand, because shader graph is not capable of editing it anymore.

    The array part is only needed if you are doing custom stuff for each instance, like different color etc. and need to have access to the instanceID of the drawn instance.

    In which event are you filling your commandbuffer?
     
  6. russnash37

    russnash37

    Joined:
    Feb 22, 2017
    Posts:
    31
    I'm actually filling the commandbuffer in the Start event as the data is static and it's only variable input to the shader graph that changes with each frame. CommandBuffer execution occurs during LateUpdate. I've also tried executing the commandbuffer during the OnRender event.
     
  7. Desoxi

    Desoxi

    Joined:
    Apr 12, 2015
    Posts:
    195
    I think OnRender won't work in URP as far as I know. What I tried in the past is to set the events "beginCameraRendering" and "endCameraRendering" via


    Code (CSharp):
    1. CommandBuffer test;
    2.  
    3. private void OnEnable()
    4.     {
    5.         test = new CommandBuffer();
    6.         RenderPipelineManager.beginCameraRendering += Render;
    7.         RenderPipelineManager.endCameraRendering += EndRender;
    8.         Camera.main.AddCommandBuffer(CameraEvent.AfterSkybox, test);
    9.     }
    10.  
    11. void Render(ScriptableRenderContext context, Camera cam)
    12.     {
    13.     //Call test.DrawMeshInstanced()
    14. }
    Though I went back to using the Graphics API (Graphics.DrawMeshInstanced()) because this approach was very slow (or I used it the wrong way, which can be the case as well as I am not very experienced with Commandbuffer usage yet).
     
  8. russnash37

    russnash37

    Joined:
    Feb 22, 2017
    Posts:
    31
    I'm going to give this another go when I have a moment to play with it and see if I can get it working now. I haven't tried since creating the shader graph so may see results now as opposed to the built in lit shaders.
     
  9. weiping-toh

    weiping-toh

    Joined:
    Sep 8, 2015
    Posts:
    192
    You definitely have misused CommandBuffer with regards to the Execution.
    https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.ExecuteCommandBuffer.html
     
    Desoxi likes this.
  10. Desoxi

    Desoxi

    Joined:
    Apr 12, 2015
    Posts:
    195