What's the difference between unity_MatrixInvV and unity_CameraToWorld?

I’m using Unity 2020.Literally,it seems that they are both for converting view space to world space.However,I tried and found they are not the same thing.It looks like unity_MatrixInvV is right.So as metioned in title,what’s the difference between unity_MatrixInvV and unity_CameraToWorld?

The unity_MatrixInvV is the inverse View matrix. I mean, that’s probably obvious already. This matches the c# script side cam.cameraToWorldMatrix, and use OpenGL’s convention for view matrices. That is the forward is -Z.

The unity_CameraToWorld is not the view matrix, not really at least. It isn’t the cam.cameraToWorldMatrix either, even though they are confusingly both “camera to world”. That matrix matches the Unity game object transform, excluding scale. In Unity transform forward is +Z, but is otherwise the same. unity_WorldToCamera is equivalent to Matrix4x4.TRS(cam.position, cam.rotation, Vector3.one), with unity_CameraToWorld being the inverse of that.

Additionally, the unity_MatrixInvV, along with the unity_MatrixV and other unity_Matrix* or MATRIX_* matrices are all based on the currently rendering view. For example it could be view matrix for shadow map rendering, or a Blit() for post processing, screen space UI, etc. The unity_Camera* and unity_WorldToCamera matrices are always the camera component that initiated rendering, so these remain unchanged during rendering of shadows or post processing.

hm , so i assume, when i am in a post process shader, and i want to reconstruct a world position:

  • unity_MatrixInvV,unity_MatrixV etc. will be wrong, because they are using the blit camera settings.
  • unity_WorldToCamera is not the view matrix, so wrong
  • passing cam.cameraToWorldMatrix into the shader via script sould be the correct one, but this does not seem to work correctly. it looks exactly like unity_WorldToCamera

Correct. During a blit they’re both just an identity matrix.

Eh yes, but … we’ll get back that.

They might both end up looking broken, but they are not going to be the same. If they look exactly alike, this tells me you’re missing something else.

So back to using unity_WorldToCamera (or rather the inverse) for the case of reconstructing the world position presumably from the camera depth texture. This is the correct way to do it. It is in fact how Unity’s own code does it for directional shadows and post process effects. Because while it’s not technically “the view matrix”, because the view matrix explicitly refers to the matrix being used for rendering, it is an entirely accurate transform matrix for getting the world position in this use case. However I have to wonder how you’re doing the rest of your shader. The camera depth texture is in screen space, which is different than view space, and requires a few steps to convert from to view space. There are several methods out for doing that, like reconstruction NDC or clip space from the screen space UV and depth and using an inverse projection matrix, or setting up a view space ray direction and multiply it by the camera far plane, etc.

Here’s an example shader of mine that shows a number of different approaches for reconstructing the world normal from the depth texture, something that requires reconstructing the (view space) position first.

https://gist.github.com/bgolus/a07ed65602c009d5e2f753826e8078a0

See the viewSpacePosAtScreenUV() and viewSpacePosAtPixelPosition() functions, as well as the commented out note near the end about using unity_CameraToWorld instead of unity_MatrixInvV. The unity_CameraToWorld may not be the inverse view matrix, but it’s so close that it’s trivial to use it like it is. The same kind of setup mention in that comment, invert the z value before transforming by the not-view-matrix, can be used to reconstruct the world space position from the view space position those functions return.

float3 worldPos = mul(unity_CameraToWorld, float4(viewPos * float3(1.0,1.0,-1.0), 1.0)).xyz;

The view space reconstruction is inspired by some code from Keijiro, someone you should want to be familiar with if you’re not already.

thank you for the explanation and the example code! i was missing the uv to viewpos conversion. also, i am still trying to wrap my head around the impact of the viewport z-position.

i am still having different results in perspective, and orthographic camera. but i will try out some more.

you definetely helped me out!

Oh, yeah, that code won’t work for orthographic. That requires a completely different setup. That shader simplifies stuff in a way that works for perspective cameras, but orthographic camera support requires doing some stuff more “correctly”.

See this function from Unity’s screen space shadows, which should work.
https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/DefaultResourcesExtra/Internal-ScreenSpaceShadows.shader#L184

dude, you are a walking library!
thank you for the help, i was able to get the conversion working for both camera types!
i owe you one

main code snippet to make it work for me was:
versionA (according to line 207)

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;

                // Input position is just the UVs going from [-1,1] at the far clip plane (1.0 because we manually passed in the matrix, which Unity stores via GL convention)
                o.viewDir = mul(_InvProjectionMatrix, float4 (o.uv * 2.0 - 1.0, 1.0, 1.0));

                //calculate positions for orthographic camera
                // code from https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/DefaultResourcesExtra/Internal-ScreenSpaceShadows.shader#L88
                float2 clipPos = v.uv * 2 - 1;
                float3 orthoPosNear = mul(unity_CameraInvProjection, float4(clipPos.x, clipPos.y, -1, 1)).xyz;
                float3 orthoPosFar = mul(unity_CameraInvProjection, float4(clipPos.x, clipPos.y, 1, 1)).xyz;
                orthoPosNear.z *= -1;
                orthoPosFar.z *= -1;
                o.orthoPosNear = orthoPosNear;
                o.orthoPosFar = orthoPosFar;
                return o;
            }


            inline float3 computeCameraSpacePos(v2f i, float depth)
            {
                // view position calculation for perspective & ortho cases
                float3 vposPersp = i.viewDir * depth;
                float3 vposOrtho = lerp(i.orthoPosNear, i.orthoPosFar, 1-depth);

                // pick the perspective or ortho position as needed
                float3 camPos = lerp(vposPersp, vposOrtho, unity_OrthoParams.w);
                return camPos;
            }

VersionB (according to line 184

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;

                // Input position is just the UVs going from [-1,1] at the far clip plane (1.0 because we manually passed in the matrix, which Unity stores via GL convention)
                o.viewDir = mul(_InvProjectionMatrix, float4 (o.uv * 2.0 - 1.0, 1.0, 1.0));

                return o;
            }


            inline float3 computeCameraSpacePos(v2f i, float zdepth)
            {
                // View position calculation for oblique clipped projection case.
                // this will not be as precise nor as fast as the other method
                // (which computes it from interpolated ray & depth) but will work
                // with funky projections.
                float4 clipPos = float4(i.uv.xy, zdepth, 1.0);
                clipPos.xyz = 2.0f * clipPos.xyz - 1.0f;
                float4 camPos = mul(unity_CameraInvProjection, clipPos);
                camPos.xyz /= camPos.w;
                camPos.z *= -1;
                return camPos.xyz;
            }