Shader "Custom/OneTileRotate" { Properties { _TileTexture("Tile Texture", 2D) = "white" {} _GroutTexture("Grout Texture", 2D) = "black" {} _TileNormalMap("Tile Normal Map", 2D) = "bump" {} _TileWidth("Tile Width (in)", Float) = 6.0 _TileHeight("Tile Height (in)", Float) = 6.0 _GroutWidth("Grout Width (in)", Float) = 2.0 _GroutColor("Grout Color", Color) = (0.1,0.1,1,1) _RotationAngle("Rotation Angle (degrees)", Float) = 0.0 _TileSmoothness("Tile Smoothness", Range(0, 1)) = 0.5 _TileNormalScale("Tile Normal Scale", Range(0, 10)) = 1.0 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _TileTexture; sampler2D _GroutTexture; sampler2D _TileNormalMap; float _TileWidth; float _TileHeight; float _GroutWidth; fixed4 _GroutColor; float _RotationAngle; float _TileSmoothness; float _TileNormalScale; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { float tileWidthMeters = _TileWidth * 0.0254; float tileHeightMeters = _TileHeight * 0.0254; float groutWidthMeters = _GroutWidth * 0.0254; float2 tileSize = float2(tileWidthMeters, tileHeightMeters); float2 groutSize = float2(groutWidthMeters, groutWidthMeters); float2 tileWithGroutSize = tileSize + groutSize; // Rotate UV coordinates by 45 degrees and additional rotation angle float totalRadians = (45.0 + _RotationAngle) * 3.14159 / 180.0; float2 uv = i.uv; // Translate UV to start from the bottom-left corner uv = float2(1.0 - uv.x, 1.0 - uv.y); uv -= 0.5; // Translate to the center for rotation float sinAngle = sin(totalRadians); float cosAngle = cos(totalRadians); float2x2 rotationMatrix = float2x2(cosAngle, -sinAngle, sinAngle, cosAngle); float2 rotatedUV = mul(rotationMatrix, uv) + 0.5; // Apply rotation and translate back float2 tilePos = frac(rotatedUV / tileWithGroutSize); bool isTile = tilePos.x < tileWidthMeters / tileWithGroutSize.x && tilePos.y < tileHeightMeters / tileWithGroutSize.y; fixed4 color = isTile ? tex2D(_TileTexture, tilePos / (tileWidthMeters / tileWithGroutSize.x)) : tex2D(_GroutTexture, tilePos / (groutWidthMeters / tileWithGroutSize.y)); if (isTile) { float3 normalTex = UnpackNormal(tex2D(_TileNormalMap, tilePos / (tileWidthMeters / tileWithGroutSize.x))); normalTex.xy *= _TileNormalScale; // Scale the normal map effect normalTex = normalize(normalTex); // Normalize the normal vector float3 normal = normalTex; // Simulate fixed lighting effect float3 fixedLightDir = normalize(float3(0.5, 0.5, 0.5)); float NdotL = max(0.0, dot(normal, fixedLightDir)); float3 reflectDir = reflect(-fixedLightDir, normal); float3 specular = pow(max(dot(reflectDir, fixedLightDir), 0.0), 16.0) * _TileSmoothness; float3 ambient = 0.5 * color.rgb; // Increased ambient for visibility float3 diffuse = NdotL * color.rgb; float3 finalColor = ambient + diffuse + specular; return fixed4(finalColor, color.a); } else { return _GroutColor; } } ENDCG } } FallBack "Diffuse" }