765 lines
27 KiB
HLSL
765 lines
27 KiB
HLSL
// 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;
|
|
} |