Shader "Coiriergraphics/WorldGrid" { Properties { [MainColor] _BaseColor ("ColorA", Color) = (0.105, 0.17, 0.22, 1) _LineColor ("ColorB", Color) = (1, 1, 1, 1) _DarkColor ("ColorC", Color) = (0.035, 0.055, 0.07, 1) _Metallic ("Metallic", Range(0, 1)) = 0 _Smoothness ("Smoothness", Range(0, 1)) = 0.3 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } LOD 300 HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) half4 _BaseColor; half4 _LineColor; half4 _DarkColor; half _Metallic; half _Smoothness; CBUFFER_END // Fixed pattern settings (meters, after object scale compensation). #define TRIPLANAR_SHARPNESS 16.0 #define DOT_SPACING 0.2 #define DOT_DIAMETER 0.026 #define DOT_DARKEN 0.30 #define DOT_FADE_START 1.5 #define DOT_FADE_END 5.0 #define GRID_WIDTH 0.008 #define GRID_DARKEN 0.45 #define MAJOR_GRID_WIDTH 0.015 #define MAJOR_GRID_OPACITY 0.20 #define CROSS_ARM_LENGTH 0.0425 #define CROSS_WIDTH 0.014 #define MAJOR_CROSS_ARM_LENGTH 0.14 #define MAJOR_CROSS_WIDTH 0.040 #define CROSS_OPACITY 0.11875 // Anti-alias the edge, but NEVER inflate tiny shapes by the full pixel // footprint. That was turning the crosses into huge white squares. float Stroke(float distanceToCenter, float halfWidth, float pixelWidth) { float edge = max(min(pixelWidth * 0.5, halfWidth * 0.5), 0.000001); return 1.0 - smoothstep(halfWidth - edge, halfWidth + edge, distanceToCenter); } // x = dots, y = 1m grid, z = 4m major grid, w = small crosses. // Larger 4m crosses are returned separately so they can force the full pattern color. float4 Pattern2D(float2 p, out float majorCrossMask) { float2 pixel = max(fwidth(p), float2(0.000001, 0.000001)); float footprint = max(pixel.x, pixel.y); float2 dotIndex = floor(p / DOT_SPACING + 0.5); float2 dotLocal = p - dotIndex * DOT_SPACING; float dotMask = Stroke(length(dotLocal), DOT_DIAMETER * 0.5, length(pixel)); // Entire dot rows and columns coinciding with a 1m grid line are absent. // This avoids visible circles straddling thin lines (even with AA). float2 distanceToGridIndex = abs((frac(dotIndex / 5.0 + 0.5) - 0.5) * 5.0); dotMask *= step(0.5, min(distanceToGridIndex.x, distanceToGridIndex.y)); // Remove sub-pixel dots rather than allowing them to alias or expand. dotMask *= 1.0 - smoothstep(0.004, 0.014, footprint); float2 cell = frac(p + 0.5) - 0.5; float2 gridDist = abs(cell); float gridMask = max( Stroke(gridDist.x, GRID_WIDTH * 0.5, pixel.x), Stroke(gridDist.y, GRID_WIDTH * 0.5, pixel.y)); gridMask *= 1.0 - smoothstep(0.025, 0.10, footprint); float2 majorDist = abs((frac(p / 4.0 + 0.5) - 0.5) * 4.0); float majorMask = max( Stroke(majorDist.x, MAJOR_GRID_WIDTH * 0.5, pixel.x), Stroke(majorDist.y, MAJOR_GRID_WIDTH * 0.5, pixel.y)); // True plus signs: intersect two thin, finite line segments. float horizontal = Stroke(abs(cell.y), CROSS_WIDTH * 0.5, pixel.y) * Stroke(abs(cell.x), CROSS_ARM_LENGTH, pixel.x); float vertical = Stroke(abs(cell.x), CROSS_WIDTH * 0.5, pixel.x) * Stroke(abs(cell.y), CROSS_ARM_LENGTH, pixel.y); float crossMask = max(horizontal, vertical); // Suppress the ENTIRE cross at 1m intersections located on 4m lines. // Using the nearest integer grid intersection prevents partial crosses. float2 intersection = floor(p + 0.5); float2 intersection4 = abs((frac(intersection / 4.0 + 0.5) - 0.5) * 4.0); crossMask *= step(0.5, min(intersection4.x, intersection4.y)); // Crosses are intentionally not drawn when they become sub-pixel. crossMask *= 1.0 - smoothstep(0.025, 0.09, footprint); // Larger crosses only at intersections of TWO 4m lines. float majorHorizontal = Stroke(majorDist.y, MAJOR_CROSS_WIDTH * 0.5, pixel.y) * Stroke(majorDist.x, MAJOR_CROSS_ARM_LENGTH, pixel.x); float majorVertical = Stroke(majorDist.x, MAJOR_CROSS_WIDTH * 0.5, pixel.x) * Stroke(majorDist.y, MAJOR_CROSS_ARM_LENGTH, pixel.y); majorCrossMask = max(majorHorizontal, majorVertical); majorCrossMask *= 1.0 - smoothstep(0.025, 0.09, footprint); // Dots must not appear on ANY line work: 1m grid, 4m grid, 1m crosses, or 4m crosses. float occupiedByLines = saturate(max(max(gridMask, majorMask), max(crossMask, majorCrossMask))); dotMask *= 1.0 - occupiedByLines; return saturate(float4(dotMask, gridMask, majorMask, crossMask)); } float3 ObjectAxisScales() { float3x3 m = (float3x3)GetObjectToWorldMatrix(); return float3(length(mul(m, float3(1, 0, 0))), length(mul(m, float3(0, 1, 0))), length(mul(m, float3(0, 0, 1)))); } ENDHLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } Cull Back ZWrite On HLSLPROGRAM #pragma target 3.5 #pragma vertex Vert #pragma fragment Frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile _ _SHADOWS_SOFT #pragma multi_compile_fog #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_instancing #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float2 staticLightmapUV : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD0; half3 normalWS : TEXCOORD1; float3 positionOS : TEXCOORD5; half3 normalOS : TEXCOORD6; half3 vertexLighting : TEXCOORD2; half fogFactor : TEXCOORD3; DECLARE_LIGHTMAP_OR_SH(staticLightmapUV, vertexSH, 4); UNITY_VERTEX_INPUT_INSTANCE_ID }; Varyings Vert(Attributes input) { Varyings output = (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); VertexPositionInputs pos = GetVertexPositionInputs(input.positionOS); VertexNormalInputs nrm = GetVertexNormalInputs(input.normalOS); output.positionCS = pos.positionCS; output.positionWS = pos.positionWS; output.positionOS = input.positionOS; output.normalOS = input.normalOS; output.normalWS = nrm.normalWS; output.vertexLighting = VertexLighting(pos.positionWS, nrm.normalWS); output.fogFactor = ComputeFogFactor(pos.positionCS.z); OUTPUT_LIGHTMAP_UV(input.staticLightmapUV, unity_LightmapST, output.staticLightmapUV); OUTPUT_SH(output.normalWS, output.vertexSH); return output; } half4 Frag(Varyings input) : SV_Target { UNITY_SETUP_INSTANCE_ID(input); float3 n = normalize(input.normalWS); // Object-local projection, expressed in physical meters along each local axis. // Moving/rotating an object moves/rotates the grid. Scaling it preserves spacing. float3 patternPosition = input.positionOS * ObjectAxisScales(); float3 localN = normalize((float3)input.normalOS); float3 weights = pow(abs(localN), TRIPLANAR_SHARPNESS); weights /= max(weights.x + weights.y + weights.z, 0.00001); float crossX, crossY, crossZ; float4 pattern = Pattern2D(patternPosition.yz, crossX) * weights.x + Pattern2D(patternPosition.xz, crossY) * weights.y + Pattern2D(patternPosition.xy, crossZ) * weights.z; float majorCross = saturate(crossX * weights.x + crossY * weights.y + crossZ * weights.z); // Tiny dark dots fade away with camera distance. float cameraDistance = distance(input.positionWS, _WorldSpaceCameraPos); float dotVisibility = 1.0 - smoothstep(DOT_FADE_START, DOT_FADE_END, cameraDistance); half3 albedo = _BaseColor.rgb; float darkening = saturate(pattern.x * DOT_DARKEN * dotVisibility + pattern.y * GRID_DARKEN); albedo = lerp(albedo, _DarkColor.rgb, darkening); // "Transparency" is expressed as a color blend factor toward Pattern Color. albedo = lerp(albedo, _LineColor.rgb, saturate(pattern.z * MAJOR_GRID_OPACITY)); albedo = lerp(albedo, _LineColor.rgb, saturate(pattern.w * CROSS_OPACITY)); // 4m crosses always use the full Pattern Color. albedo = lerp(albedo, _LineColor.rgb, saturate(majorCross)); SurfaceData surface = (SurfaceData)0; surface.albedo = albedo; surface.alpha = 1; surface.metallic = _Metallic; surface.smoothness = _Smoothness; surface.normalTS = half3(0, 0, 1); surface.occlusion = 1; InputData lighting = (InputData)0; lighting.positionWS = input.positionWS; lighting.normalWS = n; lighting.viewDirectionWS = GetWorldSpaceNormalizeViewDir(input.positionWS); lighting.shadowCoord = TransformWorldToShadowCoord(input.positionWS); lighting.fogCoord = input.fogFactor; lighting.vertexLighting = input.vertexLighting; lighting.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.vertexSH, n); lighting.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS); lighting.shadowMask = half4(1, 1, 1, 1); half4 color = UniversalFragmentPBR(lighting, surface); color.rgb = MixFog(color.rgb, lighting.fogCoord); return color; } ENDHLSL } // Solid meshes cast shadows; the pattern only modifies their albedo. Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull Back HLSLPROGRAM #pragma vertex ShadowVert #pragma fragment ShadowFrag #pragma multi_compile_instancing #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" float3 _LightDirection; float3 _LightPosition; struct ShadowAttributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct ShadowVaryings { float4 positionCS : SV_POSITION; }; ShadowVaryings ShadowVert(ShadowAttributes input) { UNITY_SETUP_INSTANCE_ID(input); ShadowVaryings output; float3 positionWS = TransformObjectToWorld(input.positionOS); float3 normalWS = TransformObjectToWorldNormal(input.normalOS); #if defined(_CASTING_PUNCTUAL_LIGHT_SHADOW) float3 lightDirection = normalize(_LightPosition - positionWS); #else float3 lightDirection = _LightDirection; #endif float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirection)); #if UNITY_REVERSED_Z positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE); #else positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE); #endif output.positionCS = positionCS; return output; } half4 ShadowFrag(ShadowVaryings input) : SV_Target { return 0; } ENDHLSL } Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } ZWrite On ColorMask 0 Cull Back HLSLPROGRAM #pragma vertex DepthVert #pragma fragment DepthFrag #pragma multi_compile_instancing struct DepthAttributes { float3 positionOS : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct DepthVaryings { float4 positionCS : SV_POSITION; }; DepthVaryings DepthVert(DepthAttributes input) { UNITY_SETUP_INSTANCE_ID(input); DepthVaryings output; output.positionCS = TransformObjectToHClip(input.positionOS); return output; } half4 DepthFrag(DepthVaryings input) : SV_Target { return 0; } ENDHLSL } } FallBack Off }