Shader "Custom/TwoTilesRotate" { Properties { _TileTexture1("Tile Texture 1", 2D) = "white" {} _TileTexture2("Tile Texture 2", 2D) = "white" {} _GroutTexture("Grout Texture", 2D) = "black" {} _TileNormalMap1("Tile Normal Map 1", 2D) = "bump" {} _TileNormalMap2("Tile Normal Map 2", 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 _TileTexture1; sampler2D _TileTexture2; sampler2D _GroutTexture; sampler2D _TileNormalMap1; sampler2D _TileNormalMap2; 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 = 1.0 - i.uv; // 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) + 0.5; // Apply rotation and translate back float2 tilePos = frac(rotatedUV / tileWithGroutSize); bool isTile = tilePos.x < tileWidthMeters / tileWithGroutSize.x && tilePos.y < tileHeightMeters / tileWithGroutSize.y; // Determine which texture to use based on row and column index int rowIndex = floor(rotatedUV.y / tileWithGroutSize.y); int columnIndex = floor(rotatedUV.x / tileWithGroutSize.x); bool useFirstTile = (rowIndex + columnIndex) % 2 == 0; fixed4 tileColor1 = tex2D(_TileTexture1, tilePos / (tileWidthMeters / tileWithGroutSize.x)); fixed4 tileColor2 = tex2D(_TileTexture2, tilePos / (tileWidthMeters / tileWithGroutSize.x)); fixed4 color = isTile ? (useFirstTile ? tileColor1 : tileColor2) : tex2D(_GroutTexture, tilePos / (groutWidthMeters / tileWithGroutSize.y)); if (isTile) { float3 normalTex = useFirstTile ? UnpackNormal(tex2D(_TileNormalMap1, tilePos / (tileWidthMeters / tileWithGroutSize.x))) : UnpackNormal(tex2D(_TileNormalMap2, 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" }