Files
2026-06-22 16:18:34 +02:00

472 lines
19 KiB
Plaintext

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"
}