// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #define SUPPORT_CONTACT_SHADOWS 0 #include "ShadingModels.ush" #include "ReflectionEnvironmentShared.ush" #include "PlanarReflectionShared.ush" #include "LightGridCommon.ush" #include "ShadowFilteringCommon.ush" #include "ClearCoatCommon.ush" #include "DeferredLightingCommon.ush" #include "SHCommon.ush" #if MOBILE_SSR_ENABLED && !(MOBILE_QL_FORCE_FULLY_ROUGH || MATERIAL_FULLY_ROUGH) #if MOBILE_DEFERRED_SHADING #if IS_MOBILE_BASE_PASS #define MOBILE_USE_SSR (MATERIAL_SSR) #else #define MOBILE_USE_SSR 1 #endif #else #define MOBILE_USE_SSR (MATERIAL_SSR || !MATERIALBLENDING_ANY_TRANSLUCENT) #endif #define SSRData MobileBasePass.SSRParams #else #define MOBILE_USE_SSR 0 #endif #if MOBILE_USE_SSR #include "MobileSSR.ush" #endif #ifndef TRANSLUCENCY_NON_DIRECTIONAL #define TRANSLUCENCY_NON_DIRECTIONAL 0 #endif #ifndef MOBILE_USE_CSM_BRANCH #define MOBILE_USE_CSM_BRANCH 0 #endif #ifndef LQ_TEXTURE_LIGHTMAP #define LQ_TEXTURE_LIGHTMAP 0 #endif #ifndef ENABLE_PLANAR_REFLECTION #define ENABLE_PLANAR_REFLECTION 0 #endif #define REFLECTION_COMPOSITE_USE_BLENDED_REFLECTION_CAPTURES 1 #define REFLECTION_COMPOSITE_SUPPORT_SKYLIGHT_BLEND 0 // Adds additional sampler #define REFLECTION_COMPOSITE_HAS_SPHERE_CAPTURES 1 #define REFLECTION_COMPOSITE_HAS_BOX_CAPTURES 1 #include "ReflectionEnvironmentComposite.ush" half GetSurfaceShadow(FGBufferData GBuffer, FShadowTerms ShadowTerms) { if (GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) { return 1.0f; } return ShadowTerms.SurfaceShadow; } half GetTransmissionShadow(FGBufferData GBuffer, FShadowTerms ShadowTerms) { if (GBuffer.ShadingModelID == GBuffer.ShadingModelID == SHADINGMODELID_EYE) { return 1.0f; } return ShadowTerms.TransmissionShadow; } half3 SafeGetOutColor(half3 OutColor) { // Sky materials can result in high luminance values, e.g. the sun disk. // The PreExposure could double the OutColor // This is so we make sure to at least stay within the boundaries of fp10 and not cause NaN on some platforms. // We also half that range to also make sure we have room for other additive elements such as bloom, clouds or particle visual effects. OutColor = min(OutColor, Max111110BitsFloat3 * 0.5f); return OutColor; } FLightAccumulator LightAccumulator_Add(FLightAccumulator A, FLightAccumulator B) { FLightAccumulator Sum = (FLightAccumulator)0; Sum.TotalLight = A.TotalLight + B.TotalLight; Sum.ScatterableLightLuma = A.ScatterableLightLuma + B.ScatterableLightLuma; Sum.ScatterableLight = A.ScatterableLight + B.ScatterableLight; Sum.EstimatedCost = A.EstimatedCost + B.EstimatedCost; Sum.TotalLightDiffuse = A.TotalLightDiffuse + B.TotalLightDiffuse; Sum.TotalLightSpecular = A.TotalLightSpecular + B.TotalLightSpecular; return Sum; } /*------------------------------------------------------------------------------ Mobile Shadow. ------------------------------------------------------------------------------*/ half ApplyPrecomputedShadowMask(half ShadowMap, half Shadow) { #if ALLOW_STATIC_LIGHTING return min(ShadowMap, Shadow); #else return ShadowMap; #endif } #if MOBILE_USE_CSM_BRANCH uint UseCSM; #endif bool IsCSMEnabled() { #if MOBILE_USE_CSM_BRANCH return UseCSM != 0u; #else return true; #endif } half MobileShadowPCF(float2 ShadowUVs, FPCFSamplerSettings Settings) { #if MOBILE_SHADOW_QUALITY == 0 half ShadowMap = ManualNoFiltering(ShadowUVs, Settings); #elif MOBILE_SHADOW_QUALITY == 1 half ShadowMap = Manual1x1PCF(ShadowUVs, Settings); #elif MOBILE_SHADOW_QUALITY == 2 half ShadowMap = Manual3x3PCF(ShadowUVs, Settings); #elif MOBILE_SHADOW_QUALITY == 3 half ShadowMap = Manual5x5PCF(ShadowUVs, Settings); #else #error Unsupported MOBILE_SHADOW_QUALITY value. #endif return ShadowMap; } // Add fading CSM plane: #define FADE_CSM 1 #ifndef ENABLE_MOBILE_CSM #define ENABLE_MOBILE_CSM 1 #endif #ifndef MAX_MOBILE_SHADOWCASCADES #define MAX_MOBILE_SHADOWCASCADES 4u #endif half MobileDirectionalLightCSM(float2 ScreenPosition, float SceneDepth, inout float ShadowPositionZ) { half ShadowMap = 1; #if ENABLE_MOBILE_CSM ShadowPositionZ = 0; FPCFSamplerSettings Settings; Settings.ShadowDepthTexture = MobileDirectionalLight.DirectionalLightShadowTexture; Settings.ShadowDepthTextureSampler = MobileDirectionalLight.DirectionalLightShadowSampler; Settings.TransitionScale = MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.w; Settings.ShadowBufferSize = MobileDirectionalLight.DirectionalLightShadowSize; Settings.bSubsurface = false; Settings.bTreatMaxDepthUnshadowed = false; Settings.DensityMulConstant = 0; Settings.ProjectionDepthBiasParameters = 0; float4 Count = float4(SceneDepth.xxxx >= MobileDirectionalLight.DirectionalLightShadowDistances); uint CascadeIndex = uint(Count.x + Count.y + Count.z + Count.w); if (CascadeIndex < MobileDirectionalLight.DirectionalLightNumCascades) { float4 ShadowPosition = float4(0, 0, 0, 0); #if MOBILE_MULTI_VIEW ShadowPosition = mul(float4(ScreenPosition.x, ScreenPosition.y, SceneDepth, 1), ResolvedView.MobileMultiviewShadowTransform); ShadowPosition = mul(ShadowPosition, MobileDirectionalLight.DirectionalLightScreenToShadow[CascadeIndex]); #else ShadowPosition = mul(float4(ScreenPosition.x, ScreenPosition.y, SceneDepth, 1), MobileDirectionalLight.DirectionalLightScreenToShadow[CascadeIndex]); #endif ShadowPositionZ = ShadowPosition.z; // Process CSM only when ShadowPosition is valid. if (ShadowPosition.z > 0) { // Clamp pixel depth in light space for shadowing opaque, because areas of the shadow depth buffer that weren't rendered to will have been cleared to 1 // We want to force the shadow comparison to result in 'unshadowed' in that case, regardless of whether the pixel being shaded is in front or behind that plane // Invert ShadowZ as the shadow space has been changed (but not yet the filtering code) float ShadowZ = 1.0f - ShadowPosition.z; float LightSpacePixelDepthForOpaque = min(ShadowZ, 0.99999f); Settings.SceneDepth = LightSpacePixelDepthForOpaque; ShadowMap = MobileShadowPCF(ShadowPosition.xy, Settings); #if FADE_CSM float Fade = saturate(SceneDepth * MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.x + MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.y); // lerp out shadow based on fade params. ShadowMap = lerp(ShadowMap, 1.0, Fade * Fade); #endif } } #endif //ENABLE_MOBILE_CSM return ShadowMap; } /*------------------------------------------------------------------------------ Mobile Translucency Light. ------------------------------------------------------------------------------*/ struct FTranslucencyLightingVector { float4 AmbientLightingVector; float3 DirectionalLightingVector; }; FTranslucencyLightingVector GetTranslucencyLightingVector(FDeferredLightData LightData, float3 TranslatedWorldPosition) { FTranslucencyLightingVector Out; float Attenuation = 1.0f; float3 L = LightData.Direction; // Already normalized float3 NormalizedLightVector = L; if (LightData.bRadialLight) { Attenuation = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, L, NormalizedLightVector); } float3 Lighting = LightData.Color / PI * Attenuation; FTwoBandSHVectorRGB SHLighting = MulSH(SHBasisFunction(NormalizedLightVector), Lighting); Out.AmbientLightingVector = float4(SHLighting.R.V.x, SHLighting.G.V.x, SHLighting.B.V.x, 1.0f); float3 LuminanceWeights = LuminanceFactors(); float3 Coefficient0 = float3(SHLighting.R.V.y, SHLighting.G.V.y, SHLighting.B.V.y); float3 Coefficient1 = float3(SHLighting.R.V.z, SHLighting.G.V.z, SHLighting.B.V.z); float3 Coefficient2 = float3(SHLighting.R.V.w, SHLighting.G.V.w, SHLighting.B.V.w); Out.DirectionalLightingVector = float3(dot(Coefficient0, LuminanceWeights), dot(Coefficient1, LuminanceWeights), dot(Coefficient2, LuminanceWeights)); return Out; } #if TRANSLUCENCY_SH_LIGHTING FLightAccumulator GetTranslucencySHLighting(FGBufferData GBuffer, FDeferredLightData LightData, float3 TranslatedWorldPosition) { float4 VolumeLighting; float3 InterpolatedLighting = 0; FTranslucencyLightingVector LightingVector = GetTranslucencyLightingVector(LightData, TranslatedWorldPosition); #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL || TRANSLUCENCY_LIGHTING_SURFACE_LIGHTINGVOLUME GetVolumeLightingDirectional(LightingVector.AmbientLightingVector, LightingVector.DirectionalLightingVector, GBuffer.WorldNormal, GBuffer.DiffuseColor, GetMaterialTranslucencyDirectionalLightingIntensity(), InterpolatedLighting, VolumeLighting); #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL GetVolumeLightingNonDirectional(LightingVector.AmbientLightingVector, GBuffer.DiffuseColor, InterpolatedLighting, VolumeLighting); #endif FLightAccumulator LightAccumulator = (FLightAccumulator)0; LightAccumulator_AddSplit(LightAccumulator, InterpolatedLighting, 0.0f, InterpolatedLighting, 1.0f, false); return LightAccumulator; } #endif /*------------------------------------------------------------------------------ Mobile Directional Light. ------------------------------------------------------------------------------*/ FDeferredLightData GetDirectionalLightData(float4 ScreenPosition, float4 SvPosition, inout half4 OutDynamicShadowFactors, inout half OutDynamicShadowing, uint EyeIndex) { #if USE_SHADOWMASKTEXTURE half4 PreviewShadowMapChannelMask = half4(1.0f, 0.0f, 0.0f, 0.0f); #if MOBILE_MULTI_VIEW OutDynamicShadowFactors = Texture2DArraySample(MobileBasePass.ScreenSpaceShadowMaskTextureArray, MobileBasePass.ScreenSpaceShadowMaskSampler, float3(SvPositionToBufferUV(SvPosition), EyeIndex)); #else OutDynamicShadowFactors = Texture2DSample(MobileBasePass.ScreenSpaceShadowMaskTexture, MobileBasePass.ScreenSpaceShadowMaskSampler, SvPositionToBufferUV(SvPosition)); #endif OutDynamicShadowFactors = DecodeLightAttenuation(OutDynamicShadowFactors); PreviewShadowMapChannelMask = UnpackShadowMapChannelMask(MobileDirectionalLight.DirectionalLightShadowMapChannelMask >> 4); OutDynamicShadowing = dot(PreviewShadowMapChannelMask, OutDynamicShadowFactors); #else float ShadowPositionZ = 0; half ShadowMap = MobileDirectionalLightCSM(ScreenPosition.xy, ScreenPosition.w, ShadowPositionZ); OutDynamicShadowing = 1.0f; #if !MOBILE_DEFERRED_SHADING && DIRECTIONAL_LIGHT_CSM // Cascaded Shadow Map if (IsCSMEnabled()) { OutDynamicShadowing = ShadowMap; } #elif MOBILE_DEFERRED_SHADING OutDynamicShadowing = ShadowMap; #endif #endif FDeferredLightData LightData = (FDeferredLightData)0; LightData.Color = MobileDirectionalLight.DirectionalLightColor.rgb; LightData.FalloffExponent = 0; LightData.Direction = MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz; LightData.bRadialLight = false; LightData.SpecularScale = MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.z; LightData.DiffuseScale = MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.w; LightData.ShadowedBits = 1; LightData.HairTransmittance = InitHairTransmittanceData(); LightData.ShadowMapChannelMask = UnpackShadowMapChannelMask(MobileDirectionalLight.DirectionalLightShadowMapChannelMask); return LightData; } void AccumulateDirectionalLighting(FGBufferData GBuffer, float3 TranslatedWorldPosition, half3 CameraVector, float4 ScreenPosition, float4 SvPosition, inout half4 DynamicShadowFactors, inout float OutDirectionalLightShadow, inout FLightAccumulator DirectLighting, uint EyeIndex) { half DynamicShadowing = 1.0f; FDeferredLightData LightData = GetDirectionalLightData(ScreenPosition, SvPosition, DynamicShadowFactors, DynamicShadowing, EyeIndex); half4 LightAttenuation = half4(1, 1, DynamicShadowing, DynamicShadowing); #if TRANSLUCENCY_SH_LIGHTING FLightAccumulator NewLighting = GetTranslucencySHLighting(GBuffer, LightData, TranslatedWorldPosition); #else FLightAccumulator NewLighting = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, 1, LightData, LightAttenuation, 0, uint2(0, 0), OutDirectionalLightShadow); #endif DirectLighting = LightAccumulator_Add(DirectLighting, NewLighting); } /*------------------------------------------------------------------------------ Mobile Reflection. ------------------------------------------------------------------------------*/ #if IS_MOBILE_BASE_PASS || IS_DECAL /** Prenormalized capture of the scene that's closest to the object being rendered. */ half3 GetMobileSkyLightReflection(half3 ReflectionVector, half Roughness, half CubemapMaxMip) { half AbsoluteSpecularMip = ComputeReflectionCaptureMipFromRoughness(Roughness, CubemapMaxMip); half4 Reflection = MobileReflectionCapture.Texture.SampleLevel(MobileReflectionCapture.TextureSampler, ReflectionVector, AbsoluteSpecularMip); #if FORWARD_SHADING_FORCES_SKYLIGHT_CUBEMAPS_BLENDING float BlendFraction = MobileReflectionCapture.Params.w; if (BlendFraction > 0.0f) { half4 ReflectionBlend = MobileReflectionCapture.TextureBlend.SampleLevel(MobileReflectionCapture.TextureBlendSampler, ReflectionVector, AbsoluteSpecularMip); Reflection = lerp(Reflection, ReflectionBlend, BlendFraction.xxxx); } #endif return Reflection.rgb * ResolvedView.SkyLightColor.rgb; } #endif // Common helper function for evaluating reflection probes for mobile half3 GetImageBasedReflectionLighting_Mobile( half3 ReflectionVector, float3 TranslatedWorldPosition, half Roughness, half IndirectIrradiance, half CompositeAlpha, uint GridIndex) { half3 SpecularIBL = (half3)0.0f; #if ENABLE_CLUSTERED_REFLECTION FCulledReflectionCapturesGridHeader CulledReflectionCapturesGridHeader = GetCulledReflectionCapturesGridHeader(GridIndex); SpecularIBL = CompositeReflectionCapturesAndSkylightTWS( CompositeAlpha, TranslatedWorldPosition, ReflectionVector,//RayDirection, Roughness, IndirectIrradiance, 1.0f, 0.0f, CulledReflectionCapturesGridHeader.NumReflectionCaptures, CulledReflectionCapturesGridHeader.DataStartIndex, 0, true); #elif MOBILE_DEFERRED_LIGHTING // Normalize for static skylight types which mix with lightmaps. bool bNormalize = ReflectionStruct.SkyLightParameters.z < 1; float SkyAverageBrightness = 1.0f; SpecularIBL = GetSkyLightReflection(ReflectionVector, Roughness, SkyAverageBrightness); if (bNormalize) { #if ALLOW_STATIC_LIGHTING SpecularIBL *= ComputeMixingWeight(IndirectIrradiance, SkyAverageBrightness, Roughness); #endif } SpecularIBL *= CompositeAlpha; #elif IS_MOBILE_BASE_PASS || IS_DECAL bool UsingSkyReflection = MobileReflectionCapture.Params.y > 0.0f; // Normalize for static skylight types which mix with lightmaps. bool bNormalize = !UsingSkyReflection || MobileReflectionCapture.Params.z < 1; if (UsingSkyReflection) { // Apply sky colour if the reflection map is the sky. SpecularIBL = GetMobileSkyLightReflection(ReflectionVector, Roughness, MobileReflectionCapture.Params.y); } else { half AbsoluteSpecularMip = ComputeReflectionCaptureMipFromRoughness(Roughness, ResolvedView.ReflectionCubemapMaxMip); SpecularIBL = MobileReflectionCapture.Texture.SampleLevel(MobileReflectionCapture.TextureSampler, ReflectionVector, AbsoluteSpecularMip).rgb; half ReflectionCaptureBrightness = MobileReflectionCapture.Params.w; SpecularIBL = SpecularIBL * ReflectionCaptureBrightness; } if (bNormalize) { #if ALLOW_STATIC_LIGHTING SpecularIBL *= ComputeMixingWeight(IndirectIrradiance, MobileReflectionCapture.Params.x, Roughness); #endif } SpecularIBL *= CompositeAlpha; #endif return SpecularIBL; } // Common helper function for evaluating planar reflection for mobile half3 GetPlanarReflectionbasedReflectionLighting_Mobile(float3 TranslatedWorldPosition, half3 WorldNormal, half Roughness, half3 InSpecularIBL) { half3 Out = InSpecularIBL; #if ENABLE_PLANAR_REFLECTION || MATERIAL_PLANAR_FORWARD_REFLECTIONS BRANCH if (abs(dot(PlanarReflectionStruct.ReflectionPlane.xyz, 1)) > .0001f) { half4 PlanarReflection = GetPlanarReflection(TranslatedWorldPosition, WorldNormal, Roughness); // Planar reflections win over reflection environment Out = lerp(InSpecularIBL, PlanarReflection.rgb, PlanarReflection.a); } #endif return Out; } // Version used for Substrate float3 GetImageBasedReflectionSpecular( half3 SpecularDirection, half Roughness, float3 TranslatedWorldPosition, half3 WorldNormal, half SpecularOcclusion, half IndirectIrradiance, uint GridIndex) { half3 Out = 0; // IBL reflection Out = GetImageBasedReflectionLighting_Mobile( SpecularDirection, TranslatedWorldPosition, Roughness, IndirectIrradiance, SpecularOcclusion, GridIndex); // Planar reflection Out = GetPlanarReflectionbasedReflectionLighting_Mobile(TranslatedWorldPosition, WorldNormal, Roughness, Out); return Out; } // Version used for legacy shading void AccumulateReflection( FGBufferData GBuffer, half4 SvPosition, half3 CameraVector, float3 TranslatedWorldPosition, half3 ReflectionVector, half IndirectIrradiance, uint GridIndex, inout FLightAccumulator DirectLighting) { half3 SpecularIBLLighting = (half3)0.0f; half3 N = GBuffer.WorldNormal; half3 V = -CameraVector; half NoV = saturate(abs(dot(N, V)) + 1e-5); half3 TopLayerR = ReflectionVector; half SpecularOcclusion = GBuffer.GBufferAO; // Point lobe in off-specular peak direction ReflectionVector = GetOffSpecularPeakReflectionDir(N, ReflectionVector, GBuffer.Roughness); half RoughnessSq = GBuffer.Roughness * GBuffer.Roughness; SpecularOcclusion = GetSpecularOcclusion(NoV, RoughnessSq, SpecularOcclusion); // IBL reflection (primary) half3 SpecularIBL = GetImageBasedReflectionLighting_Mobile(ReflectionVector , TranslatedWorldPosition , GBuffer.Roughness , IndirectIrradiance , SpecularOcclusion , GridIndex ); #if MOBILE_USE_SSR half4 SSR = 0; if (MobileSSR(GBuffer, SvPosition, TranslatedWorldPosition, ReflectionVector, SSR)) { SpecularIBL.rgb = SpecularIBL.rgb * (1 - SSR.a) + SSR.rgb; } #endif // MOBILE_USE_SSR // Planar reflection SpecularIBL = GetPlanarReflectionbasedReflectionLighting_Mobile(TranslatedWorldPosition, GBuffer.WorldNormal, GBuffer.Roughness, SpecularIBL); half3 DiffuseColor = GBuffer.DiffuseColor; half3 SpecularColor = GBuffer.SpecularColor; if (GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT) { const half ClearCoat = GBuffer.CustomData.x; const half ClearCoatRoughness = GBuffer.CustomData.y; RemapClearCoatDiffuseAndSpecularColor(GBuffer, NoV, DiffuseColor, SpecularColor); half F = GetEnvBRDF(0.04, ClearCoatRoughness, NoV).x; F *= ClearCoat; half LayerAttenuation = (1 - F); // Fc * Vis #if MOBILE_USE_PREINTEGRATED_GF half2 AB = PreIntegratedGF.SampleLevel(PreIntegratedGFSampler, float2(NoV, GBuffer.Roughness), 0).rg; #else half2 AB = EnvBRDFApproxLazarov(GBuffer.Roughness, NoV); #endif SpecularIBLLighting += SpecularIBL * LayerAttenuation * (SpecularColor * AB.x + AB.y * saturate(50 * SpecularColor.g) * (1 - ClearCoat)); // IBL reflection (secondary) SpecularIBL = GetImageBasedReflectionLighting_Mobile(TopLayerR , TranslatedWorldPosition , ClearCoatRoughness , IndirectIrradiance , F * SpecularOcclusion , GridIndex ); SpecularIBLLighting += SpecularIBL; } else if (GBuffer.ShadingModelID == SHADINGMODELID_HAIR) { //Skip IBL for Hair } else { SpecularIBLLighting += SpecularIBL * GetEnvBRDF(SpecularColor, GBuffer.Roughness, NoV); } LightAccumulator_AddSplit(DirectLighting, 0.0f, SpecularIBLLighting, 0.0f, 1.0f, false); } /*------------------------------------------------------------------------------ Mobile Local lights. ------------------------------------------------------------------------------*/ /** * Adds local lighting using the light grid, does not apply directional lights, as they are done elsewhere. */ #if ENABLE_CLUSTERED_LIGHTS void AccumulateLightGridLocalLighting(const FCulledLightsGridHeader InLightGridHeader , FGBufferData GBuffer , float3 TranslatedWorldPosition , half3 CameraVector , uint EyeIndex , uint FirstNonSimpleLightIndex , half4 DynamicShadowFactors #if !MOBILE_DEFERRED_LIGHTING , uint LightingChannelMask #endif , inout FLightAccumulator DirectLighting) { // Limit max to ForwardLightStruct.NumLocalLights. // This prevents GPU hangs when the PS tries to read from uninitialized NumCulledLightsGrid buffer const uint NumLightsInGridCell = min(InLightGridHeader.NumLights, GetMaxLightsPerCell()); LOOP for (uint GridLightListIndex = FirstNonSimpleLightIndex; GridLightListIndex < NumLightsInGridCell; GridLightListIndex++) { const FLocalLightData LocalLight = GetLocalLightDataFromGrid(InLightGridHeader.DataStartIndex + GridLightListIndex, EyeIndex); #if MOBILE_DEFERRED_LIGHTING // The lights are sorted such that all that support clustered deferred are at the beginning, there might be others // (e.g., lights with dynamic shadows) so we break out when the condition fails. if (!UnpackIsClusteredDeferredSupported(LocalLight)) { break; } #endif // extra-early out since we know light grid is sloppy and all lights in list are radial (have a range) // appears useless if (!IsLightVisible(LocalLight, TranslatedWorldPosition)) { continue; } uint LightSceneInfoExtraDataPacked = UnpackLightSceneInfoExtraDataPacked(LocalLight); #if !MOBILE_DEFERRED_LIGHTING // Check lighting channels for mobile forward if ((UnpackLightingChannelMask(LocalLight) & LightingChannelMask) == 0) { continue; } #endif // bits [17:16] really uint LightType = UnpackLightType(LightSceneInfoExtraDataPacked); half DynamicShadowing = 1.0f; #if USE_SHADOWMASKTEXTURE if (LightType == LIGHT_TYPE_SPOT || LightType == LIGHT_TYPE_POINT) { half4 PreviewShadowMapChannelMask = UnpackShadowMapChannelMask(LightSceneInfoExtraDataPacked >> 4); DynamicShadowing = dot(PreviewShadowMapChannelMask, DynamicShadowFactors); } #endif FDeferredLightData LightData = ConvertToDeferredLight_Mobile(LocalLight); if (LightType == LIGHT_TYPE_SPOT || LightType == LIGHT_TYPE_POINT) { half Attenuation = ComputeLightProfileMultiplier(TranslatedWorldPosition, LightData.TranslatedWorldPosition, -LightData.Direction, LightData.Tangent, LightData.IESAtlasIndex); LightData.Color *= Attenuation; } half4 LightAttenuation = half4(1, 1, DynamicShadowing, DynamicShadowing); float SurfaceShadow = 0; #if TRANSLUCENCY_SH_LIGHTING FLightAccumulator NewLighting = GetTranslucencySHLighting(GBuffer, LightData, TranslatedWorldPosition); #else FLightAccumulator NewLighting = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, 1, LightData, LightAttenuation, 0, uint2(0, 0), SurfaceShadow); #endif DirectLighting = LightAccumulator_Add(DirectLighting, NewLighting); } } #endif #if ENABLE_CLUSTERED_LIGHTS || MERGED_LOCAL_LIGHTS_MOBILE void MergeLocalLights(const FCulledLightsGridHeader InLightGridHeader , float3 TranslatedWorldPosition , uint EyeIndex , float bSupportLightFunctions , inout float3 MergedLightColor , inout float3 MergedLightL , inout float MergedSpecularScale , inout float MergedTotalWeight #if ENABLE_NDOTL_INTEGRATION , inout float3 N #endif ) { MergedLightColor = uint3(0.f, 0.f , 0.f); MergedLightL = float3(0,0,0); MergedTotalWeight = 0; MergedSpecularScale = 0.f; uint NumLightsInGridCell = min(InLightGridHeader.NumLights, GetMaxLightsPerCell()); LOOP for (uint GridLightListIndex = 0; GridLightListIndex < NumLightsInGridCell; GridLightListIndex++) { const FLocalLightData LocalLight = GetLocalLightDataFromGrid(InLightGridHeader.DataStartIndex + GridLightListIndex, EyeIndex); // extra-early out since we know light grid is sloppy and all lights in list are radial (have a range) // appears useless float InvLightRadiusSq = UnpackLightInvRadius(LocalLight) * UnpackLightInvRadius(LocalLight); float DistLight = length2(TranslatedWorldPosition - UnpackLightTranslatedWorldPosition(LocalLight)) * InvLightRadiusSq; if (DistLight > 1.0f) { continue; } FDeferredLightData LightData = ConvertToDeferredLight_Mobile(LocalLight); LightData.SpecularScale = UnpackLightSpecularScale(LocalLight); LightData.DiffuseScale = UnpackLightDiffuseScale(LocalLight); float3 L = LightData.Direction; // Already normalized float3 ToLight = L; float3 MaskedLightColor = LightData.Color; float LightMask = 1; if (LightData.bRadialLight) { LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L ); MaskedLightColor *= LightMask; } float WeightLight = 1 - DistLight; if (LightData.bSpotLight) { WeightLight *= SpotAttenuation(L, -LightData.Direction, LightData.SpotAngles); } #if ENABLE_NDOTL_INTEGRATION half NoL = max(0, dot(N, L)); WeightLight = WeightLight * NoL; MaskedLightColor = MaskedLightColor * NoL; #endif if( LightMask > 0) { MergedSpecularScale += LightData.SpecularScale * WeightLight; MergedTotalWeight += WeightLight; // For lights with light functions we only only use the compute the direction (OutColorB) // The Color is added in additional draws using MainLightFunction() if (!bSupportLightFunctions || !UnpackHasLightFunction(LocalLight)) { MergedLightColor += MaskedLightColor; } MergedLightL += L * WeightLight; } } } #endif /*------------------------------------------------------------------------------ Mobile Forward Decal Lighting ------------------------------------------------------------------------------*/ half3 ForwardDecalLighting( float3 TranslatedWorldPosition, float2 ScreenPosition, float SceneDepth, half3 CameraVector, half3 VertexNormal, half3 ReflectionVector, half3 WorldNormal, half Specular, half3 BaseColor, half Metallic, half Roughness, half3 EmissiveColor, half Opacity) { float ShadowPositionZ = 0; half ShadowMap = MobileDirectionalLightCSM(ScreenPosition.xy, SceneDepth, ShadowPositionZ); half3 DirectionalLightDirection = MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz; half NoL = saturate(dot(half3(WorldNormal), DirectionalLightDirection)); half3 SpecularColor = ComputeF0(Specular, BaseColor, Metallic); half3 DiffuseColor = BaseColor - BaseColor * Metallic; half3 OutColor = EmissiveColor; OutColor += MobileDirectionalLight.DirectionalLightColor.rgb * NoL * SimpleShading(DiffuseColor, SpecularColor, max(Roughness, .04f), DirectionalLightDirection, -CameraVector, WorldNormal) * ShadowMap; half3 SkyDiffuse = GetSkySHDiffuseSimple(WorldNormal) * ResolvedView.SkyLightColor.rgb; OutColor += DiffuseColor * SkyDiffuse; half3 SpecularIBL = GetImageBasedReflectionSpecular(ReflectionVector, Roughness, TranslatedWorldPosition, WorldNormal, 1.0, Luminance(SkyDiffuse), 0); half NoV = saturate(abs(dot(WorldNormal, -CameraVector)) + 1e-5); OutColor += SpecularIBL * EnvBRDFApprox(SpecularColor, Roughness, NoV); #if MATERIALBLENDING_MODULATE OutColor *= Opacity; #endif return OutColor; }