671 lines
25 KiB
HLSL
671 lines
25 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
DeferredLightingCommon.usf: Common definitions for deferred lighting.
|
|
=============================================================================*/
|
|
|
|
#pragma once
|
|
|
|
#include "DeferredShadingCommon.ush"
|
|
#include "DynamicLightingCommon.ush"
|
|
#include "IESLightProfilesCommon.ush"
|
|
#include "CapsuleLightIntegrate.ush"
|
|
#include "RectLightIntegrate.ush"
|
|
#include "ScreenSpaceShadowRayCast.ush"
|
|
#include "Substrate/Substrate.ush"
|
|
#include "LightData.ush"
|
|
|
|
#if USE_LIGHT_FUNCTION_ATLAS
|
|
#include "LightFunctionAtlas/LightFunctionAtlasCommon.usf"
|
|
#endif
|
|
|
|
#ifndef ADAPTIVE_VOLUMETRIC_SHADOW_MAP
|
|
#define ADAPTIVE_VOLUMETRIC_SHADOW_MAP 0
|
|
#endif
|
|
|
|
#ifndef ALLOW_LOCAL_LIGHT_DISTANCE_ATTENUATION
|
|
#define ALLOW_LOCAL_LIGHT_DISTANCE_ATTENUATION 0
|
|
#endif
|
|
|
|
#if ADAPTIVE_VOLUMETRIC_SHADOW_MAP
|
|
#include "HeterogeneousVolumes/HeterogeneousVolumesAdaptiveVolumetricShadowMapSampling.ush"
|
|
|
|
//#include "HeterogeneousVolumes/HeterogeneousVolumesVoxelGridUtils2.ush"
|
|
//#include "HeterogeneousVolumes/HeterogeneousVolumesRayMarchingUtils.ush"
|
|
#endif // ADAPTIVE_VOLUMETRIC_SHADOW_MAP
|
|
|
|
// This Substrate include assumes that if inline shading is required, it needs to
|
|
// be defined prior to DeferredLightingCommong.ush
|
|
// SubstrateStruct.MaterialTextureArray is only available to translucent materials or global shaders computing contact shadows.
|
|
// It is not defined for opaque materials, only the UAV is available to write the Substrate buffer.
|
|
#define SUBSTRATE_RAYCAST_ENABLED (!MATERIAL_IS_SUBSTRATE || SUBSTRATE_TRANSLUCENT_ENABLED)
|
|
#if SUBSTRATE_RAYCAST_ENABLED
|
|
#if MATERIAL_IS_SUBSTRATE
|
|
#define SubstrateRayCast SubstrateStruct
|
|
#else
|
|
#define SubstrateRayCast Substrate
|
|
#endif
|
|
#endif
|
|
|
|
#define REFERENCE_QUALITY 0
|
|
|
|
/** Returns 0 for positions closer than the fade near distance from the camera, and 1 for positions further than the fade far distance. */
|
|
float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData)
|
|
{
|
|
// depth (non radial) based fading over distance
|
|
float Fade = saturate(SceneDepth * LightData.DistanceFadeMAD.x + LightData.DistanceFadeMAD.y);
|
|
return Fade * Fade;
|
|
}
|
|
|
|
#if SUPPORT_CONTACT_SHADOWS
|
|
// Returns distance along ray that the first hit occurred, or negative on miss
|
|
// Sets bOutHitCastDynamicShadow if the hit point is marked as a dynamic shadow caster
|
|
float ShadowRayCast(
|
|
float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength,
|
|
int NumSteps, float Dither, bool bHairNoShadowLight, out bool bOutHitCastContactShadow)
|
|
{
|
|
// *2 to get less moire pattern in extreme cases, larger values make object appear not grounded in reflections
|
|
const float CompareToleranceScale = 2.0f;
|
|
|
|
float2 HitUV;
|
|
float HitDistance = CastScreenSpaceShadowRay(RayOriginTranslatedWorld, RayDirection, RayLength, NumSteps, Dither, CompareToleranceScale, bHairNoShadowLight, HitUV);
|
|
|
|
bOutHitCastContactShadow = false;
|
|
if (HitDistance > 0.0)
|
|
{
|
|
#if SUBTRATE_GBUFFER_FORMAT==1
|
|
#if SUBSTRATE_RAYCAST_ENABLED && SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE
|
|
uint2 PixelPos = View.ViewRectMin.xy + View.ViewSizeAndInvSize.xy * HitUV;
|
|
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), SubstrateRayCast.MaxBytesPerPixel);
|
|
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(SubstrateRayCast.MaterialTextureArray, SubstrateAddressing, SubstrateRayCast.TopLayerTexture);
|
|
// We check if the hit pixel has CastContactShadow, and we always exclude EYE BSDFs from there as done by CastContactShadow(SampleGBuffer) in the legacy path below.
|
|
// SUBSTRATE_TODO: try to move that test to GBuffer export pass (set DoesCastContactShadow = false for EYE BSDFs).
|
|
bOutHitCastContactShadow = SubstratePixelHeader.DoesCastContactShadow() && !SubstratePixelHeader.SubstrateGetBSDFType() == SUBSTRATE_BSDF_TYPE_EYE;
|
|
#endif
|
|
#else
|
|
FGBufferData SampleGBuffer = GetGBufferData(HitUV);
|
|
bOutHitCastContactShadow = CastContactShadow(SampleGBuffer);
|
|
#endif
|
|
}
|
|
|
|
return HitDistance;
|
|
}
|
|
#endif
|
|
|
|
#ifndef SUPPORT_CONTACT_SHADOWS
|
|
#error "Must set SUPPORT_CONTACT_SHADOWS"
|
|
#endif
|
|
|
|
void GetShadowTermsBase(
|
|
float SceneDepth,
|
|
half4 PrecomputedShadowFactors,
|
|
FDeferredLightData LightData,
|
|
half4 LightAttenuation,
|
|
inout FShadowTerms OutShadow)
|
|
{
|
|
BRANCH
|
|
if (LightData.ShadowedBits)
|
|
{
|
|
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
|
|
|
|
// LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w,
|
|
// Whole scene directional light shadows in x, whole scene directional light SSS shadows in y
|
|
// Get static shadowing from the appropriate GBuffer channel
|
|
#if ALLOW_STATIC_LIGHTING
|
|
half UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, half4(1, 1, 1, 1));
|
|
half StaticShadowing = lerp(1, dot(PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
|
|
#else
|
|
half StaticShadowing = 1.0f;
|
|
#endif
|
|
|
|
if (LightData.bRadialLight || SHADING_PATH_MOBILE)
|
|
{
|
|
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
|
|
|
|
OutShadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
|
|
// SSS uses a separate shadowing term that allows light to penetrate the surface
|
|
//@todo - how to do static shadowing of SSS correctly?
|
|
OutShadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;
|
|
|
|
OutShadow.TransmissionThickness = LightAttenuation.w;
|
|
}
|
|
else
|
|
{
|
|
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
|
|
// Also fix up the fade between dynamic and static shadows
|
|
// to work with plane splits rather than spheres.
|
|
|
|
float DynamicShadowFraction = DistanceFromCameraFade(SceneDepth, LightData);
|
|
// For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
|
|
OutShadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);
|
|
// Fade between SSS dynamic shadowing and static shadowing based on distance
|
|
OutShadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);
|
|
|
|
OutShadow.SurfaceShadow *= LightAttenuation.z;
|
|
OutShadow.TransmissionShadow *= LightAttenuation.z;
|
|
|
|
// Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
|
|
OutShadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
|
|
}
|
|
}
|
|
|
|
OutShadow.HairTransmittance = LightData.HairTransmittance;
|
|
OutShadow.HairTransmittance.OpaqueVisibility = OutShadow.SurfaceShadow;
|
|
}
|
|
|
|
void ApplyContactShadowWithShadowTerms(
|
|
float SceneDepth,
|
|
uint ShadingModelID,
|
|
float ContactShadowOpacity,
|
|
FDeferredLightData LightData,
|
|
float3 TranslatedWorldPosition,
|
|
half3 L,
|
|
float Dither,
|
|
inout FShadowTerms OutShadow)
|
|
{
|
|
#if SUPPORT_CONTACT_SHADOWS
|
|
|
|
float ContactShadowLength = 0.0f;
|
|
const float ContactShadowLengthScreenScale = GetScreenRayLengthMultiplierForProjectionType(SceneDepth).y;
|
|
|
|
FLATTEN
|
|
if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
|
|
{
|
|
ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
|
|
}
|
|
|
|
if (LightData.ShadowedBits < 2 && (ShadingModelID == SHADINGMODELID_HAIR))
|
|
{
|
|
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
|
|
}
|
|
// World space distance to cover eyelids and eyelashes but not beyond
|
|
if (ShadingModelID == SHADINGMODELID_EYE)
|
|
{
|
|
ContactShadowLength = 0.5;
|
|
|
|
}
|
|
|
|
#if MATERIAL_CONTACT_SHADOWS
|
|
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
|
|
#endif
|
|
|
|
BRANCH
|
|
if (ContactShadowLength > 0.0)
|
|
{
|
|
bool bHitCastContactShadow = false;
|
|
bool bHairNoShadowLight = ShadingModelID == SHADINGMODELID_HAIR && !LightData.ShadowedBits;
|
|
float HitDistance = ShadowRayCast( TranslatedWorldPosition, L, ContactShadowLength, 8, Dither, bHairNoShadowLight, bHitCastContactShadow );
|
|
|
|
if ( HitDistance > 0.0 )
|
|
{
|
|
float ContactShadowOcclusion = bHitCastContactShadow ? LightData.ContactShadowCastingIntensity : LightData.ContactShadowNonCastingIntensity;
|
|
|
|
// Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated
|
|
// thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior)
|
|
BRANCH
|
|
if (ContactShadowOcclusion > 0.0 &&
|
|
IsSubsurfaceModel(ShadingModelID) &&
|
|
ShadingModelID != SHADINGMODELID_HAIR &&
|
|
ShadingModelID != SHADINGMODELID_EYE &&
|
|
ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE)
|
|
{
|
|
// Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path
|
|
// Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least
|
|
// ensure that we don't darken-out the subsurface term with the contact shadows
|
|
float Density = SubsurfaceDensityFromOpacity(ContactShadowOpacity);
|
|
ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) );
|
|
}
|
|
|
|
float ContactShadow = 1.0 - ContactShadowOcclusion;
|
|
|
|
OutShadow.SurfaceShadow *= ContactShadow;
|
|
OutShadow.TransmissionShadow *= ContactShadow;
|
|
}
|
|
}
|
|
|
|
OutShadow.HairTransmittance = LightData.HairTransmittance;
|
|
OutShadow.HairTransmittance.OpaqueVisibility = OutShadow.SurfaceShadow;
|
|
|
|
#endif // SUPPORT_CONTACT_SHADOWS
|
|
}
|
|
|
|
void GetShadowTerms(
|
|
float SceneDepth,
|
|
half4 PrecomputedShadowFactors,
|
|
uint ShadingModelID,
|
|
float ContactShadowOpacity,
|
|
FDeferredLightData LightData,
|
|
float3 TranslatedWorldPosition,
|
|
half3 L,
|
|
half4 LightAttenuation,
|
|
float Dither,
|
|
inout FShadowTerms OutShadow)
|
|
{
|
|
// Get the basic shadow terms
|
|
GetShadowTermsBase(SceneDepth, PrecomputedShadowFactors, LightData, LightAttenuation, OutShadow);
|
|
|
|
// Now combined with screens space contact shadow if necessary.
|
|
ApplyContactShadowWithShadowTerms (SceneDepth, ShadingModelID, ContactShadowOpacity, LightData, TranslatedWorldPosition, L, Dither, OutShadow);
|
|
}
|
|
|
|
float GetLocalLightAttenuation(
|
|
float3 TranslatedWorldPosition,
|
|
FDeferredLightData LightData,
|
|
inout float3 ToLight,
|
|
inout float3 L)
|
|
{
|
|
ToLight = LightData.TranslatedWorldPosition - TranslatedWorldPosition;
|
|
|
|
float DistanceSqr = dot( ToLight, ToLight );
|
|
L = ToLight * rsqrt( DistanceSqr );
|
|
|
|
float LightMask;
|
|
if (LightData.bInverseSquared)
|
|
{
|
|
LightMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) );
|
|
// This extra attenuation has been added for supporting existing 'legacy' shading model on mobile.
|
|
// This is not needed for Substrate which unifies all lighting paths
|
|
#if SHADING_PATH_MOBILE && (!SUBSTRATE_ENABLED || ALLOW_LOCAL_LIGHT_DISTANCE_ATTENUATION)
|
|
if (!LightData.bRectLight)
|
|
{
|
|
LightMask *= 1.0f / (DistanceSqr + 1.0f);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
LightMask = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent);
|
|
}
|
|
|
|
if (LightData.bSpotLight)
|
|
{
|
|
LightMask *= SpotAttenuation(L, -LightData.Direction, LightData.SpotAngles);
|
|
}
|
|
|
|
if( LightData.bRectLight )
|
|
{
|
|
// Rect normal points away from point
|
|
LightMask = dot( LightData.Direction, L ) < 0 ? 0 : LightMask;
|
|
}
|
|
|
|
return LightMask;
|
|
}
|
|
|
|
#define RECLIGHT_BARNDOOR 1
|
|
// Wrapper for FDeferredLightData for computing visible rect light (i.e., unoccluded by barn doors)
|
|
FRect GetRect(float3 ToLight, FDeferredLightData LightData)
|
|
{
|
|
return GetRect(
|
|
ToLight,
|
|
LightData.Direction,
|
|
LightData.Tangent,
|
|
LightData.SourceRadius,
|
|
LightData.SourceLength,
|
|
LightData.RectLightData.BarnCosAngle,
|
|
LightData.RectLightData.BarnLength,
|
|
RECLIGHT_BARNDOOR);
|
|
}
|
|
|
|
FCapsuleLight GetCapsule( float3 ToLight, FDeferredLightData LightData )
|
|
{
|
|
FCapsuleLight Capsule;
|
|
Capsule.Length = LightData.SourceLength;
|
|
Capsule.Radius = LightData.SourceRadius;
|
|
Capsule.SoftRadius = LightData.SoftSourceRadius;
|
|
Capsule.DistBiasSqr = 1;
|
|
Capsule.LightPos[0] = ToLight - 0.5 * Capsule.Length * LightData.Tangent;
|
|
Capsule.LightPos[1] = ToLight + 0.5 * Capsule.Length * LightData.Tangent;
|
|
return Capsule;
|
|
}
|
|
|
|
FLightAccumulator AccumulateDynamicLighting(
|
|
float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion,
|
|
FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos,
|
|
inout float SurfaceShadow)
|
|
{
|
|
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
|
|
|
|
half3 V = -CameraVector;
|
|
half3 N = GBuffer.WorldNormal;
|
|
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
|
|
{
|
|
const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
|
|
N = OctahedronToUnitVector(oct1);
|
|
}
|
|
|
|
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 );
|
|
#if ADAPTIVE_VOLUMETRIC_SHADOW_MAP
|
|
//LightAttenuation *= ComputeTransmittance(DerivedParams.TranslatedWorldPosition, LightData.TranslatedWorldPosition, 256);
|
|
LightAttenuation *= AVSM_SampleTransmittance(TranslatedWorldPosition, LightData.TranslatedWorldPosition);
|
|
#endif // ADAPTIVE_VOLUMETRIC_SHADOW_MAP
|
|
MaskedLightColor *= LightMask;
|
|
}
|
|
|
|
LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost
|
|
|
|
BRANCH
|
|
if( LightMask > 0 )
|
|
{
|
|
FShadowTerms Shadow;
|
|
Shadow.SurfaceShadow = AmbientOcclusion;
|
|
Shadow.TransmissionShadow = 1;
|
|
Shadow.TransmissionThickness = 1;
|
|
Shadow.HairTransmittance.OpaqueVisibility = 1;
|
|
const float ContactShadowOpacity = GBuffer.CustomData.a;
|
|
GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
|
|
LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
|
|
SurfaceShadow = Shadow.SurfaceShadow;
|
|
|
|
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms
|
|
|
|
#if SHADING_PATH_MOBILE
|
|
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
|
|
|
|
FDirectLighting Lighting = (FDirectLighting)0;
|
|
|
|
half NoL = max(0, dot(GBuffer.WorldNormal, L));
|
|
#if TRANSLUCENCY_NON_DIRECTIONAL
|
|
NoL = 1.0f;
|
|
#endif
|
|
BRANCH
|
|
if (LightData.bRectLight)
|
|
{
|
|
FRect Rect = GetRect( ToLight, LightData );
|
|
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
|
|
|
|
#if REFERENCE_QUALITY
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
|
|
#else
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);
|
|
}
|
|
|
|
Lighting.Specular *= LightData.SpecularScale;
|
|
Lighting.Diffuse *= LightData.DiffuseScale;
|
|
|
|
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
|
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
|
#else // SHADING_PATH_MOBILE
|
|
BRANCH
|
|
if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
|
|
{
|
|
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
|
|
|
|
#if NON_DIRECTIONAL_DIRECT_LIGHTING
|
|
float Lighting;
|
|
|
|
if( LightData.bRectLight )
|
|
{
|
|
FRect Rect = GetRect( ToLight, LightData );
|
|
|
|
Lighting = IntegrateLight( Rect );
|
|
}
|
|
else
|
|
{
|
|
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
|
|
|
|
Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
|
|
}
|
|
|
|
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
|
|
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
|
|
#else
|
|
FDirectLighting Lighting;
|
|
|
|
if (LightData.bRectLight)
|
|
{
|
|
FRect Rect = GetRect( ToLight, LightData );
|
|
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
|
|
|
|
#if REFERENCE_QUALITY
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
|
|
#else
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
|
|
|
|
#if REFERENCE_QUALITY
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
|
|
#else
|
|
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
|
|
#endif
|
|
}
|
|
|
|
Lighting.Specular *= LightData.SpecularScale;
|
|
Lighting.Diffuse *= LightData.DiffuseScale;
|
|
|
|
#if USE_LIGHT_FUNCTION_ATLAS
|
|
MaskedLightColor *= GetLocalLightFunctionCommon(TranslatedWorldPosition, LightData.LightFunctionAtlasLightIndex);
|
|
#endif
|
|
|
|
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
|
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
|
|
|
|
LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)
|
|
#endif
|
|
}
|
|
#endif // SHADING_PATH_MOBILE
|
|
}
|
|
return LightAccumulator;
|
|
}
|
|
|
|
/** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */
|
|
FDeferredLightingSplit GetDynamicLightingSplit(
|
|
float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion,
|
|
FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos,
|
|
inout float SurfaceShadow)
|
|
{
|
|
FLightAccumulator LightAccumulator = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, LightData, LightAttenuation, Dither, SVPos, SurfaceShadow);
|
|
return LightAccumulator_GetResultSplit(LightAccumulator);
|
|
}
|
|
|
|
float4 GetDynamicLighting(
|
|
float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion,
|
|
FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos,
|
|
inout float SurfaceShadow)
|
|
{
|
|
FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
|
|
TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion,
|
|
LightData, LightAttenuation, Dither, SVPos,
|
|
SurfaceShadow);
|
|
|
|
return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
|
|
}
|
|
|
|
/**
|
|
* Calculates lighting for a given position, normal, etc with a simple lighting model designed for speed.
|
|
* All lights rendered through this method are unshadowed point lights with no shadowing or light function or IES.
|
|
* A cheap specular is used instead of the more correct area specular, no fresnel.
|
|
*/
|
|
float3 GetSimpleDynamicLighting(float3 TranslatedWorldPosition, float3 CameraVector, float3 WorldNormal, float AmbientOcclusion, float3 DiffuseColor, float3 SpecularColor, float Roughness, FSimpleDeferredLightData LightData)
|
|
{
|
|
float3 V = -CameraVector;
|
|
float3 N = WorldNormal;
|
|
float3 ToLight = LightData.TranslatedWorldPosition - TranslatedWorldPosition;
|
|
float DistanceAttenuation = 1;
|
|
|
|
float DistanceSqr = dot( ToLight, ToLight );
|
|
float3 L = ToLight * rsqrt( DistanceSqr );
|
|
float NoL = saturate( dot( N, L ) );
|
|
|
|
if (LightData.bInverseSquared)
|
|
{
|
|
// Sphere falloff (technically just 1/d2 but this avoids inf)
|
|
DistanceAttenuation = 1 / ( DistanceSqr + 1 );
|
|
|
|
float LightRadiusMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) );
|
|
DistanceAttenuation *= LightRadiusMask;
|
|
}
|
|
else
|
|
{
|
|
DistanceAttenuation = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent);
|
|
}
|
|
|
|
float3 OutLighting = 0;
|
|
|
|
BRANCH
|
|
if (DistanceAttenuation > 0)
|
|
{
|
|
const float3 LightColor = LightData.Color;
|
|
|
|
// Apply SSAO to the direct lighting since we're not going to have any other shadowing
|
|
float Attenuation = DistanceAttenuation * AmbientOcclusion;
|
|
|
|
#if NON_DIRECTIONAL_DIRECT_LIGHTING
|
|
float3 VolumeLighting = Diffuse_Lambert(DiffuseColor);
|
|
OutLighting += LightColor * Attenuation * VolumeLighting;
|
|
#else
|
|
OutLighting += LightColor * (NoL * Attenuation) * SimpleShading(DiffuseColor, SpecularColor, max(Roughness, .04f), L, V, N);
|
|
#endif
|
|
}
|
|
|
|
return OutLighting;
|
|
}
|
|
|
|
float3 GetIrradianceForLight(FDeferredLightData LightData, float3 WorldNormal, float3 TranslatedWorldPosition, bool bSupportRectLight)
|
|
{
|
|
float3 LightColor = LightData.Color;
|
|
float3 L = LightData.Direction;
|
|
float3 ToLight = L;
|
|
float3 AreaLightFalloffColor = 1;
|
|
float CombinedAttenuation = 1;
|
|
float NoL = saturate(dot(WorldNormal, L));
|
|
|
|
if (LightData.bRadialLight)
|
|
{
|
|
FAreaLightIntegrateContext Context = (FAreaLightIntegrateContext) 0;
|
|
float LightMask = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
|
|
|
|
float Attenuation = 0.0f;
|
|
float Roughness = 1;
|
|
float3 V = float3(1, 0, 0);
|
|
|
|
if (bSupportRectLight && LightData.bRectLight)
|
|
{
|
|
FRect Rect = GetRect(ToLight, LightData);
|
|
Attenuation = IntegrateLight(Rect);
|
|
|
|
if (IsRectVisible(Rect))
|
|
{
|
|
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
|
|
Context = CreateRectIntegrateContext(Roughness, WorldNormal, V, Rect, SourceTexture);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
|
|
Capsule.DistBiasSqr = 0;
|
|
Context = CreateCapsuleIntegrateContext(Roughness, WorldNormal, V, Capsule, LightData.bInverseSquared);
|
|
Attenuation = IntegrateLight(Capsule, LightData.bInverseSquared);
|
|
}
|
|
|
|
CombinedAttenuation = Attenuation * LightMask;
|
|
AreaLightFalloffColor = Context.AreaLight.FalloffColor;
|
|
NoL = Context.NoL;
|
|
}
|
|
|
|
return LightColor * AreaLightFalloffColor * (CombinedAttenuation * NoL * LightData.DiffuseScale);
|
|
}
|
|
|
|
float GetSpotLightVolumetricSoftFading(in const FDeferredLightData LightData, float LightVolumetricSoftFadeDistance, in float3 ToLight)
|
|
{
|
|
const float CosOuterCone = LightData.SpotAngles.x;
|
|
if (CosOuterCone > 0)
|
|
{
|
|
const float SinOuterCone = sqrt(1 - CosOuterCone * CosOuterCone);
|
|
const float TanOuterCone = SinOuterCone / CosOuterCone;
|
|
|
|
const float DistanceAlongSpotDir = dot(LightData.Direction, ToLight);
|
|
const float ApertureAtProjectedDistance = DistanceAlongSpotDir * TanOuterCone;
|
|
const float DistanceFromProjected = length(ToLight - LightData.Direction * DistanceAlongSpotDir);
|
|
|
|
const float Diff = ApertureAtProjectedDistance - DistanceFromProjected;
|
|
if (Diff < LightVolumetricSoftFadeDistance)
|
|
{
|
|
return saturate(Diff / LightVolumetricSoftFadeDistance);
|
|
}
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
float GetRectLightVolumetricSoftFading(in const FDeferredLightData LightData, in FRect Rect, in float LightVolumetricSoftFadeDistance, in float3 ToLight)
|
|
{
|
|
const float LightVolumetricSoftFadeDistanceInv = 1.0f / LightVolumetricSoftFadeDistance;
|
|
float FinalFadeOut = 1.0f;
|
|
|
|
// Bias outward the rect light polygon to reduce alising if the voxel intersects the area light
|
|
const float DistanceToPlane = dot(LightData.Direction, ToLight);
|
|
FinalFadeOut *= saturate(DistanceToPlane * LightVolumetricSoftFadeDistanceInv);
|
|
|
|
float3 LocalToLight = mul(Rect.Axis, ToLight);
|
|
|
|
const float BarnLength = LightData.RectLightData.BarnLength;
|
|
const float CosTheta = LightData.RectLightData.BarnCosAngle;
|
|
const float SinTheta = sqrt(1.0f - CosTheta * CosTheta);
|
|
const float BarnLengthOrtho = BarnLength * CosTheta;
|
|
|
|
const float FadeFroxelCount = 4.0f;
|
|
if (DistanceToPlane < (BarnLengthOrtho + FadeFroxelCount * LightVolumetricSoftFadeDistance))
|
|
{
|
|
// Fade out progressively at barn door tip to avoid harsh transition
|
|
float BarnDoorTipFadeOut = saturate((DistanceToPlane - BarnLengthOrtho) / (FadeFroxelCount * LightVolumetricSoftFadeDistance));
|
|
// Fade out as barn door length get shorter
|
|
BarnDoorTipFadeOut = lerp(BarnDoorTipFadeOut, 1.0f, 1.0f - saturate(BarnLength * LightVolumetricSoftFadeDistanceInv));
|
|
|
|
const float2 Extent = float2(LightData.SourceRadius, LightData.SourceLength);
|
|
float BarnDoorFadeOut = 1.0f;
|
|
{
|
|
float3 PlaneO = float3(Extent.x, 0.0f, 0.0f);
|
|
float3 PlaneN = float3(-CosTheta, 0.0f, SinTheta);
|
|
float4 PlanePosX = float4(PlaneN, -dot(PlaneO, PlaneN));
|
|
|
|
float d = dot(PlanePosX, float4(LocalToLight, 1.0f));
|
|
BarnDoorFadeOut *= saturate(d * LightVolumetricSoftFadeDistanceInv);
|
|
}
|
|
|
|
{
|
|
float3 PlaneO = float3(-Extent.x, 0.0f, 0.0f);
|
|
float3 PlaneN = float3(CosTheta, 0.0f, SinTheta);
|
|
float4 PlaneNegX = float4(PlaneN, -dot(PlaneO, PlaneN));
|
|
|
|
float d = dot(PlaneNegX, float4(LocalToLight, 1.0f));
|
|
BarnDoorFadeOut *= saturate(d * LightVolumetricSoftFadeDistanceInv);
|
|
}
|
|
|
|
{
|
|
float3 PlaneO = float3(0.0f, Extent.y, 0.0f);
|
|
float3 PlaneN = float3(0.0f, -CosTheta, SinTheta);
|
|
float4 PlanePosY = float4(PlaneN, -dot(PlaneO, PlaneN));
|
|
|
|
float d = dot(PlanePosY, float4(LocalToLight, 1.0f));
|
|
BarnDoorFadeOut *= saturate(d * LightVolumetricSoftFadeDistanceInv);
|
|
}
|
|
|
|
{
|
|
float3 PlaneO = float3(0.0f, -Extent.y, 0.0f);
|
|
float3 PlaneN = float3(0.0f, CosTheta, SinTheta);
|
|
float4 PlaneNegY = float4(PlaneN, -dot(PlaneO, PlaneN));
|
|
|
|
float d = dot(PlaneNegY, float4(LocalToLight, 1.0f));
|
|
BarnDoorFadeOut *= saturate(d * LightVolumetricSoftFadeDistanceInv);
|
|
}
|
|
|
|
FinalFadeOut *= lerp(BarnDoorFadeOut, 1.0f, BarnDoorTipFadeOut);
|
|
}
|
|
|
|
return FinalFadeOut;
|
|
}
|