0
\$\begingroup\$

In Unity, if I call DrawProcedural (or one of its variants) from Update() it works fine, but if I make the exact same call from OnRenderObject() it draws nothing. (It also doesn't work from OnPreRender or OnPostRender).

HOWEVER, DrawProceduralNow() works fine from all of these functions.

Why is that so? Is it documented somewhere? What can I do to make DrawProcedural and variants work in OnRenderObject? What is different about DrawProceduralNow that makes it work? (Yes, I have tried using material.SetPass(0) in the DrawProcedural version and it still renders nothing.

I understand that the OnRender functions are called later in the frame and some graphics state is different at that point, but it seems like DrawProcedural is exactly the kind of thing we should be doing in OnRenderObject. I feel like there is some fundamental thing about the Unity rendering pipeline that I am misunderstanding that would clarify all this. (Tested in 2019 LTS and 2020.2.3)

For example:

C#

public class TestRender : MonoBehaviour {  

  public Material material ;
  private ComputeBuffer vertexBuffer;
  private GraphicsBuffer indexBuffer;
  private ComputeBuffer paramsBuffer;

  private Bounds _bounds;
 
  void Start () {
    material = new Material(material);
    
    // Copy geometry from MeshFilter (you can just use a cube for testing)
    Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
    vertexBuffer = new ComputeBuffer(mesh.vertices.Length, Marshal.SizeOf<Vector3>());
    indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Index, mesh.triangles.Length, Marshal.SizeOf<int>());

    vertexBuffer.SetData(mesh.vertices);
    indexBuffer.SetData(mesh.triangles);
    
    paramsBuffer = new ComputeBuffer(5, sizeof(int), ComputeBufferType.IndirectArguments);
    paramsBuffer.SetData(new int[] { mesh.triangles.Length, 1, 0, 0, 0 });
    
    material.SetBuffer("_VertexBuffer", vertexBuffer);
  
    _bounds = new Bounds(transform.position, new Vector3(5, 5, 5));
  }

  private void Update() {
    // This works!
    Graphics.DrawProcedural(material, _bounds, MeshTopology.Triangles, indexBuffer, indexBuffer.count);
 
    // So does this!
    material.SetPass(0);
    Graphics.DrawProceduralNow(MeshTopology.Triangles, indexBuffer, indexBuffer.count);
  }
 
  private void OnRenderObject() {
    // This does NOT work
    Graphics.DrawProcedural(material, _bounds, MeshTopology.Triangles, indexBuffer, indexBuffer.count);
 
    // But this does!
    material.SetPass(0);
    Graphics.DrawProceduralNow(MeshTopology.Triangles, indexBuffer, indexBuffer.count);
  }
}

Simple shader:

Shader "Test Render" {
     SubShader {
                 
         Pass {
         CGPROGRAM
         #include "UnityCG.cginc"
         #pragma target 4.5  
         #pragma vertex vertex_shader
         #pragma fragment fragment_shader
       
         StructuredBuffer<float3> _VertexBuffer;
 
         struct v2f {
             float4 pos : SV_POSITION;
         };
 
         v2f vertex_shader (uint id : SV_VertexID, uint inst : SV_InstanceID)
         {
             v2f o;
             float4 vertex_position =  float4(_VertexBuffer[id],1.0f);
             o.pos = UnityObjectToClipPos(vertex_position);
             return o;
         }
 
         fixed4 fragment_shader (v2f i) : SV_Target
         {
            fixed4 final = fixed4(0, 0, 1, 1);
            return final;
         }
       
         ENDCG
     }
     }
}
\$\endgroup\$
1
  • \$\begingroup\$ All the drawing commands without "Now" in them queue-up the drawing to be done at some later time in the frame. It could be that by the time you get to OnRenderObject, Unity has already finalized its drawing queues for this frame. (After all, at this point by definition it needs to know the set of objects it's going to be drawing). So, anything trying to hop on at this point has missed the bus. But the "Now" variants skip queuing and just jump straight to drawing, so they don't care that the bus has left. This is just speculation though. \$\endgroup\$
    – DMGregory
    Feb 6, 2021 at 18:47

1 Answer 1

2
\$\begingroup\$

From what I could gather over the internet, it appears that calls to functions that don't draw immediately like Now methods are removed from queue after scene was rendered.

From one of the unity forums, part official response - "Another alternative could be using Graphics.DrawMesh (which does not draw mesh immediately, but "adds it into the scene"), and/or using CommandBuffers."

And from official docs - OnRenderObject - "OnRenderObject is called after camera has rendered the Scene.".

So in the end we can come to the conclusion that DrawProceduralNow immediately renders something, while DrawProcedural adds it to the render queue, but since a call of OnRenderObject is happening after scene was rendered - it never gets invoked.

I am not proficient enough to know how it works internally, but this is the best answer I can give right now, since there are 0 answers to your question at the moment.

\$\endgroup\$
1
  • \$\begingroup\$ This is a solid answer and I will accept it, but the situation is still a bit strange to me. The fact that OnPreRender didn't work either was part of the confusion, but it looks like that function is only called on game objects attached to the camera. It seems like if you want a custom renderer for a component that is processed after the update loop is finished, maybe OnWillRenderObject is your only option? I haven't tested this, and will just do my rendering from Update for now. \$\endgroup\$
    – alvion
    Feb 9, 2021 at 5:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .