Search Unity

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

Discussion in 'Shaders' started by TheCelt, Jan 4, 2020.

  1. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    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:

    Code (CSharp):
    1. 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.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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.
    Code (csharp):
    1. Shader "Unlit/WorldScaledObjectRotation"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.  
    18.             #include "UnityCG.cginc"
    19.  
    20.             struct appdata
    21.             {
    22.                 float4 vertex : POSITION;
    23.             };
    24.  
    25.             struct v2f
    26.             {
    27.                 float4 pos : SV_POSITION;
    28.                 float2 uv : TEXCOORD0;
    29.             };
    30.  
    31.             sampler2D _MainTex;
    32.             float4 _MainTex_ST;
    33.  
    34.             v2f vert (appdata v)
    35.             {
    36.                 v2f o;
    37.                 o.pos = UnityObjectToClipPos(v.vertex);
    38.  
    39.                 float2 scale = float2(
    40.                     length(unity_ObjectToWorld._m00_m10_m20),
    41.                     length(unity_ObjectToWorld._m02_m12_m22)
    42.                     );
    43.  
    44.                 float2 uv = (v.vertex.xz - unity_WorldToObject._m03_m23) * scale;
    45.                 o.uv = TRANSFORM_TEX(uv, _MainTex);
    46.                 return o;
    47.             }
    48.  
    49.             fixed4 frag (v2f i) : SV_Target
    50.             {
    51.                 fixed4 col = tex2D(_MainTex, i.uv);
    52.                 return col;
    53.             }
    54.             ENDCG
    55.         }
    56.     }
    57. }
     
    AshwinMods and PacoPeroEnGlobant like this.
  3. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    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:

    Code (CSharp):
    1. transform.position = _matrix.MultiplyPoint(point);
    2. 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 ?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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.
    Code (csharp):
    1. // c#
    2. Matrix4x4 WorldToUVMatrix = Matrix4x4.TRS(obj.transform.position, obj.transform.rotation, Vector3.one);
    3. planeMaterial.SetMatrix("_WorldToUVMatrix", WorldToUVMatrix);
    Code (csharp):
    1. // shader
    2. // outside of function
    3. float4x4 _WorldToUVMatrix;
    4.  
    5. // in vert function
    6. float4 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1));
    7. o.uv = mul(_WorldToUVMatrix, worldPos).xz;
     
    shamsfk likes this.
  5. TheCelt

    TheCelt

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

    Code (CSharp):
    1. float3 vert = v.vertex;
    2. vert = mul(_WorldToUVMatrix, vert); // the matrix
    3. o.uv = TRANSFORM_TEX(mul(unity_ObjectToWorld, vert).xz,_MainTex);

    Mine came out like this :

    https://i.imgur.com/FCizaY5.mp4

    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.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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).
     
  7. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    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 ?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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.
     
  9. PacoPeroEnGlobant

    PacoPeroEnGlobant

    Joined:
    Jul 30, 2021
    Posts:
    2
    @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
     
  10. marcell123455

    marcell123455

    Joined:
    Jun 18, 2014
    Posts:
    275
    Hi Bgolus,

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

     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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.
     
  12. marcell123455

    marcell123455

    Joined:
    Jun 18, 2014
    Posts:
    275
    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 :)

    https://imgur.com/puKjWw4
     
  13. marcell123455

    marcell123455

    Joined:
    Jun 18, 2014
    Posts:
    275
    Sorry I was silly yesterday, I will try the scale node today with x and z World scale as input.
     
  14. marcell123455

    marcell123455

    Joined:
    Jun 18, 2014
    Posts:
    275
    @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. :)

    Thanks in advance!
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    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.
    upload_2023-1-26_12-51-6.png
     
    marcell123455 likes this.
  16. marcell123455

    marcell123455

    Joined:
    Jun 18, 2014
    Posts:
    275
    Alright, HUGE THANKS for your time and efford! It works! :D