Search Unity

Question 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:
    4
    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
    whoisbma likes this.
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    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. ;)
     
  3. LeandroGil

    LeandroGil

    Joined:
    Apr 17, 2013
    Posts:
    4
    @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!!
     
    daneobyrd and whoisbma like this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    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.
     
  5. whoisbma

    whoisbma

    Joined:
    Feb 10, 2014
    Posts:
    2
    This was a great thread, thanks everybody for your contributions. I've been playing with this technique as well and am puzzled by how taking this approach with a PBR master output node appears to impact cast shadows.

    First pic is my expected shadows from a directional light, second is the actual shadows created when applying the curve world shader material on the objects in the scene (except for the blob in the center). Note that there's no curvature currently applied for comparison sake; when it is on, it doesn't seem to make a difference in the resulting shadows.

    curved pbr graph expected shadows.PNG curved pbr graph shadows.PNG

    Here's the graph, which is more or less what's been described in this thread.

    graph.PNG

    If anyone has any insight to share it would be very appreciated!
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    The problem is the "view projection" matrix being used is currently rendering view's view projection matrix. When you're rendering shadow maps, it's effectively the view projection matrix from the light, not the main camera, so it doesn't match. Any bent wold shader that relies on the view and/or projection matrix then can't properly cast shadows unless you pass in your own matrix from the camera you care about, or only use world space.
     
    laurentlavigne and whoisbma like this.
  7. whoisbma

    whoisbma

    Joined:
    Feb 10, 2014
    Posts:
    2
    Thanks very much @bgolus, that makes sense.

    Here's a graph that seems to do the trick, using world space, for anyone curious:

    curvedpbrgraph_fixed.png
     
    eunheeee likes this.
  8. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    118
    A shader with a custom function calling the UNITY_Z_0_FAR_FROM_CLIPSPACE macro in HDRP won't compile. What is the proper way to do this in HDRP?
     
    Last edited: Apr 25, 2021
  9. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    118
    Here is a graph I put together that seems to reproduce what I was trying to do with UNITY_Z_0_ FAR_FROM_CLIPSPACE. I found a helpful explanation of the macro here: https://www.programmersought.com/article/3493808672/
    I assume this still isn't the most performant way to do this, or may actually be incorrect, so any input is still welcome.
     

    Attached Files:

  10. RR7

    RR7

    Joined:
    Jan 9, 2017
    Posts:
    254
    forgive-a-n00b, but if this is UnityObjectToClipPos(), which accepts a float3 value, where do these x,y,z values get inserted to that graph, i see 3 empty points on the left, but no indication of what goes where?