Rotation of Texture/UVs directly from a shader

As I said I found out how to rotate. My shader works perfect. But now I need to modify the contrast. I found a shader which modifies image properties but it is fragment/vertex type. My rotation shader is surface type. I was unable to mix them and then I read they are not compatibles. Does anyone have an idea how to convert one in to the other type or how to make them work at the same time? I want to rotate and I want to modify the contrast.

Or maybe is there a simple way to change the contrast from the first shader??

Shader "Custom/NewShader" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_RotationSpeed ("Rotation Speed", Float) = 2.0
		_RotationDegrees ("Rotation Degrees", Float) = 0.0
		
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert vertex:vert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};
		
		float _RotationDegrees;

        void vert (inout appdata_full v) {
            v.texcoord.xy -=0.5;
            
            float s = sin ( _RotationDegrees);
            float c = cos ( _RotationDegrees);
            
            float2x2 rotationMatrix = float2x2( c, -s, s, c);
            rotationMatrix *=0.5;
			rotationMatrix +=0.5;
			rotationMatrix = rotationMatrix * 2-1;
			v.texcoord.xy = mul ( v.texcoord.xy, rotationMatrix );
			v.texcoord.xy += 0.5; 
        }

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}
Shader "Custom/Effects" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_SaturationAmount ("Saturation Amount", Range(0.0, 1.0)) = 1.0
		_BrightnessAmount ("Brightness Amount", Range(0.0, 1.0)) = 1.0
		_ContrastAmount ("Contrast Amount", Range(0.0,1.0)) = 1.0
	}
	SubShader {
		Pass {
		
			CGPROGRAM
			#pragma vertex vert_img
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform float _SaturationAmount;
			uniform float _BrightnessAmount;
			uniform float _ContrastAmount;
			
			float3 ContrastSaturationBrightness( float3 color, float brt, float sat, float con)
			{
				//RGB Color Channels
				float AvgLumR = 0.5;
				float AvgLumG = 0.5;
				float AvgLumB = 0.5;
				
				//Luminace Coefficients for brightness of image
				float3 LuminaceCoeff = float3(0.2125,0.7154,0.0721);
				
				//Brigntess calculations
				float3 AvgLumin = float3(AvgLumR,AvgLumG,AvgLumB);
				float3 brtColor = color * brt;
				float intensityf = dot(brtColor, LuminaceCoeff);
				float3 intensity = float3(intensityf, intensityf, intensityf);
				
				//Saturation calculation
				float3 satColor = lerp(intensity, brtColor, sat);
				
				//Contrast calculations
				float3 conColor = lerp(AvgLumin, satColor, con);
				
				return conColor;
			}
			
			float4 frag (v2f_img i) : COLOR
			{
				float4 renderTex = tex2D(_MainTex, i.uv);
				renderTex.rgb = ContrastSaturationBrightness(renderTex.rgb, _BrightnessAmount, _SaturationAmount, _ContrastAmount); 
				return renderTex;
			}
			ENDCG
		}
	} 
	FallBack "Diffuse"
}
2 Likes

How would i make a shader that rotated a image rotate a transparent image

1 Like

how to get the rotation angle based on the objects rotation inside the shader itself?

ok I figured that out but can it be done with out breaking batching? seems like setting the mesh to static kills the rotation

Hey,
i also need a shader to rotate a texture on a simple plane.
While using a non squared plane i get some shearing effects.


How can i avoid this effect?
I’m using this shader:

Shader "Custom/RotateUVs" {
    Properties {
        _MainTex("Base (RGB)", 2D) = "white" {}
        _Rotation("Rotation", Float) = 0.0
    }

    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            fixed4 _MainTex_ST;
            float _Rotation;
            float _OffsetX;
            float _OffsetY;

            struct appdata {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
    
            v2f vert (appdata v) {
                float s = sin ( _Rotation );
                float c = cos ( _Rotation );
                float2x2 rotationMatrix = float2x2( c, -s, s, c);

                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

                float offsetX = .5; //_MainTex_ST.z +_MainTex_ST.x / 2;
                float offsetY = .5; //_MainTex_ST.w +_MainTex_ST.y / 2;

                float x = v.texcoord.x - offsetX; //* _MainTex_ST.x + _MainTex_ST.z - offsetX;
                float y = v.texcoord.y - offsetY; //* _MainTex_ST.y + _MainTex_ST.w - offsetY;

                o.uv = mul (float2(x, y), rotationMatrix ) + float2(offsetX, offsetY);
               
                return o;
            }
    
            fixed4 frag (v2f i) : COLOR {
                return tex2D(_MainTex, i.uv);
            }
            ENDCG
        }
    }
}

Hope someone can help me.

You should use a mesh with a isotropic UV layout. My guess is you’re scaling the built-in quad non-uniformly, making its UVs anisotropic. The shader code doesn’t have enough information to correct for your scaling unless you get really fancy.

Many thanks, Daniel.
Now i build the plane mesh per script.
The uvs get the same aspect ratio as the mesh and i can leave the scaling of the gameobject at (1,1,1).

Now that I think about it, you could do the rotation when you build the mesh too, unless you want that to change more easily than each plane’s dimensions.

Oh, still have a problem.
The texture i want to display on the plane can also have another aspect ratio…
For example:
The plane has the size of 2.5 * 2.5 units
The texture’s dimensions are 512 * 256 px
I have to set the tiling to 0.25 * 0.5 for displaying one square at the plane (see picture above).

With a squared texture and a non squared plane it works, after “resizing” the uvs
(range of u = 0 - 1; range of v = 0.25 - 0.75).

Maybe i could implement a solution with projectors, but it would be nicer, if i can use a shader.

It would be much more efficient to compute the rotation matrix in a script in order to avoid the costly sincos() at each vert, and pass it as a float4 attribute to the shader, as it is constant across all verts (constant within a frame).

Actually you’re right :wink:
But the shearing would still exist

Hi everyone,

Changed shader to fix rotate 90 degrees.

Shader “Custom/Rotate90s” {
Properties {
_MainTex (“Base (RGB)”, 2D) = “white” {}
_RotationSpeed (“Rotation Speed”, Float) = 2.0
_RotationDegrees (“Rotation Degrees”, Float) = 0.0
_isRotat (“isRotat”, int) = 0

}
SubShader {
Tags { “RenderType”=“Opaque” }
LOD 200

CGPROGRAM
#pragma surface surf Lambert vertex:vert
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};

float _RotationDegrees;
int _isRotat;
void vert (inout appdata_full v) {
v.texcoord.xy -=0.5;
if (_isRotat==1) { _RotationDegrees=4.712385; } // 90 degrees.
float s = sin ( _RotationDegrees);
float c = cos ( _RotationDegrees);

float2x2 rotationMatrix = float2x2( c, -s, s, c);
rotationMatrix *=0.5;
rotationMatrix +=0.5;
rotationMatrix = rotationMatrix * 2-1;
v.texcoord.xy = mul ( v.texcoord.xy, rotationMatrix );
v.texcoord.xy += 0.5;
}
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack “Diffuse”
}

This Shader (Custom/Rotate90s) not working in android 5.1, but Shader (Custom/RotateUV) works. In android version 4.1 - 4.4. work both. What could be wrong?

Note that you can remove the redundant scaling and substitute the sincos intrinsic.

i.texcoord0.xy -= 0.5; // shift the center of the coordinates to (0,0)
float s, c;
sincos(radians(_Rotation), s, c); // compute the sin and cosine
float2x2 rotationMatrix = float2x2(c, -s, s, c);
i.texcoord0.xy = mul(i.texcoord0.xy, rotationMatrix);
i.texcoord0.xy += 0.5; // shift the center of the coordinates back to (0.5,0.5)

2 Likes

Just want to note that Metal API does not support this type of matrix contruction.

For Metal, you should write (which I found rather annoying, haha)

Matrix = float2x2( float2(c, -s), float2(s, c));
rotationMatrix *= float2x2(float2(0.5,0.5),float2(0.5,0.5));
rotationMatrix += float2x2(float2(0.5,0.5),float2(0.5,0.5));
rotationMatrix = rotationMatrix * float2x2(float2(2,2),float2(2,2)) -float2x2(float2(1,1),float2(1,1));

If you don’t it makes a compile time error (material is flat black on the mobile device);

EDIT: this won’t affect other platforms -they will all work just fine too

How does all this work on normals?

If you are talking about per pixel normals (a normal map texture), then, well, you can rotate that for sure.

If you are talking about the per vertex mesh normals, then rotate the object in the scene.

Note (a point of confusion to some) that normal textures are only a per-pixel direction modifier that still requires the normal direction of a mesh (or sometimes hardcoded direction).

Yup, i was talking about normal map textures. Problem with rotating is that at some point the normals are flipped and the light info is mirrored!

If you rotate the texture, or more specifically rotate the UVs, you have to apply the inverse rotation to the resulting normal map’s tangent space x and y.

Normal maps store a direction relative to the tangent space of the model, but that tangent is based on the orientation of the UVs and the surface normal. If you generate or alter the UVs in a shader you’ll also have to generate or alter your tangents for that normal map or transform your normal map back to match those on the model. If you’re just rotating then the inverse rotation is the easiest way to accomplish that.

Do you mind showing me how to alter the tangents?

@stationx - If you’re asking related to triplanar, that’s a more involved question. That’s not a case of “rotating the texture” and altering the existing tangents, that’s creating all new tangents from scratch since the UVs you use have no relation to those stored in the mesh. That’s a discussion better taken someplace else (ie: your triplanar thread).

If it’s actually being rotated like the code on this page then you’ll want something like this (based off of @Xorxor 's example):

i.texcoord0.xy -= 0.5; // shift the center of the coordinates to (0,0)
float s, c;
sincos(radians(_Rotation), s, c); // compute the sin and cosine
float2x2 rotationMatrix = float2x2(c, -s, s, c);
i.texcoord0.xy = mul(i.texcoord0.xy, rotationMatrix);
i.texcoord0.xy += 0.5; // shift the center of the coordinates back to (0.5,0.5)

fixed4 normal = UnpackNormal(tex2D(_BumpMap, i.texcoord0.xy)); // get normal
normal = mul(normal, inverse(rotationMatrix)); // rotate using inverse of rotation matrix

It’s important that the rotation matrix does not have the offset in it!

I’d also contend the 90 degree rotation example is a lot of wasted math if all you want is a 90 degree rotation.

IN.uv_MainTex.yx would have the same result for zero cost.

1 Like