How to get UNITY_MATRIX_MVP in Unity

I need to make a shader which freezes UNITY_MATRIX_MVP. So I figured I can calculate it in C# and then use SetMatrix() to freeze it. However, I can’t seem to get the correct MVP matrix. If I could fetch the UNITY_MATRIX_MVP matrix from the shader, store it in C#, then set it again in the shader, that would work as well, but I can’t use GetMatrix() before SetMatrix(). So that won’t work either.

This is the code I tried:

Matrix4x4 mat = Matrix4x4.identity;
mat[2,2] *= -1.0f;
Matrix4x4 frozenMatrixMVP = mat * (Camera.main.transform.worldToLocalMatrix  * vidTexPatch.transform.localToWorldMatrix);

and

Matrix4x4 p = camera.projectionMatrix;
p[2,2] = -p[2,2];
p[3,2] = -p[3,2];
Matrix4x4 frozenMatrixMVP = p * Camera.main.transform.worldToLocalMatrix * vidTexPatch.transform.localToWorldMatrix;

and

bool d3d = SystemInfo.graphicsDeviceVersion.IndexOf("Direct3D") > -1;
Matrix4x4 M = vidTexPatch.transform.localToWorldMatrix;
Matrix4x4 V = Camera.main.worldToCameraMatrix;
Matrix4x4 P = Camera.main.projectionMatrix;
if (d3d) {
		// Invert Y for rendering to a render texture
		for (int i = 0; i < 4; i++) {
			P[1,i] = -P[1,i];
		}
		// Scale and bias from OpenGL -> D3D depth range
		for (int i = 0; i < 4; i++) {
			P[2,i] = P[2,i]*0.5f + P[3,i]*0.5f;
		}
}
Matrix4x4 frozenMatrixMVP = P*V*M;

None of that works.
Any ideas?

What do you mean, fetch it from the shader? The shader’s original source for the MVP is from code, the video card doesn’t know anything, change anything, nor stores anything from the MVP, it gets it from code.

I’m also unsure what you mean with “freeze” the MVP, do you mean stop the screen from moving, etc… What is the effect you want to accomplish with this freezing and why does it need to adjust the MVP to achieve this effect?

Edit: forgot to add that the third example should be the correct way to set an MVP up in code in Unity.

Fetch it from the shader would be something like:
Matrix4x4 frozenMatrixMVP = vidTexPatch.renderer.material.GetMatrix(“FROZEN_MATRIX_MVP”);
I am sure the shader actually does know about the MVP, because “FROZEN_MATRIX_MVP” is build in.

With freezing I mean that the values of that matrix don’t change anymore, even if the camera and or the object transform changes. This is for an augmented reality video texture special effect.

Thanks, I will try to get the 3rd example to work again.

Try this:

Camera cam = Camera.main;
Matrix4x4 P = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false);
Matrix4x4 V = cam.worldToCameraMatrix;
Matrix4x4 M = renderer.localToWorldMatrix;

Matrix4x4 MVP = P * V * M;

I have wasted time figuring this out before aswell. In the end I found a gitHub page from Aras that led me to the solution.

2 Likes

Ok, I got it to work (sort of)

Here is my shader:

Shader "Custom/Test" {
	Properties {
		_MainTex("Texture", 2D) = "white" { }	
	}
	
	SubShader{	
	
	Pass{
	
	Cull Back
	
	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	
	#include "UnityCG.cginc"
	
	sampler2D _MainTex;
	float4x4 _MATRIX_MVP;

	struct v2f{
		float4  pos : SV_POSITION;
		float2  uv : TEXCOORD0;
	};

	v2f vert(appdata_base v){
		
		v2f o;
		float2 screenSpacePos;
		float4 clipPos;
	
		//Convert position from world space to clip space.
		//Only the UV coordinates should be frozen, so use a different matrix
		clipPos = mul(_MATRIX_MVP, v.vertex);
		
		//Convert position from clip space to screen space.
		//Screen space has range x=-1 to x=1
		screenSpacePos.x = clipPos.x / clipPos.w;
		screenSpacePos.y = clipPos.y / clipPos.w;

		//the screen space range (-1 to 1) has to be converted to
		//the UV range 0 to 1 
		o.uv.x = (0.5f*screenSpacePos.x) + 0.5f;
		o.uv.y = (0.5f*screenSpacePos.y) + 0.5f;
	
		//The position of the vertex should not be frozen, so use 
		//the standard UNITY_MATRIX_MVP matrix
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

		return o;
	}
	
	half4 frag(v2f i) : COLOR{
	
		half4 texcol = tex2D(_MainTex, i.uv);
		return texcol;
	}
	
	ENDCG
	
	}
	}
}

From Unity I call:

Matrix4x4 P = GL.GetGPUProjectionMatrix(Camera.main.projectionMatrix, false);
Matrix4x4 V = Camera.main.worldToCameraMatrix;
Matrix4x4 M = debugObject.renderer.localToWorldMatrix;
Matrix4x4 MVP = P * V * M;
debugObject.renderer.material.SetMatrix("_MATRIX_MVP", MVP);
1 Like

Great!

What was it in the end?

Not sure. I changed many things along the way and I lost track :expressionless:
Anyway, it is working now.

Edit:
Ok, I found out what I was doing wrong. Besides some silly mistakes like adding the wrong shader to my object and using the wrong game object reference, the main thing was that the scale of the plane should be the same in x, y, and z. I had a scale of 0.02, 1, 0.02 and that wasn’t working. When I changed the scale to 0.02, 0.02, 0.02, it suddenly worked. Not sure why that is though…

@Elecman , is there a way to use your shader to modify Unity’s projection matrix to do something like this?

I’ve been able to do this in Unity’s update loop without shaders, but I’d prefer to have more customization options using a shader.

I don’t think so because my shader is specifically designed for modifying and freezing a video texture for AR. Not sure if that is possible in a shader at all…