UVs in world space, how to rotate to match object's rotation?

Hi

I have a simple bit of code that apply’s uv’s from world space rather than object space. But i still need the texture to rotate with the object.

In the vertex shader i have:

o.uv = TRANSFORM_TEX(mul(unity_ObjectToWorld, v.vertex).xz,_MainTex);

When i rotate the object, the texture needs to rotate with it, whilst still remaining in world space for position. How do i apply a rotation to it to match the rotation of my gameobject ?

Thanks.

There’s a number of ways to skin this cat, depending on exactly what you need. But it mainly comes down to this:

The unity_ObjectToWorld is a transform matrix that transforms the vertex positions from local object space to world space. The matrix holds rotation, scale, and translation, with with a little (or a lot) of work can be extracted to use as much or as little of those components as you need.

However world space position and object space rotation is one of the harder ones. Not because the code is hard, but because it’s potentially impossible. Depending on exactly what you’re envisioning, there’s no perfect way to reconcile object space rotation with world space position.

So there’s three ways to go about it. The two easy ways are to scale object space UVs by the object to world scale and then either offset the UVs by the world to object offset, or not. The hard way is to have world space tiles that are individually rotated and blend between them.

Here’s the “easy” way.

Shader "Unlit/WorldScaledObjectRotation"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

                float2 scale = float2(
                    length(unity_ObjectToWorld._m00_m10_m20),
                    length(unity_ObjectToWorld._m02_m12_m22)
                    );

                float2 uv = (v.vertex.xz - unity_WorldToObject._m03_m23) * scale;
                o.uv = TRANSFORM_TEX(uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}
2 Likes

Hi

Thanks for the reply, this works (see the little square in image), though one thing i was wondering, is it possible to pass in data from another game object to the shader to translate/rotate the texture to match rather than use the object its tied to?

For example i have a script on a particular object that has a matrix TRS that tells me the rotation of it, and its translation (scale is always 1) and i want to update the output texture (which you see on the right hand side in the big mesh) to match the little object without moving vertices so that the grid lines up.

This is a visual of what i am trying to do, the little object is some random object with a local grid, the big object follows the camera, but the grid texture for it needs to match the little object’s translation/rotation, but only its texture, not its vertices:

The vertices of the big grid mesh only move based on where ever the camera moves.

I manipulate the little object with my TRS matrix:

transform.position = _matrix.MultiplyPoint(point);
transform.rotation =  _matrix.rotation;

My first guess was to iterate through the mesh uv’s in C# and transform each point by the matrix, but i’d rather do it in a shader for performance gains.

Is there a way to do that at all ?

Pass the world space rotation and position of the object you’re trying to match as a matrix to the material of the main plane.

// c#
Matrix4x4 WorldToUVMatrix = Matrix4x4.TRS(obj.transform.position, obj.transform.rotation, Vector3.one);
planeMaterial.SetMatrix("_WorldToUVMatrix", WorldToUVMatrix);
// shader
// outside of function
float4x4 _WorldToUVMatrix;

// in vert function
float4 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1));
o.uv = mul(_WorldToUVMatrix, worldPos).xz;
1 Like

Ah that works, though i wish i could understand the math better! I originally tried something like that but was doing it like this:

float3 vert = v.vertex;
vert = mul(_WorldToUVMatrix, vert); // the matrix
o.uv = TRANSFORM_TEX(mul(unity_ObjectToWorld, vert).xz,_MainTex);

Mine came out like this :

What was i getting wrong with my math here? The rotation was going wrong for me, yours fixed it, the translation i had correct though.

I can see two issues. First is the “world to UV” matrix is for transforming from world to “UV” space. The position in the v.vertex isn’t in world space, it’s in local space, so you were applying the matrices out of order. In some cases that might still work, but there was also a second issue here. Matrix multiplication of a position is a float4x4 multiplied with a float4 value, not a float3 value. If you use a float3 you’re only getting the rotation and scale, but not the translation. So even if the matrices were applied in the correct order, you weren’t ever getting the position offset meaning your grid was probably rotating around either the world origin or the mesh’s own pivot (rather than the other object’s pivot).

Ah okay the world space i understand, though the vertex requiring a w = 1 i did not expect.

If i always have a transform scale of (1,1,1) can i reduce the amount of data past to the shader by using a 3x3 matrix from the transform some how which only has translation and rotation as an optimisation ?

I mean you can pass in a 3x3 matrix and a float3 position as 3 float4 values, but really you’d be wasting time in the c# breaking the matrix up and in the shader reconstructing it. The extra float4 of data is inconsequential unless you’re planning on doing this for thousands of objects.

@bgolus you are full of love… the “easy” it’s perfect for me, and now I’ll take a moment to understand it, but if you have some links that make it more fully understandable, that would be even more lovingly

Hi Bgolus,

any idea how to build this in shader graph / amplify shader editor :slight_smile: ?

Shader Graph and Amplify both have Object Scale nodes that get exactly the same scale information as above. And transform nodes for converting between different spaces. The description of the steps the code is doing should be enough to replicate it in either.

Hi bgolus,

thanks for your quick answer! I agree, guess I have to be more specific. I am asking myself what these ._m00_m10_m20 for example behind unity_ObjectToWorld mean. is this x and z?

thank you :slight_smile:

https://imgur.com/puKjWw4

Sorry I was silly yesterday, I will try the scale node today with x and z World scale as input.

@bgolus ,
Hey Ben,

This problem is giving me a headache for at least a week now. Whatever I try, I can’t get the wanted result. I am not a shader expert
Like you. I would really appreciate a bit more assistance. :slight_smile:

Thanks in advance!

Here’s a recreation of the original example in Shader Graph. This assumes you’re using a mesh that’s aligned like the default plane mesh. You can get rid of the transform and subtract nodes if you just want the texture to scale with the object’s size.

1 Like

Alright, HUGE THANKS for your time and efford! It works! :smile: