#ifndef UTS_SHADOW_EVALUATION #define UTS_SHADOW_EVALUATION float3 UtsGetShadowNormal(UtsBSDFData bsdfData) { #if _MATERIAL_TYPE_FACE return normalize(mul(UNITY_MATRIX_M, float4(0.0, 0.0, 1.0, 0.0))).xyz; #else return bsdfData.normalWS * (1.0 + _ShadowNormalBias); #endif } float3 SampleSDFTexture(float3 L, float2 uv, out float angle) { // TODO: Move sdf sample result to UtsBSDFData to avoid sampleing in a loop float2 right_uv = float2(1 - uv.x, uv.y); float3 left_SDFTex = SAMPLE_TEXTURE2D(_SDFShadingMap, sampler_SDFShadingMap, uv).rgb; float3 right_SDFTex = SAMPLE_TEXTURE2D(_SDFShadingMap, sampler_SDFShadingMap, right_uv).rgb; float2 leftVector = normalize(mul(UNITY_MATRIX_M, float4(1.0, 0.0, 0.0, 0.0)).xz); float2 forwardVector = normalize(mul(UNITY_MATRIX_M, float4(0.0, 0.0, 1.0, 0.0)).xz); float2 lightDirection = normalize(L.xz); angle = saturate(dot(forwardVector, lightDirection) * -1.0 + _SDFShadowLevel); bool isRightSide = dot(lightDirection, leftVector) > 0; return isRightSide ? right_SDFTex : left_SDFTex; } float GetHairShadow(PositionInputs posInput, float3 L) { float shadow = 1.0; // Push the face fragment view space position towards the light for a little bit float hairShadowOpacity = saturate(Remap(length(posInput.positionWS), float2(_HairShadowFadeOutDistance, _HairShadowFadeInDistance), float2(0, 1))); if (hairShadowOpacity > 0.0) { float3 viewLightDir = TransformWorldToViewDir(L); float shadowLengthY = _HairShadowDistance * 5.0 * max(0.5, posInput.linearDepth * _HairShadowDistanceScaleFactor) / posInput.linearDepth; float2 shadowLength = float2(shadowLengthY * 2.0f, shadowLengthY); float3 cameraDirOS = normalize(TransformWorldToObject(GetCameraPositionWS())); float cameraDirFactor = 1.0 - smoothstep(0.1, 0.9, cameraDirOS.y); // shadowLength.y *= cameraDirFactor; // TODO: sample point is still shifting when fov change. float2 samplingPoint = (posInput.positionSS + shadowLength * viewLightDir.xy) * _ScreenSize.zw; // Use 1080p as the reference resolution to achieve consistent shadow lengths across various screen resolutions. float2 scaledUVs = samplingPoint * _RTHandleScale.xy; // We have to including the scaling factor for our shadow map since we are not going to allocate new texture if the rendering resolution changed. float hairShadow = SAMPLE_TEXTURE2D_SHADOW(_HairShadowTex, s_linear_clamp_compare_sampler, float3(scaledUVs, posInput.deviceDepth + _HairShadowDepthBias)).r; shadow = lerp(1.0 - hairShadowOpacity, 1.0, hairShadow); } return shadow; } // distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, light.forward). SHADOW_TYPE UtsEvaluateShadow_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput, LightData light, BuiltinData builtinData, float3 N, float3 L, float4 distances) { #ifndef LIGHT_EVALUATION_NO_SHADOWS float shadow = 1.0; float shadowMask = 1.0; float NdotL = dot(N, L); // Disable contact shadow and shadow mask when facing away from light (i.e transmission) #ifdef SHADOWS_SHADOWMASK // shadowMaskSelector.x is -1 if there is no shadow mask // Note that we override shadow value (in case we don't have any dynamic shadow) shadow = shadowMask = (light.shadowMaskSelector.x >= 0.0 && NdotL > 0.0) ? dot(BUILTIN_DATA_SHADOW_MASK, light.shadowMaskSelector) : 1.0; #endif #if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT) && defined(_RECEIVE_SCREEN_SPACE_SHADOW_ON) if ((light.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW) { shadow = GetScreenSpaceShadow(posInput, light.screenSpaceShadowIndex); shadow = lerp(shadowMask, shadow, light.shadowDimmer); } else #endif if ((light.shadowIndex >= 0) && (light.shadowDimmer > 0) && IsNonZeroBSDF(L, N)) { shadow = GetPunctualShadowAttenuation(lightLoopContext.shadowContext, posInput.positionSS, posInput.positionWS + L * _ShadowDistanceBias, N, light.shadowIndex, L, distances.x, light.lightType == GPULIGHTTYPE_POINT, light.lightType != GPULIGHTTYPE_PROJECTOR_BOX); #ifdef SHADOWS_SHADOWMASK // Note: Legacy Unity have two shadow mask mode. ShadowMask (ShadowMask contain static objects shadow and ShadowMap contain only dynamic objects shadow, final result is the minimun of both value) // and ShadowMask_Distance (ShadowMask contain static objects shadow and ShadowMap contain everything and is blend with ShadowMask based on distance (Global distance setup in QualitySettigns)). // HDRenderPipeline change this behavior. Only ShadowMask mode is supported but we support both blend with distance AND minimun of both value. Distance is control by light. // The following code do this. // The min handle the case of having only dynamic objects in the ShadowMap // The second case for blend with distance is handled with ShadowDimmer. ShadowDimmer is define manually and by shadowDistance by light. // With distance, ShadowDimmer become one and only the ShadowMask appear, we get the blend with distance behavior. shadow = light.nonLightMappedOnly ? min(shadowMask, shadow) : shadow; #endif shadow = lerp(shadowMask, shadow, light.shadowDimmer); } // Transparents have no contact shadow information #if !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(LIGHT_EVALUATION_NO_CONTACT_SHADOWS) { // In certain cases (like hair) we allow to force the contact shadow sample. #ifdef LIGHT_EVALUATION_CONTACT_SHADOW_DISABLE_NDOTL const bool allowContactShadow = true; #else const bool allowContactShadow = NdotL > 0.0; #endif shadow = min(shadow, allowContactShadow ? GetContactShadow(lightLoopContext, light.contactShadowMask, light.isRayTracedContactShadow) : 1.0); } #endif #ifdef DEBUG_DISPLAY if (_DebugShadowMapMode == SHADOWMAPDEBUGMODE_SINGLE_SHADOW && light.shadowIndex == _DebugSingleShadowIndex) g_DebugShadowAttenuation = shadow; #endif return shadow; #else // LIGHT_EVALUATION_NO_SHADOWS return 1.0; #endif } #endif