Search Unity

Question [URGENT] a STRANGE problem about using unity_CameraInvProjection

Discussion in 'Shaders' started by gannchuukaju, Mar 18, 2022.

  1. gannchuukaju

    gannchuukaju

    Joined:
    Jun 16, 2021
    Posts:
    8
    Recently I searched many articles about how to do full-screen Raymarching. There is a point that transform points(actually a quad with 4 points generated by Blit) from ndc space to view space and compute the direction of frustum corner ray. Then we can get the interpolated raymarching direction from camera to each pixel. The theory is easy but... the specific methods really cause a headache. Here is the method1:
    Code (CSharp):
    1.  
    2.           v2f vert(appdata v)
    3.             {
    4.                 v2f o;
    5.                 VertexPositionInputs vertexPos = GetVertexPositionInputs(v.vertex.xyz);
    6.                 o.vertex = vertexPos.positionCS;
    7.                 o.uv = v.uv;
    8.                 float3 viewDir = mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, -1)).xyz  ;
    9.                 o.viewDir = mul(unity_CameraToWorld, float4(viewDir, 0)).xyz;
    10.                 return o;
    11.             }
    You can see this method in https://github.com/BenSnow6/Drone-f...Test/Image Effect Test/ImageEffectTest.shader
    I think this is a very strange way to get the viewDir. Especially after I saw articles about reconstructing posCS using screen [0,1] uv and rawDepth. And here is the method2:
    Code (CSharp):
    1.  
    2.                 float ndcZ = (SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
    3.                 // ndc to world pos => worldPos = _InvVP * ndc; //  _InvVP =  Matrix4x4.Inverse(GL.GetGPUProjectionMatrix(camera.projectionMatrix, false) * camera.worldToCameraMatrix);
    4.                 float4 fwp = mul(_InvVP, float4(i.uv * 2 - 1, ndcZ, 1));
    5.                 fwp /= fwp.w;
    6.                 return fwp;
    7.  
    You can also see this method in
    https://github.com/LGhassen/Scatter...tererShaders/Assets/Shaders/DepthCommon.cginc
    And here are questions:
    1. I can understand why use "/fwp.w"(https://answers.unity.com/questions/1657972/what-does-unity-camerainvprojection-actually-ishow.html), but why method1 does not use like "/o.viewDir.w"? That really confuses me.
    2. In method2 , the ndc.w is set 1 because forward after w-division ndc.w is 1. But why the ndc.w is -1 in method1? I try modifying the ndc.z but nothing changed while perspective is totally wrong when ndc.w is set to 1. That is so strange.
    3. What is the different in GL.GetGPUProjectionMatrix() , camera.XXMatrix , UNITY_MATRIX_I_P?
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    gannchuukaju likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    unity_Camera*
    matrices don't match the various
    UNITY_MATRIX_*
    matrices used for rendering.

    UNITY_MATRIX_V
    is the view matrix which transforms from world space to view space (aka eye space or camera space), which in Unity uses a -Z forward. This is equivalent to the c#
    camera.worldToCameraMatrix
    matrix, which does not match the game object's transform matrix as it ignores scale, and Unity's transforms are +Z forward.
    UNITY_MATRIX_I_V
    is the inverse of that.
    UNITY_MATRIX_P
    is the projection matrix which transforms from view space to clip space using the current graphics API's projection model. This is equivalent to
    GL.GetGPUProjectionMatrix(camera.projectionMatrix, false)
    . There is no built in
    UNITY_MATRIX_I_P
    for the inverse!

    unity_CameraToWorld
    is the world to camera space matrix, which like the view matrix has no scale from the camera's game object, but still uses +Z forward. So these two matrices are not the same! This is equivalent to c#
    Matrix4x4.TRS(camera.transform.position, camera.transform.rotation, Vector3.one)
    .
    unity_WorldToCamera
    is the inverse.
    unity_CameraProjection
    is the default OpenGL projection matrix which transforms from view space to OpenGL clip space. This is equivalent to
    camera.projectionMatrix
    as Unity defines all of the projection matrices on the camera in OpenGL form until they're sent to the GPU, at which point it transforms them into the projection matrix for the current platform via the above mentioned
    GL.GetGPUProjectionMatrix()
    function. Note, OpenGL's clip space is uniquely -1 to 1 near to far on the z where all other APIs use 1 to 0 near to far. So on OpenGL platforms,
    UNITY_MATRIX_P
    and
    unity_CameraProjection
    do match, but they do not on all other APIs. There are also cases where Unity will flip the Y axis of the projection matrix for other APIs to make them match OpenGL ... which renders upside down compared to all other APIs.
    unity_CameraInvProjection
    is the inverse.

    Using
    unity_CameraInvProjection
    can be nice because it works "the same" regardless of which API is in use for calculating a view direction from the screen UV. But the depth texture isn't the same depending on if it's OpenGL or not. It also only gets you the view space view direction, not the world space one.

    Using a manually calculated
    _InvVP
    has the advantage of going straight to world space in one step, and you can use the raw depth texture value as the input. But of course it requires you have an additional script.
     
    gannchuukaju likes this.
  4. gannchuukaju

    gannchuukaju

    Joined:
    Jun 16, 2021
    Posts:
    8
  5. gannchuukaju

    gannchuukaju

    Joined:
    Jun 16, 2021
    Posts:
    8
    Thank you, Mr.Bgolus! I have seen your high-quality answers in many threads. Now I understand that :
    1. camera.worldToCameraMatrix == UNITY_MATRIX_V
    2.
    · GL.GetGPUProjectionMatrix(came.projectionMatrix, false) == UNITY_MATRIX_P (in the current graphics API)
    · unity_CameraProjection == came.projectionMatrix == UNITY_MATRIX_P (in OpenGL API)
    3.
    · camera.cameraToWorldMatrix = Matrix4x4.TRS(camera.transform.position, camera.transform.rotation, new Vector3(1,1,-1))
    · unity_CameraToWorld=Matrix4x4.TRS(camera.transform.position, camera.transform.rotation, Vector3.one)
    So if we try transforming from view space to world space by unity_CameraToWorld , the Z of view space(right-handed coordinate system) should be inverse first.
    4. We can use unity_CameraInvProjection to go back to view space if we do not use depth like computing screen-based raymarching direction. But when we need depth data like reconstructing posCS or posWS, the Z range of different graphics API should be considered.
    -------------------------------------------------------------------------------------------------------------------------------------------------------
    But I am still confused in Why "float3 viewDir = mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, -1)).xyz " is correct ?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    It's a very odd setup, I agree. There's an old graphics programmer idiom I like to pull out at times like this.

    "Make sure you have an even number of sign errors."

    The
    unity_CameraInvProjection
    matrix converts from an OpenGL clip space to a -Z forward view space. Using a w of -1 I think results in the equivalent of a +Z forward vector. Which then using the +Z forward
    unity_CameraToWorld
    matrix gets you a proper world space vector.
     
    gannchuukaju likes this.
  7. gannchuukaju

    gannchuukaju

    Joined:
    Jun 16, 2021
    Posts:
    8
    Yes, you are right! I just do this:
    Code (CSharp):
    1.  float3 viewDir = mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, 1)).xyz  ;
    2. viewDir.z=-viewDir.z ;
    3. o.viewDir = mul(unity_CameraToWorld, float4(viewDir, 0)).xyz;
    It is equivalent to "float3 viewDir = mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, -1)).xyz " , that's amazing!
    Now it leaves one last question. Why not divide "mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, -1)).w"?
    Here is my draft:

    As you can see, to compute viewDir, it still need divide viewDir.w(like mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, 1)).w ) . But the divition dispears in method1.
    Here is my conjecture :
    I notice that unity_CameraInvProjection may be this:

    Az+B =1/z' xyz is the position of ndc space while x'y'z' of view space.
    So if we need to compute real view space position, multiplying z' by result of the img above is necessary.
    "multiply z' " means "divide 1/z' " and you can according to "Az+B =1/z' " and "mul(unity_CameraInvProjection, float4(v.uv * 2.0 - 1.0, 0, 1)).w==Az+B " ) affirm that "multiply z' " is is equivalent to "divide viewDir.w".
    But if we just need to compute view space direction, not do "multiply z' " is okay. Because no matter x'y'z' devide by any of the same numbers , the result still stands for the direction. Oh that is really exciting if the conjecture is correct!
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You’re over thinking the problem.

    In the example shader the first thing it does to that vector in the fragment shader is this:
    Code (CSharp):
    1. float3 rayDir = normalize(input.viewVector);
    normalize(viewVec.xyz)
    is equal to
    normalize(viewVec.xyz / n)
    where n is any positive value. So there’s no reason to do a divide if it isn’t going to affect the results.

    But also because the input w was -1, I believe the output w is also going to be negative and dividing by that would undo the whole “trick”. You’d have an odd number of sign errors.;)
     
    gannchuukaju likes this.
  9. gannchuukaju

    gannchuukaju

    Joined:
    Jun 16, 2021
    Posts:
    8
    Oh,So that's it! Cheers!:)