Search Unity

Moving UVs of a texture by an angle 0-360°

Discussion in 'General Graphics' started by Quatum1000, Aug 1, 2017.

  1. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Hi everyone,

    I'd like to move the UV of a texture by an angle 0°-360°
    The angle is represented eg. in 90° steps by the UV's like this.

    Code (CSharp):
    1.  
    2. 000°  uv_x = 0, uv_y = -1
    3. 090°  uv_x = -1, uv_y = 0
    4. 180°  uv_x = 0, uv_y = 1
    5. 270°  uv_x = 1, uv_y = 0
    Untitled-1.jpg

    Does any know how to calculate the angle to the correct vector float2(x,y) ?
    I think it should be done with sin/cos but I didn't get this.
    Thank you.
     
    Last edited: Aug 1, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    Quatum1000 likes this.
  3. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thank you, I read it.

    But I want to scroll the texture based on an angle only. Therefore it would be a waste of performance to do this per pixel in the shader because the movement based 1 frame and not per pixel.

    What I need is the math of rotate angle to UV(x,y)

    Script:
    Code (CSharp):
    1. Shader.SetVector("_AngleToUV",  Angle2UV(angle));
    2.  
    3. Vector2 Angle2UV(double angle) {
    4. ///// .... code for transform angle to UV(x,y)...  //////
    5. return v2
    6.  
    Shader:
    Code (CSharp):
    1. Properties{
    2.     _AngleToUV("_AngleToUV(x,y)", Vector) = (0,0,0,0)
    3.  
    4. uniform half4 _AngleToUV;
    5. ...
    6. fixed mx = frac(_AngleToUV.x * _Time);
    7. fixed my = frac(_AngleToUV.y * _Time);
    8. c = tex2D(_Texture, IN.worldPos.xz + fixed2(mx,my));
    It's possible to solve by code logic, but that cause in quirky if else lerp commands.
     
    Last edited: Aug 1, 2017
  4. ifurkend

    ifurkend

    Joined:
    Sep 4, 2012
    Posts:
    350
    According to your picture, it simply is:
    uv.xy = float2(-sin(_AngleToUV), -cos(_AngleToUV));

    TBH, since you're seemingly writing Surface Shader, I don't know why such simple maths would cause performance issue.
     
    Last edited: Aug 2, 2017
    Quatum1000 likes this.
  5. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    You could do it per-vertex if you prefer. You need to transform every UV, so you're going to have to do it at least per-vertex.

    See https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html for how to add a per-vertex function to a surface shader, under the Normal Extrusion with Vertex Modifier heading.
     
    Quatum1000 likes this.
  6. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thanks for your reply.

    Based on the small code, _AngleToUV.x and _AngleToUV.y should be calculated previously when it's used in the shader.
    And what values _AngleToUV.x/y should have if they transformed with -sin/-cos inside the shader? If your solution should be _AngleToUV.x/y = angle then your example produce complete wrong values eg on 180° (0.8011526 -0.5984601)

    Hmm, I want to simply move/scroll a texture by UV's based on a given angle in the surf shader. Not deform vertexes / texture in local space by an Extrusion. Perhaps it's described not clear enough: Thread "Moving UVs of a texture by an angle 0-360°" A small math code example in c# here would be enough.:)

    Code (CSharp):
    1.  
    2. Vector2 Angle2UV(double angle) {
    3. ///// .... code for transform angle to UV(x,y)...  //////
    4. return v2
     
  7. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    I simply provided those links to show you how to write a per vertex manipulation. The UV is a per vertex attribute. You can replace the extrusion code with something that modifies the UV instead..

    v.uv_MainTex = somethingClever;
     
    Quatum1000 likes this.
  8. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    The math for rotating a texture doesn't change depending on if you're doing it per pixel, or per vertex, or pre-computing it.

    float s = sin ( radians );
    float c = cos ( radians );
    float2x2 rotationMatrix = float2x2( c, -s, s, c);
    uv = mul ( uv - 0.5, rotationMatrix ) + 0.5;


    Also that thread has several examples of doing the UV rotation in the vertex shader (in fact most are doing it that way).

    If you don't want to compute the cos and sin in the shader, then store them in a vector like you're attempting. But make sure you're passing the correct information to the Mathf.Cos and Mathf.Sin functions. They both take radians, just like cos and sin in the shader. Unity's C# has a handy value to do that with.

    https://docs.unity3d.com/ScriptReference/Mathf.Deg2Rad.html
     
    Quatum1000 likes this.
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    Those values, 0.8011526 and -0.5984601, is what happens when you pass the value 180 to a Cos and Sin function expecting radians and not degrees. The math is correct, your expectation for the values to use are wrong. That's why I mentioned degree to radian conversion in my first post.

    180° == π (3.141592...) radians
     
    Quatum1000 likes this.
  11. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thank you boglus. I will test and check all this for performance too.

    From dreamtech Mitko told me to use in script
    var v2 = Quaternion.AngleAxis(angle, Vector3.forward) * -Vector2.up;
    v2.x = -v2.x;
    and passt to the shader.
    Thank you very much to all.
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    I mean, that works, but it's a lot more expensive. Calculating the quaternion using AngleAxis is doing the exact same cosine and sine calculations needed to do the rotation matrix, along with a ton more work, and then you're applying that quaternion to the vector, which is also a ton more work, just to get the cosine and sine value.
    Code (CSharp):
    1. Vector2 AngleToSinCos(float angle) {
    2.     float radians = Mathf.Deg2Rad * angle;
    3.     return new Vector2(
    4.             Mathf.Sin(radians),
    5.             Mathf.Cos(radians)
    6.         );
    7. }

    For reference, the code you posted above is doing something like this:
    Code (CSharp):
    1. Quaternion AngleAxis(float angle, Vector3 axis) {
    2.     // the below is what Vector3.normalize does
    3.     float axisMagnitude = Mathf.Sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
    4.     if (axisMagnitude > 0.0001f) {
    5.         axis.x /= axisMagnitude;
    6.         axis.y /= axisMagnitude;
    7.         axis.z /= axisMagnitude;
    8.     } else {
    9.         axis = new Vector3(0f, 1f, 0f);
    10.     }
    11.  
    12.     // now we create the quaternion
    13.     float halfAngleRadians  = angle * Mathf.Deg2Rad * 0.5f;
    14.     float sineHalfAngle = Mathf.Sin(halfAngleRadians);
    15.     float qx = axis.x * sineHalfAngle;
    16.     float qy = axis.y * sineHalfAngle;
    17.     float qz = axis.z * sineHalfAngle;
    18.     float qw = Mathf.Cos(halfAngleRadians);
    19.  
    20.     return new Quaternion(qx, qy, qz, qw);
    21. }
    And that's just to create the quaternion. That Quaternion * Vector2 is even more math than that!
     
    Last edited: Aug 2, 2017
    RendergonPolygons and Quatum1000 like this.
  13. ifurkend

    ifurkend

    Joined:
    Sep 4, 2012
    Posts:
    350
    If I understand correctly, Quatum1000 only wants to "scroll" the UV instead of "rotating", so it even saves the matrix multiplication for that matter.
     
    bgolus likes this.
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    So he just needs:
    uv += AngleToSinCos(angle) * scrollAmount;

    A guess a useful bit of information is the value that comes out of the AngleToSinCos function is a normalized Vector2 direction that should match the angle orientation he described above. If you multiply that by some value you can control the pan speed as well.


    If I was going to do this in a shader I would do something like:

    float2 scrolledUV = i.uv + frac(float2(sin(_Angle * UNITY_PI / 180), cos(_Angle * UNITY_PI / 180)) * _Time.y * _PanSpeed);

    For mobile I might possibly bother doing this with a precomputed vector, but probably not.
     
    Last edited: Aug 2, 2017
    Quatum1000 likes this.
  15. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    AngleToSinCos works and the scrolling does great so far. That's really nice.

    I used:
    half _PanSpeed = 0.1;
    half scale = 0.001;
    float2 scrolledUV = (IN.worldPos.xz * scale.xx) + frac(float2(sin(_Angle * UNITY_PI / 180), cos(_Angle * UNITY_PI / 180)) * _Time.y * _PanSpeed);


    Sampling the texture from the world position requires IN.worldPos.xz * scale.xx.

    But changing the angle in real-time cause the texture to float extremely fast and bad. Because the vector2 of frac() is now virtually up-scaled by 1000 through IN.worldPos.xz * = 0.001;

    float2 scrolledUV = (IN.worldPos.xz * scale.xx) + float2(sin(_Angle * UNITY_PI / 180), cos(_Angle * UNITY_PI / 180)) * frac(_Time.y * _PanSpeed);

    Cause a lot smoother results, but the texture jumps if frac() restarts from 0. frac() should become 0 if the moving length of the scrolledUV.x/y is > 1.

    If you have an idea by use IN.worldPos.xz * scale.xx and angle change in real-time, without fast texture floating and jumping, you made my day!

    But I'm afraid it isn't possible in this case.

    Untitled-1.jpg

    I have a simple small testing environment for this.
     
    Last edited: Aug 2, 2017
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    Has nothing to do with the frac(), that's you can't change the angle with out it looking terrible using this method. If you need to change the angle and have everything behave gracefully you must do it in script. This is because you have to accumulate the offset each frame, which is something you can't do in the shader.

    In the shader you just have the angle, and the amount of offset (the time * pan rate), so if you change the angle you're rotating that already offset position. Similarly changing the pan rate will look terrible too.

    So, instead you'll want to do something like this:
    Code (CSharp):
    1. public float angle;
    2. public float panSpeed;
    3. public Material mat;
    4. public String textureSlot = "_MainTex";
    5.  
    6. public Vector2 offset = Vector2.zero;
    7.  
    8. void Update() {
    9.     // get pan direction vector
    10.     Vector2 dir = AngleToSinCos(angle);
    11.  
    12.     // calculate offset delta, add to current delta
    13.     offset += dir * Time.deltaTime * panSpeed;
    14.  
    15.     // apply modulo to wrap offset between 0~1
    16.     offset.x = Offset.x % 1f;
    17.     offset.y = Offset.y % 1f;
    18.  
    19.     // assuming you're using a texture slot with TRANSFORM_TEX in the shader
    20.     mat.SetTextureOffset(textureSlot, offset)
    21.  
    22.     // otherwise pass it as a vector
    23.     // mat.SetVector("_PanOffset", offset);
    24. }
     
    Quatum1000 likes this.
  17. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,349
    Addressing this part, this is why I put the frac where I did, after the time and sincos are multiplied together.

    float2 scrolledUV = (IN.worldPos.xz * scale.xx) + frac(float2(sin(_Angle * UNITY_PI / 180), cos(_Angle * UNITY_PI / 180)) * _Time.y * _PanSpeed);
     
    Quatum1000 and ifurkend like this.
  18. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thanks a lot bgolus, works perfect now!