Search Unity

UnityObjectToViewPos * ProjectionParams.w vs UnityObjectToClipPos

Discussion in 'Shaders' started by MJonee, Apr 25, 2020.

  1. MJonee

    MJonee

    Joined:
    Oct 19, 2013
    Posts:
    20
    Hi, I'm following a tutorial and it calculates the depth of a fragment like this:
    depth = -UnityObjectToViewPos(v.vertex).z * _ProjectionParams.w;

    I don't understand what's the difference with the following:
    depth = UnityObjectToClipPos(v.vertex).z;

    Also why in the first case it's negative that value?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Well, neither of those two match, nor are they the correct way to calculate the depth. If you were using Unity 5.3 or older with an orthographic camera with a near clip plane of 0.0 on Windows, then they would match, but that's the only time they would do so.

    But, I can understand why you would assume they would be the same. So lets break it down a bit.

    The
    UnityObjectToViewPos()
    function transforms an object space position into view space. Unity's view space matches the standard used by OpenGL, which is +X left, +Y up, -Z forward relative to the view (aka, the camera). This roughly matches the camera's game object local space, apart from Unity uses +Z forward, and view space is unaffected by the game object's scale. Why is it -Z forward? Because someone decided to use that 30 years ago arbitrarily when they were writing OpenGL and everyone copied them for consistency. No really, that's it. It's totally arbitrary, but there's not a huge reason to change it and break everything, so they haven't. Modern OpenGL and Direct3D don't even actually care, so when Unity added Direct3D support (Unity was originally OpenGL only) they just kept it -Z.

    So the -Z of the view space position that gives you the linear world scale depth from the camera. The
    _ProjectionParams.w
    is
    1.0 / FarPlane
    , so multiplying the -Z by that gives you a 0.0 to 1.0 value that goes from the camera to the far plane.

    Here's the problem with that. The "real" depth range is from the near plane to the far plane. Most of the time the near plane is relatively small, but the result is the value you get from the
    -viewPos.z / FarPlane
    is slightly off. Really it should be:
    float linearDepth01 = (viewPos.z - NearPlane) / (FarPlane - NearPlane);


    However, it can be "close enough" for a lot of things, especially if your near plane is small, and the things you care about aren't close to the camera. Even some of Unity's own code does this as an optimization for mobile.


    So what about
    UnityObjecToClipPos()
    ? That's the homogeneous clip space position. Clip space can be thought of as "projection space", and this is where things get a little weird. It's a four dimensional representation of a 3D space so that it can be interpolated linearly in screen space. The X and Y values are often referred to as being in the range "-1 to +1", but that's technically wrong, they're actually in the range "-W to +W" where W is the W value of that clip space position. What is W? If you're using a perspective camera, it's
    -viewSpace.z
    ! If you're using an orthographic camera, it's 1!

    Clip space Z is extra weird, because the range depends on the rendering API. In OpenGL it's -W to +W, with -W being the near plane, +W being the far plane. Every other graphics API uses 0.0 to +W as the range, because people realized -W to +W for the depth was dumb, but OpenGL came first and has to live with it now. (Technically newer versions of OpenGL allow it to be configured to be whatever you want, so some people do that, but Unity doesn't since the GPUs that support that also support Vulkan.) Now, you might expect 0.0 to be the near plane and +W to be the far plane, but Unity (and almost everyone else at this point) flip that so 0.0 is the far plane and +W is the near plane. The reasons for this have to do with floating point math precision.
    https://developer.nvidia.com/content/depth-precision-visualized

    So if you want it to match the
    -viewPos.z
    linear depth's 0.0 near 1.0 far on non-OpenGL platforms you'd have to do:
    float depth01 = 1.0 - clipPos.z / clipPos.w;

    and on OpenGL
    float depth01 = (clipPos.z / clipPos.w) * 0.5 + 0.5;

    But don't do that because it still won't match!

    Clip space Z isn't linear when using a perspective projection! Even after dividing by W and inverting, a depth value of 0.5 is not halfway between the near and far plane.

    Luckily there are some handy functions built into Unity to handle this for you ... though they're designed to work with the depth value that the fragment shader sees, which ... again ... isn't the same as what you have in the vertex shader...

    But for non-OpenGL it would be used like this:
    float linearDepth01 = Linear01Depth(clipPos.z / clipPos.w);

    (It does the inversion from 1.0 - 0.0 to 0.0 - 1.0 for you.)


    TLDR: There are a lot of different "depth" values, which one do you want?
     
  3. joshuacwilde

    joshuacwilde

    Joined:
    Feb 4, 2018
    Posts:
    731
    @bgolus to be clear, the Linear01Depth function should be used in the fragment shader, not the vert shader?

    I am trying to write the most performant linear depth output shader as possible for some HiZ culling.

    To be clear, I want depth from the camera plane
     
    Last edited: Jun 11, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    What exactly are you attempting to do? The Linear01Depth is just a function for converting non-linear screen depth to linear screen depth. I can be used in whichever situation that’s needed. Usually that is in the fragment shader, but not always. Usually there’s a cheaper way to get the depth in the vertex shader if you’re looking to get that data from the current mesh, and it doesn’t make sense to do it in a vertex shader if you’re sampling the camera depth texture.
     
  5. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    This didn't help my problem, but daaamn, Ben. Every time I come across one of your comments (and that's at least twice a day) I learn something new. Thank you so much for taking the time to teach these things. I can safely say 80% of my rendering knowledge comes from reading your comments. Shaders are an obscure subject and every time I face a problem I end up googling for days. You're a blessing. Thank you.