Search Unity

Help Wanted What is the equivalent of UnityObjectToClipPos inside Shader Graph??

Discussion in 'Shader Graph' started by LeandroGil, Jan 15, 2020.

  1. LeandroGil

    LeandroGil

    Joined:
    Apr 17, 2013
    Posts:
    2
    Hi, I'm pretty new with shaders and started to look more into them in Shader Graph, and trying to convert from a script shader is not resulting on the same results

    Code (CSharp):
    1.  
    2. v2f vert(appdata v) {
    3.     v2f aux;
    4.     aux.vertex = UnityObjectToClipPos(v.vertex);
    5.  
    6.     float delta = UNITY_Z_0_FAR_FROM_CLIPSPACE(aux.vertex.z);
    7.  
    8.     aux.vertex.y -= _Magnitude * delta * delta * _ProjectionParams.x;
    9.  
    10.     return aux;
    11. }
    12.  
    I created this
    01.jpg
    I'm not really sure on the equivalent way of having the UnityObjectToClipPos and UNITY_Z_0_FAR_FROM_CLIPSPACE applied correctly

    Here is a side by side (right road is with shader Graph version)

    Preview.JPG

    The bending is more strong on the right and also is happening in more than one axis (intended was only on "y")
    2.JPG

    I searched the documentation and forum but couldn't find any hint on whats the equivalent for this, any help would be appreciated!
     
    Last edited: Jan 16, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,327
    There’s a bit of complexity to this question because there are no straight equivalents in Shader Graph, but it can be recreated.

    First, the
    UnityObjectToClipPos()
    function. That transforms the mesh vertex position from local object space to clip space, as the function implies. It does that by transforming from object to world space, which is easy enough to understand, and then world to clip space using the view projection matrix. Clip space, or more accurately, homogeneous clip space, is a special 4 component space that describes the screen space position of a vertex. If you want to understand that better, I’d search for homogenous clip space and follow that rabbit hole. But the crux of it is you need to multiply the world position (with a w of 1.0) by the View Projection matrix, which you can do easy enough.
    upload_2020-1-15_14-40-19.png
    Notice I'm reconstructing the Vector3 position into a Vector4. This is because of how matrix & vector multiplication works. Inputing a Vector3 or a Vector4 with a w of 0 will mean the output vector only takes the scale and rotation from the matrix, but not the translation (position). I'm also not using the built in Transform node because it doesn't have an option for transforming into clip space (though that'd be nice to have).

    Next is that
    UNITY_Z_0_FAR_FROM_CLIPSPACE
    . That's to handle the case where Unity uses an reversed Z depth for precision reasons ... which it does on all platforms but those running OpenGL. The reason why it does that can be read here:
    https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html

    But that particular macro changes what it does based on what the current build target is. Technically we can handle that in Shader Graph with nodes, but it'd be more expensive than just calling that macro to begin with. So just do that using a Custom Function node.
    upload_2020-1-15_14-47-54.png
    Code (csharp):
    1. Out = UNITY_Z_0_FAR_FROM_CLIPSPACE(In);
    Note: For the "Name" field there I just reused the macro's name. Unity uses the name as the name of the function it generates, which would normally be an issue since it's the same as the macro, but the actual function name is
    UNITY_Z_0_FAR_FROM_CLIPSPACE_float
    so there's no conflict. It does need a name though, so keep that in mind.

    Then it's just a matter of recreating the rest of the math in node form and you're done! Right?

    Nope.

    The original vertex shader example is outputting the clip space position directly, as a vertex shader should. But in Shader Graph you need to output the position in local object space as it always applies the equivalent of the
    UnityObjectToClipPos()
    function to that. So, you need to convert to clip space, do the modifications, then convert back to object space so it can go back to clip space. Like before, there's no nice Transform node to do this, but luckily Shader Graph does have access to the Inverse View Projection matrix, which transforms from clip space back to world space! Then you can use the built in Transform node to convert from world to object space.

    So now we're done!

    Still nope. There's one last wiggle, and to be honest I'm not entirely sure what the cause is. But for some reason going from object to clip space, then back from clip to object space, even without doing anything else, results in the Y being flipped. I'm not sure if this is a bug with Unity, or what. But I solved it by multiplying the Y by the Camera's "Z Buffer Sign" (which happens to be the same as
    _ProjectionParam.x
    ). This might not be correct, but it worked for Direct3D since that value is -1 since Direct3D (usually) renders upside down compared to OpenGL and Unity flips the projection matrix to handle that difference and I'm assuming the issue is related.

    So here's the final Shader Graph.
    upload_2020-1-15_15-14-33.png
    It's possible that on OpenGL it's upside down again, or that the magnitude is inverted, but ... *shrug* ... I'm honestly not going to spend the time to test this on OpenGL to find out. ;)
     
    PatHightree likes this.
  3. LeandroGil

    LeandroGil

    Joined:
    Apr 17, 2013
    Posts:
    2
    @bgolus you are LEGEND!!
    I've seen so many of your replies regarding shaders (Always learning from them to solve different issues I found) and was secretly hoping to get one from you for this one =)

    The whole explanation is pretty clear and I got it working just needed to remove this multiply cause it was bending upwards instead
    Capture.JPG

    I consider this closed and solved, the following part is out of curiosity:

    Seeing how limited shadergraph seems regarding some functions and how many extra steps are required, will this have an impact on performance? (this is just a vertex displacement for horizon bending)
    Should I stick to the script version that is literally 3 lines?

    And the last one is it possible to include the script lines into a custom node and avoid the gigantic graph?
    Custom.JPG
    Was to naive to think that the Macro was found maybe the function could resolve too xD

    Once again thank you very much!!
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,327
    Compared to the original hand written shader, yes, the 4 extra matrix multiplies will have something of a performance impact. How much? That depends on how bottle necked you are by vertex count. My guess is the perf difference between having the world bending code vs note will be negligible though, to the point that just looking at raw framerate between the two won't show more than the usual inconsistency Unity already has between runs.
     
unityunity