How to reproduce _Object2World?

Hi all,

I’ve got a shader which generates texture coordinates based on the position of the vertex in the world, this was fairly simple:

Shader "XY" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color ("Tint", Color) = (1,1,1,1)
	}
	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		
		CGPROGRAM
		#pragma surface surf Lambert
		
		sampler2D _MainTex;
		float4    _MainTex_ST;
		fixed4    _Color;
		
		struct Input
		{
			float3 worldPos;
		};

		void surf( Input IN, inout SurfaceOutput o )
		{
			fixed4 c = tex2D( _MainTex, TRANSFORM_TEX( IN.worldPos.xy, _MainTex ) ) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	Fallback "Diffuse"
}

But I want to be able to freeze the texture coordinates at certain times, I figured that worldPos is equal to:

worldPos = mul( _Object2World, v.vertex );

So this could be achieved by passing the Renderer’s localToWorldMatrix to the shader:

renderer.sharedMaterial.SetMatrix( "_CustomMatrix", renderer.localToWorldMatrix );

My shader looks like this:

Shader "XY Fixed"  
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Color ("Tint", Color) = (1,1,1,1)
	}
	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		
		CGPROGRAM
		#pragma surface surf Lambert vertex:vert
		
		sampler2D _MainTex;
		float4    _MainTex_ST;
		fixed4    _Color;
		float4x4  _CustomMatrix;
		
		struct Input
		{
			float3 altWorldPos;
		};
		
		void vert( inout appdata_full v, out Input o )
		{
			o.altWorldPos = mul( _CustomMatrix, v.vertex ).xyz;
		}

		void surf( Input IN, inout SurfaceOutput o )
		{
			fixed4 c = tex2D( _MainTex, TRANSFORM_TEX( IN.altWorldPos.xy, _MainTex ) ) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	Fallback "Diffuse"
}

For anything with non-uniform scale, the texture appears stretched. I don’t think the shader is the problem, as if I multiply the vertex by _Object2World then it looks just the same as when I use the automatically generated worldPos. So it looks like renderer.localToWorldMatrix is incorrect instead, what do I need to do to make it scale properly?

Thanks
J

I think the problem is that Unity does some scaling (I don’t remember whether it was the uniform or the non-uniform scaling) before the data is handed to the GPU. Thus, this scaling isn’t done on the GPU and therefore _Object2World doesn’t include it. Your issue might be related to this behavior.

One solution might be to reverse engineer the scaling behavior by Unity and find out when and which scaling is done in software such that you could take that into account.

Another suggestion that comes to my mind is to have a look at transform.localToWorldMatrix. This is not recommended for use with shaders but your application might be an exception.

Yeah I figured it was something to do with how Unity deals with scaling under the hood… The weird thing is that with _Object2World it works fine, I just want to save the value of _Object2World at certain times, I even tried calling renderer.sharedMaterial.GetMatrix( “_Object2World” ) but I get an error saying the property doesn’t exist… I also did try transform.localToWorldMatrix, but this has the same effect as renderer.localToWorldMatrix. I’ll have a look at UnityEngine.dll with a reflector tomorrow when I get in to work and see if I can find any managed code related to the computation of _Object2World, but I suspect it won’t be that easy to find… What I really need is someone from Unity to come along and answer this question :frowning:

Sorry to bump, but I really need an answer to this!

I got an answer from Aras on twitter, non-uniform scale is applied to vertices before they’re sent to the GPU. So my code has turned into something like this, which seems to be working:

if( renderer.transform.lossyScale.x == renderer.transform.lossyScale.y  renderer.transform.lossyScale.x == renderer.transform.lossyScale.z )
{
   renderer.sharedMaterial.SetMatrix( "_CustomMatrix", renderer.localToWorldMatrix );
}
else
{
   renderer.sharedMaterial.SetMatrix( "_CustomMatrix", Matrix4x4.TRS( renderer.transform.position, renderer.transform.rotation, Vector3.one );
}