How to make a localToWorld matrix for shader parameters?

Hi, I would like to make a matrix which is passed to a shader like “_Projector” matrix. It was not so difficult if localToWorld matrix had no scale. However, if localToWorld matrix had scale, it seemed like the scale parameters were applied twice to the vertices. To check this problem, I tested the following script:

using UnityEngine;
public class ManualTransform : MonoBehaviour {
	void OnWillRenderObject()
	{
		//Matrix4x4 m = renderer.localToWorldMatrix;
		Matrix4x4 m = Matrix4x4.identity;
		m.SetColumn(3, renderer.localToWorldMatrix.GetColumn(3));
		renderer.material.SetMatrix("mL2W", m);
	}
}

And draw a sphere with this shader:

Shader "Custom/ManualTransform" {
	SubShader {
		Pass {
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			float4x4 mL2W;
			float4 vert(float4 vertex : POSITION) : SV_POSITION
			{
				return mul(UNITY_MATRIX_VP, mul(mL2W, vertex));
			}
			fixed4 frag() : COLOR
			{
				return fixed4(1,0,0,1);
			}
			ENDCG
		}
	} 
}

Even though, the matrix “mL2W” was identity, the sphere was deformed when I set some scale. It seems like the vertices are already scaled before they are passed to the vertex shader. So, if I use the code in the line comment in OnWillRenderObject, scale parameters will be applied twice.

Here is a workaround:

public class ManualTransform : MonoBehaviour {
	void OnWillRenderObject()
	{
		Vector3 scale = transform.localScale;
		Matrix4x4 m = renderer.localToWorldMatrix * Matrix4x4.Scale(new Vector3(1.0f/scale.x, 1.0f/scale.y, 1.0f/scale.z));
		renderer.material.SetMatrix("mL2W", m);
	}
}

However, it doesn’t look a good idea, because I’m not sure if the vertices are always scaled before vertex shader.

So, my questions are:

  1. Is it true that vertices of a mesh are scaled before they are passed to a vertex shader?
  2. If so, does it happen always, or under some conditions? Is there any way to check if the vertices are already scaled or not, like renderer.isPartOfStaticBatch?
  3. Shouldn’t renderer.localToWorldMatrix have no scale, if vertices are already scaled?
  4. Is there a better way to make a matrix which contains localToWorld matrix with scale?

It would be appreciated if I could get answers for any of them. Thanks in advance!

Hi, those vertices are pre-scaled because you must be using non-uniform scale on the mesh. Unity applies the scale before passing vertices to the shader. Try setting the same scale for all axis to see if this changes something. Also, note that Unity 5 will remove this behavior (info about this in the Unity blog)

You already have access to some transform matrices in the shader, check this page: http://docs.unity3d.com/Manual/SL-BuiltinValues.html.

Also, another effect you may see is the merge of meshes due to dynamic batching: this will change the origin & coordinates of the vertices of batched meshes.

Lastly, if you still have to pass parameters to a shader during play, you may want to check Unity - Scripting API: Renderer.SetPropertyBlock. It’s faster than using the material and is done on one object/renderer only. Using renderer.material like you do duplicates the material for this object, leading to un-batchable meshes, more memory usage etc…