Shader "Custom/CheckerboardWithTileColorGrout" { Properties { _MainColor("Main Color", Color) = (1, 1, 1, 1) _GroutColor("Grout Color", Color) = (1, 1, 1, 1) _BlockWidth("Block Width (inches)", Float) = 5.0 _BlockHeight("Block Height (inches)", Float) = 5.0 _GroutWidth("Grout Width (inches)", Float) = 0.25 _TileTexture("Tile Texture", 2D) = "white" {} _TileNormalMap("Tile Normal Map", 2D) = "bump" {} _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 100 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 _TileNormalMap; fixed4 _MainColor; fixed4 _GroutColor; float _BlockWidth; float _BlockHeight; float _GroutWidth; 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 blockWidth = _BlockWidth * 0.0254; float blockHeight = _BlockHeight * 0.0254; float groutWidth = (_GroutWidth * 0.0254) * 4; float halfGroutWidth = groutWidth / 4.0; // Rotation matrix float rad = radians(_RotationAngle); float2x2 rotationMatrix = float2x2(cos(rad), -sin(rad), sin(rad), cos(rad)); // Apply rotation to UV coordinates float2 rotatedUV = mul(rotationMatrix, i.uv - 0.5) + 0.5; float2 uv = rotatedUV * float2(1.0 / (blockWidth + halfGroutWidth), 1.0 / (blockHeight + halfGroutWidth)); // Обратное масштабирование UV для блоков uv = float2(1.0 - uv.x, 1.0 - uv.y); float2 grid = floor(uv); float2 frac_uv = frac(uv); // Проверка затирки для блоков bool isBlockGrout = frac_uv.x < halfGroutWidth || frac_uv.x > 1.0 - halfGroutWidth || frac_uv.y < halfGroutWidth || frac_uv.y > 1.0 - halfGroutWidth; // Уменьшение координат для меньших плиток внутри блока float2 subUV; bool isSmallGrout; bool isHorizontal = fmod(grid.x + grid.y, 2.0) == 0.0; if (isHorizontal) { subUV = float2(frac(uv.x), frac(uv.y * 3.0)); // Горизонтальное деление isSmallGrout = subUV.y < groutWidth || subUV.y > 1.0 - groutWidth; } else { subUV = float2(frac(uv.x * 3.0), frac(uv.y)); // Вертикальное деление isSmallGrout = subUV.x < groutWidth || subUV.x > 1.0 - groutWidth; } bool isGrout = isBlockGrout || isSmallGrout; // Применение текстуры к каждой маленькой плитке float2 tileUV = isHorizontal ? float2(1.0 - subUV.y, subUV.x) : subUV; fixed4 tileColor = tex2D(_TileTexture, tileUV) * _MainColor; if (!isGrout) { float3 normalTex = UnpackNormal(tex2D(_TileNormalMap, tileUV)); normalTex.xy *= _TileNormalScale; // Масштабирование эффекта карты нормалей normalTex = normalize(normalTex); // Нормализация вектора нормалей float3 normal = normalTex; // Фиксированное освещение 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 * tileColor.rgb; // Увеличенная интенсивность амбиентного света float3 diffuse = NdotL * tileColor.rgb; float3 finalColor = ambient + diffuse + specular; return fixed4(finalColor, tileColor.a); } else { // Возвращаем только цвет затирки return _GroutColor; } } ENDCG } } FallBack "Diffuse" }