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.
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:
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.
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.
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.