Search Unity

How to get model matrix into Graphics.DrawProcedural custom shader?

Discussion in 'Shaders' started by rickappleton, Aug 22, 2017.

  1. rickappleton

    rickappleton

    Joined:
    Feb 2, 2017
    Posts:
    2
    I am trying to get a custom shader to work with vertex pulling from a generic buffer. To do this, I've created a custom shader that only uses SV_VertexId and a StructuredBuffer as inputs, and then I call Graphics.DrawProcedural to actually draw stuff. The basics work, but I'm having a lot of trouble getting the correct MVP matrix in the shader.

    I found http://answers.unity3d.com/questions/12713/how-do-i-reproduce-the-mvp-matrix.html but it doesn't work reliably on all my platforms; sometimes I need to flip the Y-coordinate, and sometimes I don't. So I figured I would go back to the shader and attempt to use the UnityObjectToClipPos() function in 2017.1.

    That works for the VP part of the MVP matrix, but the model part isn't updated. If I create a scene then I can see that standard objects/shaders do scale for example, but the object with my custom script and shader doesn't.

    Below is some example code that exhibits the issue. Simply create an empty GameObject, attach this script, create a new Material that uses this custom shader and link that into the script. Although the procedural triangle is correctly rendered as the camera changes position, any changes to the position/rotation/scale aren't visible.

    I have looked at the render calls with RenderDoc, and as far as I can tell there is a matrix in the shader that stays at identity. I suspect that is the model matrix, and that I somehow need to tell Unity to update it from the current transform.

    Any help would be greatly appreciated!

    Kind regards,
    Rick

    Script
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class TestDirectProcedural : MonoBehaviour {
    4.  
    5.     public Material mat;
    6.     public void OnRenderObject()
    7.     {
    8.         mat.SetPass(0);
    9.         Graphics.DrawProcedural(MeshTopology.Triangles, 3, 1);
    10.     }
    11. }
    12.  
    Shader
    Code (csharp):
    1. Shader "Custom/TestProceduralShader" {
    2.     SubShader {
    3.         Tags { "RenderType"="Opaque" }
    4.         LOD 200
    5.        
    6.         Pass
    7.         {
    8.             Cull Off
    9.  
    10.             CGPROGRAM
    11.             #include "UnityShaderVariables.cginc"
    12.             #pragma only_renderers d3d11
    13.            
    14.             #pragma vertex vs
    15.             #pragma fragment ps
    16.  
    17.             #pragma target 3.0
    18.  
    19.             struct v2f {
    20.                 float4 position : SV_Position;
    21.             };
    22.        
    23.  
    24.             static float3 positions[3] = {
    25.                 float3(0,0,0),
    26.                 float3(10,0,0),
    27.                 float3(0,0,10)
    28.             };
    29.  
    30.             v2f vs( uint index : SV_VertexId ) {
    31.                 v2f o;
    32.  
    33.                 float4 position = float4(positions[index], 1);
    34.                 position = UnityObjectToClipPos(position);
    35.                 o.position = position;
    36.                
    37.                 return o;
    38.             }
    39.            
    40.             fixed4 ps(v2f input) : SV_Target0
    41.             {
    42.                 return fixed4(1, 0, 0, 1);
    43.             }
    44.             ENDCG
    45.         }
    46.     }
    47. }
    48.  
    49.  
    50.  
     
  2. Zarenityx

    Zarenityx

    Joined:
    Dec 2, 2012
    Posts:
    9
    I'm running into the same issue. So far, it seems that Graphics.DrawProcedural just doesn't set the model matrix (or perhaps clears it, as OnRenderObject supposedly is called after all normal drawing and the matrix should be set).

    The short answer is that, unfortunately, at the moment it does seem that you will have to recreate at least the model matrix for this. Now to preface this, I should point out that I target Win/Mac/Linux and UWP almost exclusively, and have never had to deal with the flipped y axis of which you speak.

    As it turns out, UnityObjectToClipPos() is actually just defined as:
    Code (CSharp):
    1. // Tranforms position from object to homogenous space
    2. inline float4 UnityObjectToClipPos(in float3 pos)
    3. {
    4.     // More efficient than computing M*VP matrix product
    5.     return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
    6. }
    It's pretty much just a shorthand for what you'd be doing anyway, and doesn't do anything platform-specific. I would assume that such flipping would likely be done by the camera in preparation for rendering. Defining a float4x4 in a shader and recreating the UnityObjectToClipPos function would be trivial, leaving only the model matrix to be calculated. Once again, I do not unfortunately have the hardware to test this, but if the VP portion is being calculated accurately for all platforms then I see no reason why calculating a model matrix with Matrix4x4.TRS or the like would cause any issues.
    If it is the case, you may need to (and I can't believe I'm saying this) use a bunch of preprocessor defines. Yeah, it would be kinda gross, but you could wrap it in a method, and if it works for everything you're targeting, well then it works.


    So far, there unfortunately doesn't seem to be a solution for this other than to just work around it and make your own modelmatrix. In some ways, one could think of this as a feature, in the case of, say, DrawProcedural being used to draw a lot of different objects that might all have different matrices, a bit like instancing (on some hardware this is actually faster than instancing), or for something like particles. Regardless, for just rendering bits of geometry on an object, you would just need to use Material.SetMatrix or a command buffer to transform the vertices correctly.

    Certainly, however, the VP matrix can stay the same per camera, and does when using DrawProcedural, so there's no need to recreate an entire MVP matrix.