Shader "GAME/PickableHighlight" { Properties { [Header(Surface)][Space] [MainColor] _BaseColor("Base Color", Color) = (1,1,1,1) [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [Toggle(_NORMALMAP)] _UseNormalMap("Use Normal Map", Float) = 0 [Normal] _BumpMap("Normal Map", 2D) = "bump" {} _NormalStrength("Normal Strength", Range(0,2)) = 1 [Toggle(_MASKMAP)] _UseMaskMap("Use Mask Map (R:Metallic G:AO A:Smooth)", Float) = 0 _MaskMap("Mask Map", 2D) = "white" {} _Metallic("Metallic", Range(0,1)) = 0 _Smoothness("Smoothness", Range(0,1)) = 0.2 _Occlusion("Occlusion", Range(0,1)) = 1 _ShadowLift("Shadow Lift (lighten shadows so item never goes black)", Range(0,1)) = 0.25 [Header(Fresnel Rim silhouette pop)][Space] [HDR] _RimColor("Rim Color", Color) = (1,0.85,0.35,1) _RimPower("Rim Power (higher = thinner edge)", Range(0.5,12)) = 3 _RimIntensity("Rim Intensity", Range(0,8)) = 2.5 [Header(Shine Sweep moving highlight band)][Space] [HDR] _ShineColor("Shine Color", Color) = (1,1,0.9,1) _ShineDir("Shine Direction (object space xyz)", Vector) = (0,1,0,0) _ShineTiling("Shine Tiling (bands across object)", Float) = 1.2 _ShineSpeed("Shine Speed", Float) = 0.6 _ShineSharpness("Shine Sharpness (higher = thinner band)", Range(1,64)) = 12 _ShineIntensity("Shine Intensity", Range(0,8)) = 2 [Header(Emissive Pulse gentle breathing)][Space] _PulseSpeed("Pulse Speed", Float) = 2 _PulseMin("Pulse Min", Range(0,2)) = 0.6 _PulseMax("Pulse Max", Range(0,2)) = 1.1 [Header(Proximity Fade highlight reacts to player distance)][Space] [Toggle(_DISTANCE_FADE)] _DistanceFade("Enable Distance Fade", Float) = 1 _FadeNear("Fade Near (full strength within, metres)", Float) = 3 _FadeFar("Fade Far (gone beyond, metres)", Float) = 8 [Header(Rendering)][Space] [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 2 } SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" "UniversalMaterialType" = "Lit" } // Shared declarations for every pass. HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap); TEXTURE2D(_MaskMap); SAMPLER(sampler_MaskMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _NormalStrength; half _Metallic; half _Smoothness; half _Occlusion; half _ShadowLift; half4 _RimColor; half _RimPower; half _RimIntensity; half4 _ShineColor; float4 _ShineDir; float _ShineTiling; float _ShineSpeed; half _ShineSharpness; half _ShineIntensity; float _PulseSpeed; half _PulseMin; half _PulseMax; float _FadeNear; float _FadeFar; CBUFFER_END // ========================================================================= // Proximity strength: 1 when the camera is within _FadeNear, smoothly // ramping down to 0 past _FadeFar. Camera-distance based, so it costs // nothing on the CPU. Measured against the pixel world position so it // stays correct under static batching (which flattens unity_ObjectToWorld). // ========================================================================= half ProximityStrength(float3 positionWS) { #if defined(_DISTANCE_FADE) float camDist = distance(_WorldSpaceCameraPos, positionWS); return 1.0 - smoothstep(_FadeNear, _FadeFar, camDist); #else return 1.0; #endif } // ========================================================================= // Highlight emission: rim + animated sweep, scaled by the breathing pulse // and by player proximity. This is what makes a pickable read in grass. // ========================================================================= half3 ComputeHighlightEmission(float3 positionOS, half3 normalWS, half3 viewDirWS, float3 positionWS) { // Fresnel rim: bright on grazing angles so the silhouette detaches from grass. half fresnel = pow(1.0 - saturate(dot(normalWS, viewDirWS)), _RimPower); half3 rim = fresnel * _RimIntensity * _RimColor.rgb; // Shine sweep: a band travelling along an object-space axis over time. float coord = dot(positionOS, normalize(_ShineDir.xyz)); float phase = coord * _ShineTiling - _Time.y * _ShineSpeed; half wave = sin(phase * 6.2831853) * 0.5 + 0.5; half band = pow(wave, _ShineSharpness); half3 shine = band * _ShineIntensity * _ShineColor.rgb; // Breathing pulse so the item draws the eye without ever moving. half pulse = lerp(_PulseMin, _PulseMax, sin(_Time.y * _PulseSpeed) * 0.5 + 0.5); return (rim + shine) * pulse * ProximityStrength(positionWS); } ENDHLSL // ===================================================================== // ForwardLit — full PBR (metallic/smoothness/normal/AO) + highlight. // ===================================================================== Pass { Name "ForwardLit" Tags { "LightMode" = "UniversalForward" } Cull [_Cull] ZWrite On ZTest LEqual HLSLPROGRAM #pragma target 3.0 #pragma vertex PickableVertex #pragma fragment PickableFragment #pragma shader_feature_local _NORMALMAP #pragma shader_feature_local_fragment _MASKMAP #pragma shader_feature_local_fragment _DISTANCE_FADE #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 _ EVALUATE_SH_MIXED EVALUATE_SH_VERTEX #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION #pragma multi_compile _ _CLUSTER_LIGHT_LOOP #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile _ LIGHTMAP_SHADOW_MIXING #pragma multi_compile _ SHADOWS_SHADOWMASK #pragma multi_compile_fog #pragma multi_compile_instancing #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; float2 lightmapUV : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; float3 positionOS : TEXCOORD2; half3 normalWS : TEXCOORD3; half4 tangentWS : TEXCOORD4; DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 5); float fogFactor : TEXCOORD6; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; Varyings PickableVertex(Attributes IN) { Varyings OUT = (Varyings)0; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, OUT); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS); OUT.positionCS = posInput.positionCS; OUT.positionWS = posInput.positionWS; OUT.positionOS = IN.positionOS.xyz; OUT.normalWS = normalInput.normalWS; real sign = IN.tangentOS.w * GetOddNegativeScale(); OUT.tangentWS = half4(normalInput.tangentWS, sign); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); OUT.fogFactor = ComputeFogFactor(posInput.positionCS.z); OUTPUT_LIGHTMAP_UV(IN.lightmapUV, unity_LightmapST, OUT.lightmapUV); OUTPUT_SH(OUT.normalWS, OUT.vertexSH); return OUT; } half4 PickableFragment(Varyings IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN); half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv).rgb * _BaseColor.rgb; // Mask map: R metallic, G occlusion, A smoothness. half metallic = _Metallic; half smoothness = _Smoothness; half occlusion = _Occlusion; #ifdef _MASKMAP half4 mask = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, IN.uv); metallic *= mask.r; occlusion *= mask.g; smoothness *= mask.a; #endif // Normal mapping (TBN) when a normal map is supplied. half3 normalWS = normalize(IN.normalWS); half3 normalTS = half3(0, 0, 1); #ifdef _NORMALMAP normalTS = UnpackNormalScale( SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, IN.uv), _NormalStrength); float sgn = IN.tangentWS.w; float3 bitangent = sgn * cross(normalWS, IN.tangentWS.xyz); half3x3 tbn = half3x3(IN.tangentWS.xyz, bitangent, normalWS); normalWS = normalize(TransformTangentToWorld(normalTS, tbn)); #endif InputData inputData = (InputData)0; inputData.positionWS = IN.positionWS; inputData.normalWS = normalWS; inputData.viewDirectionWS = normalize(GetWorldSpaceViewDir(IN.positionWS)); inputData.shadowCoord = TransformWorldToShadowCoord(IN.positionWS); inputData.fogCoord = IN.fogFactor; inputData.bakedGI = SAMPLE_GI(IN.lightmapUV, IN.vertexSH, normalWS); inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(IN.positionCS); inputData.shadowMask = SAMPLE_SHADOWMASK(IN.lightmapUV); SurfaceData surfaceData = (SurfaceData)0; surfaceData.albedo = albedo; surfaceData.metallic = metallic; surfaceData.smoothness = smoothness; surfaceData.occlusion = occlusion; surfaceData.normalTS = normalTS; surfaceData.alpha = 1.0; // Highlight rides in the emission channel so PBR lighting stays correct. surfaceData.emission = ComputeHighlightEmission( IN.positionOS, normalWS, inputData.viewDirectionWS, IN.positionWS); half4 color = UniversalFragmentPBR(inputData, surfaceData); // Shadow lift: keep the item readable in deep shade instead of going black. color.rgb = max(color.rgb, albedo * _ShadowLift); color.rgb = MixFog(color.rgb, inputData.fogCoord); color.a = 1.0; return color; } ENDHLSL } // ===================================================================== // ShadowCaster — lets pickables cast shadows into the scene. // ===================================================================== Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 Cull [_Cull] HLSLPROGRAM #pragma target 3.0 #pragma vertex ShadowVertex #pragma fragment ShadowFragment #pragma multi_compile_instancing #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" float3 _LightDirection; float3 _LightPosition; struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; float4 GetShadowPositionHClip(Attributes IN) { float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz); float3 normalWS = TransformObjectToWorldNormal(IN.normalOS); #if _CASTING_PUNCTUAL_LIGHT_SHADOW float3 lightDirectionWS = normalize(_LightPosition - positionWS); #else float3 lightDirectionWS = _LightDirection; #endif float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS)); positionCS = ApplyShadowClamping(positionCS); return positionCS; } Varyings ShadowVertex(Attributes IN) { Varyings OUT; UNITY_SETUP_INSTANCE_ID(IN); OUT.positionCS = GetShadowPositionHClip(IN); return OUT; } half4 ShadowFragment(Varyings IN) : SV_Target { return 0; } ENDHLSL } // ===================================================================== // DepthOnly — feeds the depth prepass. // ===================================================================== Pass { Name "DepthOnly" Tags { "LightMode" = "DepthOnly" } ZWrite On ColorMask R Cull [_Cull] HLSLPROGRAM #pragma target 3.0 #pragma vertex DepthVertex #pragma fragment DepthFragment #pragma multi_compile_instancing struct Attributes { float4 positionOS : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; Varyings DepthVertex(Attributes IN) { Varyings OUT; UNITY_SETUP_INSTANCE_ID(IN); OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); return OUT; } half4 DepthFragment(Varyings IN) : SV_Target { return 0; } ENDHLSL } // ===================================================================== // DepthNormals — supplies surface normals for SSAO / depth-normal effects. // ===================================================================== Pass { Name "DepthNormals" Tags { "LightMode" = "DepthNormals" } ZWrite On Cull [_Cull] HLSLPROGRAM #pragma target 3.0 #pragma vertex DepthNormalsVertex #pragma fragment DepthNormalsFragment #pragma shader_feature_local _NORMALMAP #pragma multi_compile_instancing #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half3 normalWS : TEXCOORD1; half4 tangentWS : TEXCOORD2; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; Varyings DepthNormalsVertex(Attributes IN) { Varyings OUT = (Varyings)0; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, OUT); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS); OUT.normalWS = normalInput.normalWS; real sign = IN.tangentOS.w * GetOddNegativeScale(); OUT.tangentWS = half4(normalInput.tangentWS, sign); return OUT; } half4 DepthNormalsFragment(Varyings IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN); half3 normalWS = normalize(IN.normalWS); #ifdef _NORMALMAP half3 normalTS = UnpackNormalScale( SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, IN.uv), _NormalStrength); float sgn = IN.tangentWS.w; float3 bitangent = sgn * cross(normalWS, IN.tangentWS.xyz); half3x3 tbn = half3x3(IN.tangentWS.xyz, bitangent, normalWS); normalWS = normalize(TransformTangentToWorld(normalTS, tbn)); #endif return half4(NormalizeNormalPerPixel(normalWS), 0.0); } ENDHLSL } } FallBack "Universal Render Pipeline/Lit" }