--- title: Untitled date: 2025-02-11 11:30:34 excerpt: tags: rating: ⭐ --- # FSortedLightSetSceneInfo 有序的光源集合相关定义: ```c++ /** Data for a simple dynamic light. */ class FSimpleLightEntry { public: FVector3f Color; float Radius; float Exponent; float InverseExposureBlend = 0.0f; float VolumetricScatteringIntensity; bool bAffectTranslucency; }; struct FSortedLightSceneInfo { union { struct { // Note: the order of these members controls the light sort order! // Currently bHandledByLumen is the MSB and LightType is LSB /** The type of light. */ uint32 LightType : LightType_NumBits; /** Whether the light has a texture profile. */ uint32 bTextureProfile : 1; /** Whether the light uses a light function. */ uint32 bLightFunction : 1; /** Whether the light uses lighting channels. */ uint32 bUsesLightingChannels : 1; /** Whether the light casts shadows. */ uint32 bShadowed : 1; /** Whether the light is NOT a simple light - they always support tiled/clustered but may want to be selected separately. */ uint32 bIsNotSimpleLight : 1; /* We want to sort the lights that write into the packed shadow mask (when enabled) to the front of the list so we don't waste slots in the packed shadow mask. */ uint32 bDoesNotWriteIntoPackedShadowMask : 1; /** * True if the light doesn't support clustered deferred, logic is inverted so that lights that DO support clustered deferred will sort first in list * Super-set of lights supporting tiled, so the tiled lights will end up in the first part of this range. */ uint32 bClusteredDeferredNotSupported : 1; /** Whether the light should be handled by Lumen's Final Gather, these will be sorted to the end so they can be skipped */ uint32 bHandledByLumen : 1; } Fields; /** Sort key bits packed into an integer. */ int32 Packed; } SortKey; const FLightSceneInfo* LightSceneInfo; int32 SimpleLightIndex; /** Initialization constructor. */ explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo) : LightSceneInfo(InLightSceneInfo), SimpleLightIndex(-1) { SortKey.Packed = 0; SortKey.Fields.bIsNotSimpleLight = 1; } explicit FSortedLightSceneInfo(int32 InSimpleLightIndex) : LightSceneInfo(nullptr), SimpleLightIndex(InSimpleLightIndex) { SortKey.Packed = 0; SortKey.Fields.bIsNotSimpleLight = 0; }}; /** * Stores info about sorted lights and ranges. * The sort-key in FSortedLightSceneInfo gives rise to the following order: * [SimpleLights,Clustered,UnbatchedLights,LumenLights] * Note that some shadowed lights can be included in the clustered pass when virtual shadow maps and one pass projection are used. */struct FSortedLightSetSceneInfo { int32 SimpleLightsEnd; int32 ClusteredSupportedEnd; /** First light with shadow map or */ int32 UnbatchedLightStart; int32 LumenLightStart; FSimpleLightArray SimpleLights; TArray SortedLights; }; ``` ## 开始获取有序光源集合 UE的光源分配由`FDeferredShadingSceneRenderer::Render`内的`bComputeLightGrid`变量决定的,bComputeLightGrid的赋值逻辑如下: ```c++ void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) { ... bool bComputeLightGrid = false; if (RendererOutput == ERendererOutput::FinalSceneColor) { if (bUseVirtualTexturing) { // Note, should happen after the GPU-Scene update to ensure rendering to runtime virtual textures is using the correctly updated scene FVirtualTextureSystem::Get().EndUpdate(GraphBuilder, MoveTemp(VirtualTextureUpdater), FeatureLevel); } #if RHI_RAYTRACING GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene, InitViewTaskDatas.RayTracingRelevantPrimitives); #endif // RHI_RAYTRACING bool bAnyLumenEnabled = false; { if (bUseGBuffer) { bComputeLightGrid = bRenderDeferredLighting; } else { bComputeLightGrid = ViewFamily.EngineShowFlags.Lighting; } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; bAnyLumenEnabled = bAnyLumenEnabled || GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen; } bComputeLightGrid |= ( ShouldRenderVolumetricFog() || VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) || ViewFamily.ViewMode != VMI_Lit || bAnyLumenEnabled || VirtualShadowMapArray.IsEnabled() || ShouldVisualizeLightGrid()); } } ... } ``` 获取有序的光源集合 ```c++ void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) { ... // 有序的光源集合. FSortedLightSetSceneInfo& SortedLightSet = *GraphBuilder.AllocObject(); { RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, SortLights); RDG_GPU_STAT_SCOPE(GraphBuilder, SortLights); ComputeLightGridOutput = GatherLightsAndComputeLightGrid(GraphBuilder, bComputeLightGrid, SortedLightSet); } ... } ``` PS. 简单光源都可以被分块或分簇渲染,但对于非简单光源,只有满足以下条件的光源才可被分块或分簇渲染: - 没有使用光源的附加特性(TextureProfile、LightFunction、LightingChannel)。 - 没有开启阴影。 - 非平行光或矩形光。 另外,是否支持分块渲染,还需要光源场景代理的`IsTiledDeferredLightingSupported`返回true,长度为0的点光源才支持分块渲染。 ## GatherLightsAndComputeLightGrid ```c++ FComputeLightGridOutput FDeferredShadingSceneRenderer::GatherLightsAndComputeLightGrid(FRDGBuilder& GraphBuilder, bool bNeedLightGrid, FSortedLightSetSceneInfo& SortedLightSet) { SCOPED_NAMED_EVENT(GatherLightsAndComputeLightGrid, FColor::Emerald); FComputeLightGridOutput Result = {}; bool bShadowedLightsInClustered = ShouldUseClusteredDeferredShading() && CVarVirtualShadowOnePassProjection.GetValueOnRenderThread() && VirtualShadowMapArray.IsEnabled(); const bool bUseLumenDirectLighting = ShouldRenderLumenDirectLighting(Scene, Views[0]); GatherAndSortLights(SortedLightSet, bShadowedLightsInClustered, bUseLumenDirectLighting); if (!bNeedLightGrid) { SetDummyForwardLightUniformBufferOnViews(GraphBuilder, ShaderPlatform, Views); return Result; } bool bAnyViewUsesForwardLighting = false; bool bAnyViewUsesLumen = false; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FViewInfo& View = Views[ViewIndex]; bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial || VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) || ShouldVisualizeLightGrid(); bAnyViewUsesLumen |= GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen; } const bool bCullLightsToGrid = GLightCullingQuality && (IsForwardShadingEnabled(ShaderPlatform) || bAnyViewUsesForwardLighting || IsRayTracingEnabled() || ShouldUseClusteredDeferredShading() || bAnyViewUsesLumen || ViewFamily.EngineShowFlags.VisualizeMeshDistanceFields || VirtualShadowMapArray.IsEnabled()); // Store this flag if lights are injected in the grids, check with 'AreLightsInLightGrid()' bAreLightsInLightGrid = bCullLightsToGrid; Result = ComputeLightGrid(GraphBuilder, bCullLightsToGrid, SortedLightSet); return Result; } ``` - GatherAndSortLights:收集与排序当前场景中所有的可见光源(当前View)。 - ComputeLightGrid:是在锥体空间(frustum space)裁剪局部光源和反射探针到3D格子中,构建每个视图相关的光源列表和格子。 # RenderLights() -> RenderLight() ## InternalRenderLight() ## DeferredLightVertexShaders ```c++ // 输入参数. struct FInputParams { float2 PixelPos; float4 ScreenPosition; float2 ScreenUV; float3 ScreenVector; }; // 派生参数. struct FDerivedParams { float3 CameraVector; float3 WorldPosition; }; // 获取派生参数. FDerivedParams GetDerivedParams(in FInputParams Input, in float SceneDepth) { FDerivedParams Out; #if LIGHT_SOURCE_SHAPE > 0 // With a perspective projection, the clip space position is NDC * Clip.w // With an orthographic projection, clip space is the same as NDC float2 ClipPosition = Input.ScreenPosition.xy / Input.ScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f); Out.WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz; Out.CameraVector = normalize(Out.WorldPosition - View.WorldCameraOrigin); #else Out.WorldPosition = Input.ScreenVector * SceneDepth + View.WorldCameraOrigin; Out.CameraVector = normalize(Input.ScreenVector); #endif return Out; } Texture2D LightingChannelsTexture; uint GetLightingChannelMask(float2 UV) { uint2 IntegerUV = UV * View.BufferSizeAndInvSize.xy; return LightingChannelsTexture.Load(uint3(IntegerUV, 0)).x; } float GetExposure() { return View.PreExposure; } ``` 向往文章中的SetupLightDataForStandardDeferred()变为InitDeferredLightFromUniforms()。位于LightDataUniform.ush。 ```c++ FDeferredLightData InitDeferredLightFromUniforms(uint InLightType) { const bool bIsRadial = InLightType != LIGHT_TYPE_DIRECTIONAL; FDeferredLightData Out; Out.TranslatedWorldPosition = GetDeferredLightTranslatedWorldPosition(); Out.InvRadius = DeferredLightUniforms.InvRadius; Out.Color = DeferredLightUniforms.Color; Out.FalloffExponent = DeferredLightUniforms.FalloffExponent; Out.Direction = DeferredLightUniforms.Direction; Out.Tangent = DeferredLightUniforms.Tangent; Out.SpotAngles = DeferredLightUniforms.SpotAngles; Out.SourceRadius = DeferredLightUniforms.SourceRadius; Out.SourceLength = bIsRadial ? DeferredLightUniforms.SourceLength : 0; Out.SoftSourceRadius = DeferredLightUniforms.SoftSourceRadius; Out.SpecularScale = DeferredLightUniforms.SpecularScale; Out.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength); Out.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f; Out.ContactShadowCastingIntensity = DeferredLightUniforms.ContactShadowCastingIntensity; Out.ContactShadowNonCastingIntensity = DeferredLightUniforms.ContactShadowNonCastingIntensity; Out.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD; Out.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask; Out.ShadowedBits = DeferredLightUniforms.ShadowedBits; Out.bInverseSquared = bIsRadial && DeferredLightUniforms.FalloffExponent == 0; // Directional lights don't use 'inverse squared attenuation' Out.bRadialLight = bIsRadial; Out.bSpotLight = InLightType == LIGHT_TYPE_SPOT; Out.bRectLight = InLightType == LIGHT_TYPE_RECT; Out.RectLightData.BarnCosAngle = DeferredLightUniforms.RectLightBarnCosAngle; Out.RectLightData.BarnLength = DeferredLightUniforms.RectLightBarnLength; Out.RectLightData.AtlasData.AtlasMaxLevel = DeferredLightUniforms.RectLightAtlasMaxLevel; Out.RectLightData.AtlasData.AtlasUVOffset = DeferredLightUniforms.RectLightAtlasUVOffset; Out.RectLightData.AtlasData.AtlasUVScale = DeferredLightUniforms.RectLightAtlasUVScale; Out.HairTransmittance = InitHairTransmittanceData(); return Out; } ``` ### DeferredLightPixelMain ```c++ void DeferredLightPixelMain( #if LIGHT_SOURCE_SHAPE > 0 float4 InScreenPosition : TEXCOORD0, #else float2 ScreenUV : TEXCOORD0, float3 ScreenVector : TEXCOORD1, #endif float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0 #if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED , out float3 OutOpaqueRoughRefractionSceneColor : SV_Target1 , out float3 OutSubSurfaceSceneColor : SV_Target2 #endif ) { const float2 PixelPos = SVPos.xy; OutColor = 0; #if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED OutOpaqueRoughRefractionSceneColor = 0; OutSubSurfaceSceneColor = 0; #endif // Convert input data (directional/local light) // 计算屏幕UV FInputParams InputParams = (FInputParams)0; InputParams.PixelPos = SVPos.xy; #if LIGHT_SOURCE_SHAPE > 0 InputParams.ScreenPosition = InScreenPosition; InputParams.ScreenUV = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz; InputParams.ScreenVector = 0; #else InputParams.ScreenPosition = 0; InputParams.ScreenUV = ScreenUV; InputParams.ScreenVector = ScreenVector; #endif #if STRATA_ENABLED FStrataAddressing StrataAddressing = GetStrataPixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Strata.MaxBytesPerPixel); FStrataPixelHeader StrataPixelHeader = UnpackStrataHeaderIn(Strata.MaterialTextureArray, StrataAddressing, Strata.TopLayerTexture); BRANCH if (StrataPixelHeader.BSDFCount > 0 // This test is also enough to exclude sky pixels #if USE_LIGHTING_CHANNELS //灯光通道逻辑 && (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask) #endif ) { //通过SceneDepth获取的CameraVector以及当前像素的世界坐标 const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV); const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth); //设置获取光源各种信息 FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE); UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度(非IES灯数值为1) float3 V =-DerivedParams.CameraVector; float3 L = LightData.Direction; // Already normalized float3 ToLight = L; float LightMask = 1; if (LightData.bRadialLight) { LightMask = GetLocalLightAttenuation(DerivedParams.TranslatedWorldPosition, LightData, ToLight, L); } if (LightMask > 0) { FShadowTerms ShadowTerms = { StrataGetAO(StrataPixelHeader), 1.0, 1.0, InitHairTransmittanceData() }; float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth); float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8); const uint FakeShadingModelID = 0; const float FakeContactShadowOpacity = 1.0f; float4 PrecomputedShadowFactors = StrataReadPrecomputedShadowFactors(StrataPixelHeader, PixelPos, SceneTexturesStruct.GBufferETexture); GetShadowTerms(SceneDepth, PrecomputedShadowFactors, FakeShadingModelID, FakeContactShadowOpacity, LightData, DerivedParams.TranslatedWorldPosition, L, LightAttenuation, Dither, ShadowTerms); FStrataDeferredLighting StrataLighting = StrataDeferredLighting( LightData, V, L, ToLight, LightMask, ShadowTerms, Strata.MaterialTextureArray, StrataAddressing, StrataPixelHeader); OutColor += StrataLighting.SceneColor; #if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED OutOpaqueRoughRefractionSceneColor += StrataLighting.OpaqueRoughRefractionSceneColor; OutSubSurfaceSceneColor += StrataLighting.SubSurfaceSceneColor; #endif } } #else // STRATA_ENABLED //取得屏幕空间数据(FGbufferData、AO) FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV); // Only light pixels marked as using deferred shading BRANCH if (ScreenSpaceData.GBuffer.ShadingModelID > 0 #if USE_LIGHTING_CHANNELS && (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask) #endif ) { //通过SceneDepth获取的CameraVector以及当前像素的世界坐标 const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV); const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth); //设置获取光源各种信息 FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE); UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度(非IES灯数值为1) #if USE_HAIR_COMPLEX_TRANSMITTANCE //针对ShadingModel Hair(同时需要CustomData.a > 0)计算头发散射结果 if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer)) { LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction); } #endif //计算当前像素的抖动值 float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8); float SurfaceShadow = 1.0f; float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);//根绝是否开启VSM 分别从VirtualShadowMap 或者 LightAttenuationTexture(上一阶段渲染的ShadowProjction) 获取灯光衰减值。 float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow); OutColor += Radiance; } #endif // STRATA_ENABLED // RGB:SceneColor Specular and Diffuse // A:Non Specular SceneColor Luminance // So we need PreExposure for both color and alpha OutColor.rgba *= GetExposure(); #if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED // Idem OutOpaqueRoughRefractionSceneColor *= GetExposure(); OutSubSurfaceSceneColor *= GetExposure(); #endif } #endif ``` ### GetDynamicLighting() => GetDynamicLightingSplit() ```c++ FDeferredLightingSplit GetDynamicLightingSplit( float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, inout float SurfaceShadow) { FLightAccumulator LightAccumulator = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, LightData, LightAttenuation, Dither, SVPos, SurfaceShadow); return LightAccumulator_GetResultSplit(LightAccumulator); } ``` LightAccumulator_GetResultSplit():针对Subsurface,`RetDiffuse.a = In.ScatterableLightLuma;` 或者 `RetDiffuse.a = Luminance(In.ScatterableLight);` ```c++ FDeferredLightingSplit LightAccumulator_GetResultSplit(FLightAccumulator In) { float4 RetDiffuse; float4 RetSpecular; if (VISUALIZE_LIGHT_CULLING == 1) { // a soft gradient from dark red to bright white, can be changed to be different RetDiffuse = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost; RetSpecular = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost; } else { RetDiffuse = float4(In.TotalLightDiffuse, 0); RetSpecular = float4(In.TotalLightSpecular, 0); //针对Subsurface会额外对RetDiffuse的Alpha设置数值 ScatterableLight的亮度数值 if (SUBSURFACE_CHANNEL_MODE == 1 ) { if (View.bCheckerboardSubsurfaceProfileRendering == 0) { // RGB accumulated RGB HDR color, A: specular luminance for screenspace subsurface scattering RetDiffuse.a = In.ScatterableLightLuma; } } else if (SUBSURFACE_CHANNEL_MODE == 2) { // RGB accumulated RGB HDR color, A: view independent (diffuse) luminance for screenspace subsurface scattering // 3 add, 1 mul, 2 mad, can be optimized to use 2 less temporary during accumulation and remove the 3 add RetDiffuse.a = Luminance(In.ScatterableLight); // todo, need second MRT for SUBSURFACE_CHANNEL_MODE==2 } } FDeferredLightingSplit Ret; Ret.DiffuseLighting = RetDiffuse; Ret.SpecularLighting = RetSpecular; return Ret; } ``` #### AccumulateDynamicLighting ```c++ FLightAccumulator AccumulateDynamicLighting( float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID, FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos, inout float SurfaceShadow) { FLightAccumulator LightAccumulator = (FLightAccumulator)0; half3 V = -CameraVector; half3 N = GBuffer.WorldNormal; //针对开启CLEAR_COAT_BOTTOM_NORMAL的清漆ShadingModel进行Normal处理 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; // 获取辐射光源的衰减值,衰减方法根据LightData.bInverseSquared,会分别使用新版衰减方法InverseSquared 或者 旧方法。如果是SpotLight与RectLight就乘以SpotLight、RectLight对应的形状衰减数值。 if (LightData.bRadialLight) { LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L ); MaskedLightColor *= LightMask; } LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost BRANCH if( LightMask > 0 )//如果不是完全死黑就计算阴影部分逻辑 { FShadowTerms Shadow; Shadow.SurfaceShadow = AmbientOcclusion;//GBuffer中的AO Shadow.TransmissionShadow = 1; Shadow.TransmissionThickness = 1; Shadow.HairTransmittance.OpaqueVisibility = 1; const float ContactShadowOpacity = GBuffer.CustomData.a;//TODO:修正ToonStandard对应的逻辑 // 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 Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow); Lighting.Specular *= LightData.SpecularScale; 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; 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; } ``` 光源新衰减公式,相关计算位于`GetLocalLightAttenuation()`: $$Falloff = \frac{saturate(1-(distance/lightRadius)^4)^2}{distance^2 + 1}$$ 光源旧衰减公式,相关函数位于DynamicLightingCommon.ush中的`RadialAttenuation()` $$Falloff = (1 - saturate(length(WorldLightVector)))^ {FalloffExponent}$$ ##### GetShadowTerms() ```c++ void GetShadowTerms(float SceneDepth, half4 PrecomputedShadowFactors, uint ShadingModelID, float ContactShadowOpacity, FDeferredLightData LightData, float3 TranslatedWorldPosition, half3 L, half4 LightAttenuation, float Dither, inout FShadowTerms Shadow) { float ContactShadowLength = 0.0f; const float ContactShadowLengthScreenScale = GetTanHalfFieldOfView().y * SceneDepth; 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) Shadow.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? Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing; Shadow.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 Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction); // Fade between SSS dynamic shadowing and static shadowing based on distance Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w); Shadow.SurfaceShadow *= LightAttenuation.z; Shadow.TransmissionShadow *= LightAttenuation.z; // Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light) Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w); } FLATTEN if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0) { ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale); } } #if SUPPORT_CONTACT_SHADOWS #if STRATA_ENABLED == 0 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; } #endif #if MATERIAL_CONTACT_SHADOWS ContactShadowLength = 0.2 * ContactShadowLengthScreenScale; #endif BRANCH if (ContactShadowLength > 0.0) { float StepOffset = Dither - 0.5; bool bHitCastContactShadow = false; bool bHairNoShadowLight = ShadingModelID == SHADINGMODELID_HAIR && !LightData.ShadowedBits; float HitDistance = ShadowRayCast( TranslatedWorldPosition, L, ContactShadowLength, 8, StepOffset, bHairNoShadowLight, bHitCastContactShadow ); if ( HitDistance > 0.0 ) { float ContactShadowOcclusion = bHitCastContactShadow ? LightData.ContactShadowCastingIntensity : LightData.ContactShadowNonCastingIntensity; #if STRATA_ENABLED == 0 // 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 ) ); } #endif float ContactShadow = 1.0 - ContactShadowOcclusion; Shadow.SurfaceShadow *= ContactShadow; Shadow.TransmissionShadow *= ContactShadow; } } #endif Shadow.HairTransmittance = LightData.HairTransmittance; Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow; } ```