// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= Functionality for capturing and pre-filtering a sky env map in real time. =============================================================================*/ #include "ReflectionEnvironmentCapture.h" #include "ClearQuad.h" #include "MeshPassProcessor.h" #include "PrimitiveSceneProxy.h" #include "MeshPassProcessor.inl" #include "ScenePrivate.h" #include "SkyPassRendering.h" #include "RenderGraphUtils.h" #include "VolumetricCloudRendering.h" #include "VolumetricCloudProxy.h" #include "FogRendering.h" #include "GPUScene.h" #include "ScreenPass.h" #if WITH_EDITOR #include "CanvasTypes.h" #include "RenderTargetTemp.h" #endif extern float GReflectionCaptureNearPlane; DECLARE_GPU_STAT(CaptureConvolveSkyEnvMap); static TAutoConsoleVariable CVarRealTimeReflectionCaptureTimeSlicing( TEXT("r.SkyLight.RealTimeReflectionCapture.TimeSlice"), 1, TEXT("When enabled, the real-time sky light capture and convolutions will by distributed over several frames to lower the per-frame cost. Value in [1,6]."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarRealTimeReflectionCaptureTimeSlicingSkyCloudCubeFacePerFrame( TEXT("r.SkyLight.RealTimeReflectionCapture.TimeSlice.SkyCloudCubeFacePerFrame"), 6, TEXT("When enabled, the real-time sky light capture and convolutions will by distributed over several frames to lower the per-frame cost."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarRealTimeReflectionCaptureShadowFromOpaque( TEXT("r.SkyLight.RealTimeReflectionCapture.ShadowFromOpaque"), 0, TEXT("Opaque meshes cast shadow from directional lights onto sky and clouds when enabled.\n"), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarRealTimeReflectionCaptureDepthBuffer( TEXT("r.SkyLight.RealTimeReflectionCapture.DepthBuffer"), 1, TEXT("When enabled, the real-time sky light capture will have a depth buffer, this is for multiple meshes to be cover each other correctly. The height fog will also be applied according to the depth buffer."), ECVF_RenderThreadSafe); class FDownsampleCubeFaceCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FDownsampleCubeFaceCS); SHADER_USE_PARAMETER_STRUCT(FDownsampleCubeFaceCS, FGlobalShader); static const uint32 ThreadGroupSize = 8; using FPermutationDomain = TShaderPermutationDomain<>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, MipIndex) SHADER_PARAMETER(uint32, NumMips) SHADER_PARAMETER(int32, CubeFace) SHADER_PARAMETER(int32, FaceThreadGroupSize) SHADER_PARAMETER(FIntPoint, ValidDispatchCoord) SHADER_PARAMETER_RDG_TEXTURE_SRV(TextureCube, SourceCubemapTexture) SHADER_PARAMETER_SAMPLER(SamplerState, SourceCubemapSampler) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTextureMipColor) END_SHADER_PARAMETER_STRUCT() public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return GetMaxSupportedFeatureLevel(Parameters.Platform) >= ERHIFeatureLevel::SM5; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), ThreadGroupSize); OutEnvironment.SetDefine(TEXT("USE_COMPUTE"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FDownsampleCubeFaceCS, "/Engine/Private/ReflectionEnvironmentShaders.usf", "DownsampleCS", SF_Compute); class FConvolveSpecularFaceCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FConvolveSpecularFaceCS); SHADER_USE_PARAMETER_STRUCT(FConvolveSpecularFaceCS, FGlobalShader); static const uint32 ThreadGroupSize = 8; using FPermutationDomain = TShaderPermutationDomain<>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, MipIndex) SHADER_PARAMETER(uint32, NumMips) SHADER_PARAMETER(int32, CubeFaceOffset) SHADER_PARAMETER(int32, CubeFace) SHADER_PARAMETER(int32, FaceThreadGroupSize) SHADER_PARAMETER(FIntPoint, ValidDispatchCoord) SHADER_PARAMETER_RDG_TEXTURE_SRV(TextureCube, SourceCubemapTexture) SHADER_PARAMETER_SAMPLER(SamplerState, SourceCubemapSampler) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTextureMipColor) END_SHADER_PARAMETER_STRUCT() public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return GetMaxSupportedFeatureLevel(Parameters.Platform) >= ERHIFeatureLevel::SM5; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), ThreadGroupSize); OutEnvironment.SetDefine(TEXT("USE_COMPUTE"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FConvolveSpecularFaceCS, "/Engine/Private/ReflectionEnvironmentShaders.usf", "FilterCS", SF_Compute); class FComputeSkyEnvMapDiffuseIrradianceCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FComputeSkyEnvMapDiffuseIrradianceCS); SHADER_USE_PARAMETER_STRUCT(FComputeSkyEnvMapDiffuseIrradianceCS, FGlobalShader); // 8*8=64 threads in a group. // Each thread uses 4*7*RGB sh float => 84 bytes shared group memory. // 64 * 84 = 5376 bytes which fits dx11 16KB shared memory limitation. 6144 with vector alignement in shared memory and it still fits // Low occupancy on a single CU. static const uint32 ThreadGroupSizeX = 8; static const uint32 ThreadGroupSizeY = 8; using FPermutationDomain = TShaderPermutationDomain<>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE_SRV(TextureCube, SourceCubemapTexture) SHADER_PARAMETER_SAMPLER(SamplerState, SourceCubemapSampler) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutIrradianceEnvMapSH) SHADER_PARAMETER(float, UniformSampleSolidAngle) SHADER_PARAMETER(uint32, MipIndex) END_SHADER_PARAMETER_STRUCT() public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return GetMaxSupportedFeatureLevel(Parameters.Platform) >= ERHIFeatureLevel::SM5; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE_X"), ThreadGroupSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE_Y"), ThreadGroupSizeY); OutEnvironment.SetDefine(TEXT("SHADER_DIFFUSE_TO_SH"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FComputeSkyEnvMapDiffuseIrradianceCS, "/Engine/Private/ReflectionEnvironmentShaders.usf", "ComputeSkyEnvMapDiffuseIrradianceCS", SF_Compute); class FApplyLowerHemisphereColor : public FGlobalShader { DECLARE_GLOBAL_SHADER(FApplyLowerHemisphereColor); SHADER_USE_PARAMETER_STRUCT(FApplyLowerHemisphereColor, FGlobalShader); static const uint32 ThreadGroupSize = 8; using FPermutationDomain = TShaderPermutationDomain<>; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FLinearColor, LowerHemisphereSolidColor) SHADER_PARAMETER(FIntPoint, ValidDispatchCoord) SHADER_PARAMETER(int32, FaceThreadGroupSize) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutTextureMipColor) END_SHADER_PARAMETER_STRUCT() public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return GetMaxSupportedFeatureLevel(Parameters.Platform) >= ERHIFeatureLevel::SM5; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), ThreadGroupSize); OutEnvironment.SetDefine(TEXT("USE_COMPUTE"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FApplyLowerHemisphereColor, "/Engine/Private/ReflectionEnvironmentShaders.usf", "ApplyLowerHemisphereColorCS", SF_Compute); class FRenderRealTimeReflectionHeightFogVS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FRenderRealTimeReflectionHeightFogVS); SHADER_USE_PARAMETER_STRUCT(FRenderRealTimeReflectionHeightFogVS, FGlobalShader);; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain<>; static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("REALTIME_REFLECTION_HEIGHT_FOG"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FRenderRealTimeReflectionHeightFogVS, "/Engine/Private/ReflectionEnvironmentShaders.usf", "RenderRealTimeReflectionHeightFogVS", SF_Vertex); class FRenderRealTimeReflectionHeightFogPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FRenderRealTimeReflectionHeightFogPS); SHADER_USE_PARAMETER_STRUCT(FRenderRealTimeReflectionHeightFogPS, FGlobalShader); class FDepthTexture : SHADER_PERMUTATION_BOOL("PERMUTATION_DEPTHTEXTURE"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FFogUniformParameters, FogStruct) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthTexture) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("REALTIME_REFLECTION_HEIGHT_FOG"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FRenderRealTimeReflectionHeightFogPS, "/Engine/Private/ReflectionEnvironmentShaders.usf", "RenderRealTimeReflectionHeightFogPS", SF_Pixel); void FScene::ValidateSkyLightRealTimeCapture( FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef SceneColorTexture) { #if WITH_EDITOR if (!GAreScreenMessagesEnabled) { return; } bool bSkyMeshInMainPassExist = false; bool bSkyMeshInRealTimeSkyCaptureExtist = false; const int32 SkyRealTimeReflectionOnlyMeshBatcheCount = View.SkyMeshBatches.Num(); for (int32 MeshBatchIndex = 0; MeshBatchIndex < SkyRealTimeReflectionOnlyMeshBatcheCount; ++MeshBatchIndex) { const FSkyMeshBatch& SkyMeshBatch = View.SkyMeshBatches[MeshBatchIndex]; bSkyMeshInMainPassExist |= SkyMeshBatch.bVisibleInMainPass; bSkyMeshInRealTimeSkyCaptureExtist |= SkyMeshBatch.bVisibleInRealTimeSkyCapture; } if (!bSkyMeshInMainPassExist || !bSkyMeshInRealTimeSkyCaptureExtist) { AddDrawCanvasPass(GraphBuilder, {}, View, FScreenPassRenderTarget(SceneColorTexture, View.ViewRect, ERenderTargetLoadAction::ELoad), [this, &View, bSkyMeshInMainPassExist, bSkyMeshInRealTimeSkyCaptureExtist](FCanvas& Canvas) { FLinearColor TextColor(1.0f, 0.5f, 0.0f); if (View.bSceneHasSkyMaterial && !bSkyMeshInMainPassExist) { Canvas.DrawShadowedString(100.0f, 100.0f, TEXT("At least one mesh with a sky material is in the scene but none are rendered in main view."), GetStatsFont(), TextColor); } if (View.bSceneHasSkyMaterial && !bSkyMeshInRealTimeSkyCaptureExtist && SkyLight && SkyLight->bRealTimeCaptureEnabled) { Canvas.DrawShadowedString(100.0f, 110.0f, TEXT("At least one mesh with a sky material is in the scene but none are rendered in the real-time sky light reflection."), GetStatsFont(), TextColor); } }); } #endif } BEGIN_SHADER_PARAMETER_STRUCT(FCaptureSkyMeshReflectionPassParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FOpaqueBasePassUniformParameters, BasePass) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() void FScene::AllocateAndCaptureFrameSkyEnvMap( FRDGBuilder& GraphBuilder, FSceneRenderer& SceneRenderer, FViewInfo& MainView, bool bShouldRenderSkyAtmosphere, bool bShouldRenderVolumetricCloud, FInstanceCullingManager& InstanceCullingManager) { check(SkyLight && SkyLight->bRealTimeCaptureEnabled && !SkyLight->bHasStaticLighting); // Ignore viewfamilies without the Atmosphere showflag enabled as the sky capture may fail otherwise // as well as all views being "scene captures" which cannot be used to update the sky light data. if (MainView.bIsSceneCapture || !MainView.Family->EngineShowFlags.Atmosphere) { return; } const bool bIsNewFrame = GFrameNumberRenderThread != RealTimeSlicedReflectionCaptureFrameNumber; RealTimeSlicedReflectionCaptureFrameNumber = GFrameNumberRenderThread; RDG_EVENT_SCOPE(GraphBuilder, "CaptureConvolveSkyEnvMap"); RDG_GPU_STAT_SCOPE(GraphBuilder, CaptureConvolveSkyEnvMap); const uint32 CubeWidth = SkyLight->CaptureCubeMapResolution; const uint32 CubeMipCount = FMath::CeilLogTwo(CubeWidth) + 1; // Make a snapshot we are going to use for the 6 cubemap faces and set it up. // Note: cube view is not meant to be sent to lambdas because we only create a single one. You should only send the ViewUniformBuffer around. FViewInfo& CubeView = *MainView.CreateSnapshot(); CubeView.FOV = 90.0f; // Note: We cannot override exposure because sky input texture are using exposure // Other view data clean up CubeView.StereoPass = EStereoscopicPass::eSSP_FULL; CubeView.DrawDynamicFlags = EDrawDynamicFlags::ForceLowestLOD; CubeView.MaterialTextureMipBias = 0; FViewMatrices::FMinimalInitializer SceneCubeViewInitOptions; SceneCubeViewInitOptions.ConstrainedViewRect = FIntRect(FIntPoint(0, 0), FIntPoint(CubeWidth, CubeWidth)); FBox VolumeBounds[TVC_MAX]; CubeView.CachedViewUniformShaderParameters = MakeUnique(); CubeView.SetupUniformBufferParameters( VolumeBounds, TVC_MAX, *CubeView.CachedViewUniformShaderParameters); const FMatrix CubeProjectionMatrix = GetCubeProjectionMatrix(CubeView.FOV * 0.5f, (float)CubeWidth, GReflectionCaptureNearPlane); CubeView.UpdateProjectionMatrix(CubeProjectionMatrix); FPooledRenderTargetDesc SkyCubeTexDesc = FPooledRenderTargetDesc::CreateCubemapDesc(CubeWidth, PF_FloatR11G11B10, FClearValueBinding::Black, TexCreate_TargetArraySlicesIndependently, TexCreate_ShaderResource | TexCreate_UAV | TexCreate_RenderTargetable, false, 1, CubeMipCount, false); FRDGExternalAccessQueue ExternalAccessQueue; const bool bTimeSlicedRealTimeCapture = CVarRealTimeReflectionCaptureTimeSlicing.GetValueOnRenderThread() > 0; const bool CubeResolutionInvalidated = ConvolvedSkyRenderTargetReadyIndex < 0 || (ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex].IsValid() && ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex]->GetDesc().GetSize().X != CubeWidth); if (!ConvolvedSkyRenderTarget[0].IsValid() || CubeResolutionInvalidated) { // Always allocated GRenderTargetPool.FindFreeElement(GraphBuilder.RHICmdList, SkyCubeTexDesc, ConvolvedSkyRenderTarget[0], TEXT("SkyLight.ConvolvedSkyRenderTarget0")); GRenderTargetPool.FindFreeElement(GraphBuilder.RHICmdList, SkyCubeTexDesc, CapturedSkyRenderTarget, TEXT("SkyLight.CapturedSkyRenderTarget")); } if (bTimeSlicedRealTimeCapture && (!ConvolvedSkyRenderTarget[1].IsValid() || CubeResolutionInvalidated)) { // Additional allocation for time slicing GRenderTargetPool.FindFreeElement(GraphBuilder.RHICmdList, SkyCubeTexDesc, ConvolvedSkyRenderTarget[1], TEXT("SkyLight.ConvolvedSkyRenderTarget1")); } auto ClearCubeFace = [&](FRDGTextureRef SkyCubeTexture, int32 CubeFace) { FRenderTargetParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->RenderTargets[0] = FRenderTargetBinding(SkyCubeTexture, ERenderTargetLoadAction::ENoAction, 0, CubeFace); FLinearColor ClearColor = FLinearColor::Black; GraphBuilder.AddPass( RDG_EVENT_NAME("ClearSkyRenderTarget"), Parameters, ERDGPassFlags::Raster, [Parameters, ClearColor](FRHICommandList& RHICmdList) { DrawClearQuad(RHICmdList, ClearColor); }); }; auto RenderCubeFaces_SkyCloud = [&](bool bExecuteSky, bool bExecuteCloud, TRefCountPtr& SkyRenderTarget, int32 StartCubeFace, int32 EndCubeFace) { FScene* Scene = MainView.Family->Scene->GetRenderScene(); FRDGTextureRef SkyCubeTexture = GraphBuilder.RegisterExternalTexture(SkyRenderTarget, TEXT("SkyRenderTarget")); if (bExecuteSky || bExecuteCloud) { FRDGTextureRef BlackDummy2dTex = GraphBuilder.RegisterExternalTexture(GSystemTextures.BlackDummy); FRDGTextureRef BlackDummy3dTex = GraphBuilder.RegisterExternalTexture(GSystemTextures.VolumetricBlackDummy); const bool CaptureShadowFromOpaque = CVarRealTimeReflectionCaptureShadowFromOpaque.GetValueOnRenderThread() > 0; FSkyAtmosphereRenderContext SkyRC; const FAtmosphereSetup* AtmosphereSetup = nullptr; if (bShouldRenderSkyAtmosphere) { FSkyAtmosphereRenderSceneInfo& SkyInfo = *GetSkyAtmosphereSceneInfo(); const FSkyAtmosphereSceneProxy& SkyAtmosphereSceneProxy = SkyInfo.GetSkyAtmosphereSceneProxy(); // Global data constant between faces AtmosphereSetup = &SkyAtmosphereSceneProxy.GetAtmosphereSetup(); SkyRC.bFastSky = false; SkyRC.bFastAerialPerspective = false; SkyRC.bFastAerialPerspectiveDepthTest = false; SkyRC.bSecondAtmosphereLightEnabled = IsSecondAtmosphereLightEnabled(); // Enable opaque shadow on sky if needed SkyRC.bShouldSampleOpaqueShadow = false; if (CaptureShadowFromOpaque) { SkyAtmosphereLightShadowData LightShadowData; SkyRC.bShouldSampleOpaqueShadow = ShouldSkySampleAtmosphereLightsOpaqueShadow(*Scene, SceneRenderer.ActiveViewFamily->VisibleLightInfos, LightShadowData); GetSkyAtmosphereLightsUniformBuffers(GraphBuilder, SkyRC.LightShadowShaderParams0UniformBuffer, SkyRC.LightShadowShaderParams1UniformBuffer, LightShadowData, CubeView, SkyRC.bShouldSampleOpaqueShadow, UniformBuffer_SingleDraw); } SkyRC.bUseDepthBoundTestIfPossible = false; SkyRC.bForceRayMarching = true; // We do not have any valid view LUT SkyRC.bDepthReadDisabled = true; SkyRC.bDisableBlending = true; SkyRC.TransmittanceLut = GraphBuilder.RegisterExternalTexture(SkyInfo.GetTransmittanceLutTexture()); SkyRC.MultiScatteredLuminanceLut = GraphBuilder.RegisterExternalTexture(SkyInfo.GetMultiScatteredLuminanceLutTexture()); } FCloudRenderContext CloudRC; if (bShouldRenderVolumetricCloud) { FVolumetricCloudRenderSceneInfo& CloudInfo = *GetVolumetricCloudSceneInfo(); FVolumetricCloudSceneProxy& CloudSceneProxy = CloudInfo.GetVolumetricCloudSceneProxy(); if (CloudSceneProxy.GetCloudVolumeMaterial()) { FMaterialRenderProxy* CloudVolumeMaterialProxy = CloudSceneProxy.GetCloudVolumeMaterial()->GetRenderProxy(); CloudRC.CloudInfo = &CloudInfo; CloudRC.CloudVolumeMaterialProxy = CloudVolumeMaterialProxy; CloudRC.SceneDepthZ = GSystemTextures.GetMaxFP16Depth(GraphBuilder); CloudRC.MainView = &CubeView; /// This is only accessing data that is not changing between view orientation. Such data are accessed from the ViewUniformBuffer. See CubeView comment above. CloudRC.bShouldViewRenderVolumetricRenderTarget = false; CloudRC.bIsReflectionRendering = true; CloudRC.bIsSkyRealTimeReflectionRendering = true; CloudRC.bSecondAtmosphereLightEnabled = IsSecondAtmosphereLightEnabled(); CloudRC.bSkipAtmosphericLightShadowmap = !CaptureShadowFromOpaque; if (CaptureShadowFromOpaque) { FLightSceneInfo* AtmosphericLight0Info = Scene->AtmosphereLights[0]; FLightSceneProxy* AtmosphericLight0 = AtmosphericLight0Info ? AtmosphericLight0Info->Proxy : nullptr; const FProjectedShadowInfo* ProjectedShadowInfo0 = nullptr; if (AtmosphericLight0Info) { ProjectedShadowInfo0 = GetFirstWholeSceneShadowMap(SceneRenderer.ActiveViewFamily->VisibleLightInfos[AtmosphericLight0Info->Id]); } // Get the main view shadow info for the cloud shadows in reflection. if (!CloudRC.bSkipAtmosphericLightShadowmap && AtmosphericLight0 && ProjectedShadowInfo0) { SetVolumeShadowingShaderParameters(GraphBuilder, CloudRC.LightShadowShaderParams0, MainView, AtmosphericLight0Info, ProjectedShadowInfo0); } else { SetVolumeShadowingDefaultShaderParameters(GraphBuilder, CloudRC.LightShadowShaderParams0); } } else { SetVolumeShadowingDefaultShaderParameters(GraphBuilder, CloudRC.LightShadowShaderParams0); } } else { bShouldRenderVolumetricCloud = false; // Disable cloud rendering } } for (int32 CubeFace = StartCubeFace; CubeFace < EndCubeFace; CubeFace++) { RDG_EVENT_SCOPE(GraphBuilder, "Capture Face=%d", CubeFace); SkyRC.RenderTargets[0] = FRenderTargetBinding(SkyCubeTexture, ERenderTargetLoadAction::ELoad, 0, CubeFace); const FMatrix CubeViewRotationMatrix = CalcCubeFaceViewRotationMatrix((ECubeFace)CubeFace); SceneCubeViewInitOptions.ViewRotationMatrix = CubeViewRotationMatrix; SceneCubeViewInitOptions.ViewOrigin = SkyLight->CapturePosition; SceneCubeViewInitOptions.ProjectionMatrix = CubeProjectionMatrix; FViewMatrices CubeViewMatrices = FViewMatrices(SceneCubeViewInitOptions); CubeView.SetupCommonViewUniformBufferParameters( *CubeView.CachedViewUniformShaderParameters, FIntPoint(CubeWidth, CubeWidth), 1, FIntRect(FIntPoint(0, 0), FIntPoint(CubeWidth, CubeWidth)), CubeViewMatrices, CubeViewMatrices); // Notify the fact that we render a reflection, e.g. remove sun disk. CubeView.CachedViewUniformShaderParameters->RenderingReflectionCaptureMask = 1.0f; // Notify the fact that we render a reflection, e.g. use special exposure. CubeView.CachedViewUniformShaderParameters->RealTimeReflectionCapture = 1.0f; // We have rendered a sky dome with identity rotation at the SkyLight position for the capture. if (AtmosphereSetup) { FVector3f SkyCameraTranslatedWorldOrigin; FMatrix44f SkyViewLutReferential; FVector4f TempSkyPlanetData; if (MainView.bSceneHasSkyMaterial) { // Setup a constant referential for each of the faces of the dynamic reflection capture. // This is to have the FastSkyViewLUT match the one generated specifically for the capture point of view. const FVector3f SkyViewLutReferentialForward = FVector3f(1.0f, 0.0f, 0.0f); const FVector3f SkyViewLutReferentialRight = FVector3f(0.0f, 0.0f, -1.0f); AtmosphereSetup->ComputeViewData( SkyLight->CapturePosition, MainView.ViewMatrices.GetPreViewTranslation(), SkyViewLutReferentialForward, SkyViewLutReferentialRight, SkyCameraTranslatedWorldOrigin, TempSkyPlanetData, SkyViewLutReferential); CubeView.CachedViewUniformShaderParameters->SkyViewLutTexture = RealTimeReflectionCaptureSkyAtmosphereViewLutTexture->GetRHI(); } else { // Else if there is no sky material, we assume that no material is sampling the FastSkyViewLUT texture in the sky light reflection (bFastSky=bFastAerialPerspective=false). // But, we still need to udpate the sky parameters on the view according to the sky light capture position const FVector3f SkyViewLutReferentialForward = FVector3f(1.0f, 0.0f, 0.0f); const FVector3f SkyViewLutReferentialRight = FVector3f(0.0f, 0.0f, -1.0f); // LWC_TODO: SkyPlanetTranslatedWorldCenterAndViewHeight is FVector4f because it's from a shader, and will have lost precision already. AtmosphereSetup->ComputeViewData( SkyLight->CapturePosition, MainView.ViewMatrices.GetPreViewTranslation(), SkyViewLutReferentialForward, SkyViewLutReferentialRight, SkyCameraTranslatedWorldOrigin, TempSkyPlanetData, SkyViewLutReferential); } CubeView.CachedViewUniformShaderParameters->SkyPlanetTranslatedWorldCenterAndViewHeight = TempSkyPlanetData; CubeView.CachedViewUniformShaderParameters->SkyCameraTranslatedWorldOrigin = SkyCameraTranslatedWorldOrigin; CubeView.CachedViewUniformShaderParameters->SkyViewLutReferential = SkyViewLutReferential; } if (HasSkyAtmosphere()&& (MainView.bSceneHasSkyMaterial || HasVolumetricCloud()) && RealTimeReflectionCaptureCamera360APLutTexture.IsValid()) // we also check that because it seems it can happen for some view setup UE-107270, TODO find a repro for a proper fix. { CubeView.CachedViewUniformShaderParameters->CameraAerialPerspectiveVolume = RealTimeReflectionCaptureCamera360APLutTexture->GetRHI(); } else { CubeView.CachedViewUniformShaderParameters->CameraAerialPerspectiveVolume = GSystemTextures.VolumetricBlackDummy->GetRHI(); } CubeView.CreateViewUniformBuffers(*CubeView.CachedViewUniformShaderParameters); if (CubeView.bSceneHasSkyMaterial) { GPUScene.UploadDynamicPrimitiveShaderDataForView(GraphBuilder, this, CubeView, ExternalAccessQueue); } SkyRC.ViewUniformBuffer = CubeView.ViewUniformBuffer; SkyRC.ViewMatrices = &CubeViewMatrices; SkyRC.SkyAtmosphereViewLutTexture = BlackDummy2dTex; SkyRC.SkyAtmosphereCameraAerialPerspectiveVolume = BlackDummy3dTex; SkyRC.Viewport = FIntRect(FIntPoint(0, 0), FIntPoint(CubeWidth, CubeWidth)); SkyRC.bLightDiskEnabled = false; SkyRC.bRenderSkyPixel = true; SkyRC.AerialPerspectiveStartDepthInCm = 0.01f; SkyRC.NearClippingDistance = 0.01f; SkyRC.FeatureLevel = FeatureLevel; FCloudShadowAOData CloudShadowAOData; GetCloudShadowAOData(GetVolumetricCloudSceneInfo(), CubeView, GraphBuilder, CloudShadowAOData); SkyRC.bShouldSampleCloudShadow = CloudShadowAOData.bShouldSampleCloudShadow; SkyRC.VolumetricCloudShadowMap[0] = CloudShadowAOData.VolumetricCloudShadowMap[0]; SkyRC.VolumetricCloudShadowMap[1] = CloudShadowAOData.VolumetricCloudShadowMap[1]; SkyRC.bShouldSampleCloudSkyAO = CloudShadowAOData.bShouldSampleCloudSkyAO; SkyRC.VolumetricCloudSkyAO = CloudShadowAOData.VolumetricCloudSkyAO; const bool bUseDepthBuffer = CVarRealTimeReflectionCaptureDepthBuffer.GetValueOnRenderThread() > 0; FRDGTextureRef CubeDepthTexture = nullptr; if (bExecuteSky) { if(MainView.bSceneHasSkyMaterial || bShouldRenderSkyAtmosphere) { // If there are any mesh tagged as IsSky then we render them only, otherwise we simply render the sky atmosphere itself. if (MainView.bSceneHasSkyMaterial) { RDG_EVENT_SCOPE(GraphBuilder, "Capture Sky Materials", CubeFace); auto* PassParameters = GraphBuilder.AllocParameters(); PassParameters->View = CubeView.GetShaderParameters(); PassParameters->RenderTargets = SkyRC.RenderTargets; PassParameters->BasePass = CreateOpaqueBasePassUniformBuffer(GraphBuilder, MainView, 0); // Setup the depth buffer if (bUseDepthBuffer) { FRDGTextureDesc CubeDepthTextureDesc = FRDGTextureDesc::Create2D(FIntPoint(CubeWidth, CubeWidth), PF_DepthStencil, MainView.GetSceneTexturesConfig().DepthClearValue, TexCreate_DepthStencilTargetable | TexCreate_ShaderResource); CubeDepthTexture = GraphBuilder.CreateTexture(CubeDepthTextureDesc, TEXT("SkyLight.CubeDepthTexture")); PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(CubeDepthTexture, ERenderTargetLoadAction::EClear, FExclusiveDepthStencil::DepthWrite_StencilNop); } AddSimpleMeshPass(GraphBuilder, PassParameters, Scene, MainView, &InstanceCullingManager, RDG_EVENT_NAME("CaptureSkyMeshReflection"), SkyRC.Viewport, [&MainView, CubeViewUniformBuffer = CubeView.ViewUniformBuffer, bUseDepthBuffer, Scene](FDynamicPassMeshDrawListContext* DynamicMeshPassContext) { FMeshPassProcessorRenderState DrawRenderState; FExclusiveDepthStencil::Type BasePassDepthStencilAccess_Sky = bUseDepthBuffer ? FExclusiveDepthStencil::Type(Scene->DefaultBasePassDepthStencilAccess | FExclusiveDepthStencil::DepthWrite) : FExclusiveDepthStencil::Type(Scene->DefaultBasePassDepthStencilAccess & ~FExclusiveDepthStencil::DepthWrite); SetupBasePassState(BasePassDepthStencilAccess_Sky, false, DrawRenderState); FSkyPassMeshProcessor PassMeshProcessor(Scene, nullptr, DrawRenderState, DynamicMeshPassContext); const int32 SkyRealTimeReflectionOnlyMeshBatcheCount = MainView.SkyMeshBatches.Num(); for (int32 MeshBatchIndex = 0; MeshBatchIndex < SkyRealTimeReflectionOnlyMeshBatcheCount; ++MeshBatchIndex) { FSkyMeshBatch& SkyMeshBatch = MainView.SkyMeshBatches[MeshBatchIndex]; if (!SkyMeshBatch.bVisibleInRealTimeSkyCapture) { continue; } const FMeshBatch* MeshBatch = SkyMeshBatch.Mesh; const FPrimitiveSceneProxy* PrimitiveSceneProxy = SkyMeshBatch.Proxy; const FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo(); // Real time sky light capture cannot render dynamic meshes for now. // For those to be rendered we would need to specify a view to the PassMeshProcessor creation above. // Dynamic draws uses temporary per frame & per view data (appended at the end of the GPUScene buffer). // But the view is transient and data on it can morph, and correct data would need to be added to FGPUScenePrimitiveCollector (see UploadDynamicPrimitiveShaderDataForViewInternal) bool bSkipDynamicMesh = false; for (auto& Element : MeshBatch->Elements) { if (Element.PrimitiveIdMode == PrimID_DynamicPrimitiveShaderData) { bSkipDynamicMesh = true; } } if (bSkipDynamicMesh) { continue; } const uint64 DefaultBatchElementMask = ~0ull; PassMeshProcessor.AddMeshBatch(*MeshBatch, DefaultBatchElementMask, PrimitiveSceneProxy); } }); } else { RDG_EVENT_SCOPE(GraphBuilder, "Capture Sky Raw", CubeFace); FSceneTextureShaderParameters SceneTextures = CreateSceneTextureShaderParameters(GraphBuilder, &SceneRenderer.GetActiveSceneTextures(), SceneRenderer.FeatureLevel, ESceneTextureSetupMode::SceneDepth); SceneRenderer.RenderSkyAtmosphereInternal(GraphBuilder, SceneTextures, SkyRC); } // Also render the height fog as part of the sky render pass when time slicing is enabled. if (Scene && Scene->ExponentialFogs.Num() > 0) { FRenderRealTimeReflectionHeightFogVS::FPermutationDomain VsPermutationVector; TShaderMapRef VertexShader(GetGlobalShaderMap(SkyRC.FeatureLevel), VsPermutationVector); FRenderRealTimeReflectionHeightFogPS::FPermutationDomain PsPermutationVector; PsPermutationVector.Set(CubeDepthTexture != nullptr); TShaderMapRef PixelShader(GetGlobalShaderMap(SkyRC.FeatureLevel), PsPermutationVector); FRenderRealTimeReflectionHeightFogPS::FParameters* PsPassParameters = GraphBuilder.AllocParameters(); PsPassParameters->ViewUniformBuffer = CubeView.ViewUniformBuffer; PsPassParameters->RenderTargets = SkyRC.RenderTargets; PsPassParameters->DepthTexture = CubeDepthTexture != nullptr ? CubeDepthTexture : BlackDummy2dTex; PsPassParameters->FogStruct = CreateFogUniformBuffer(GraphBuilder, CubeView); ClearUnusedGraphResources(PixelShader, PsPassParameters); // Render height fog at an infinite distance since real time reflections does not have a depth buffer for now. // Volumetric fog is not supported in such reflections. GraphBuilder.AddPass( RDG_EVENT_NAME("DistantHeightFog"), PsPassParameters, ERDGPassFlags::Raster, [PsPassParameters, VertexShader, PixelShader, CubeWidth](FRHICommandList& RHICmdListLambda) { RHICmdListLambda.SetViewport(0.0f, 0.0f, 0.0f, CubeWidth, CubeWidth, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdListLambda.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; SetGraphicsPipelineState(RHICmdListLambda, GraphicsPSOInit, 0); FRenderRealTimeReflectionHeightFogVS::FParameters VsPassParameters; VsPassParameters.ViewUniformBuffer = PsPassParameters->ViewUniformBuffer; SetShaderParameters(RHICmdListLambda, VertexShader, VertexShader.GetVertexShader(), VsPassParameters); SetShaderParameters(RHICmdListLambda, PixelShader, PixelShader.GetPixelShader(), *PsPassParameters); RHICmdListLambda.DrawPrimitive(0, 1, 1); }); } } else { ClearCubeFace(SkyCubeTexture, CubeFace); } } if (bShouldRenderVolumetricCloud && bExecuteCloud) { CloudRC.ViewUniformBuffer = CubeView.ViewUniformBuffer; CloudRC.RenderTargets[0] = SkyRC.RenderTargets[0]; // CloudRC.RenderTargets[1] = Null target will skip export CloudRC.VolumetricCloudShadowTexture[0] = CloudShadowAOData.VolumetricCloudShadowMap[0]; CloudRC.VolumetricCloudShadowTexture[1] = CloudShadowAOData.VolumetricCloudShadowMap[1]; SceneRenderer.RenderVolumetricCloudsInternal(GraphBuilder, CloudRC, InstanceCullingManager); } } // Render lower hemisphere color if (SkyLight->bLowerHemisphereIsSolidColor) { FApplyLowerHemisphereColor::FPermutationDomain PermutationVector; TShaderMapRef ComputeShader(GetGlobalShaderMap(FeatureLevel), PermutationVector); const uint32 MipIndex = 0; const uint32 Mip0Resolution = SkyCubeTexture->Desc.GetSize().X; FApplyLowerHemisphereColor::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->ValidDispatchCoord = FIntPoint(Mip0Resolution, Mip0Resolution); PassParameters->LowerHemisphereSolidColor = SkyLight->LowerHemisphereColor; PassParameters->OutTextureMipColor = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkyCubeTexture, MipIndex)); FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(Mip0Resolution, Mip0Resolution, 1), FIntVector(FApplyLowerHemisphereColor::ThreadGroupSize, FApplyLowerHemisphereColor::ThreadGroupSize, 1)); // The groupd size per face with padding PassParameters->FaceThreadGroupSize = NumGroups.X * FConvolveSpecularFaceCS::ThreadGroupSize; // We are going to dispatch once for all faces NumGroups.X *= 6; FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("ApplyLowerHemisphereColor"), ComputeShader, PassParameters, NumGroups); } } else { for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++) { ClearCubeFace(SkyCubeTexture, CubeFace); } } }; auto RenderCubeFaces_GenCubeMips = [&](uint32 CubeMipStart, uint32 CubeMipEnd, TRefCountPtr& SkyRenderTarget) { check(CubeMipStart > 0); // Never write to mip0 as it has just been redered into FRDGTextureRef SkyCubeTexture = GraphBuilder.RegisterExternalTexture(SkyRenderTarget, TEXT("SkyRenderTarget")); FDownsampleCubeFaceCS::FPermutationDomain PermutationVector; TShaderMapRef ComputeShader(GetGlobalShaderMap(FeatureLevel), PermutationVector); for (uint32 MipIndex = CubeMipStart; MipIndex <= CubeMipEnd; MipIndex++) { const uint32 MipResolution = 1 << (CubeMipCount - MipIndex - 1); FRDGTextureSRVRef SkyCubeTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForMipLevel(SkyCubeTexture, MipIndex - 1)); // slice/face selection is useless so remove from CreateForMipLevel FDownsampleCubeFaceCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->MipIndex = MipIndex; PassParameters->NumMips = CubeMipCount; PassParameters->CubeFace = 0; // unused PassParameters->ValidDispatchCoord = FIntPoint(MipResolution, MipResolution); PassParameters->SourceCubemapSampler = TStaticSamplerState::GetRHI(); PassParameters->SourceCubemapTexture = SkyCubeTextureSRV; PassParameters->OutTextureMipColor = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(SkyCubeTexture, MipIndex)); FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(MipResolution, MipResolution, 1), FIntVector(FDownsampleCubeFaceCS::ThreadGroupSize, FDownsampleCubeFaceCS::ThreadGroupSize, 1)); // The groupd size per face with padding PassParameters->FaceThreadGroupSize = NumGroups.X * FDownsampleCubeFaceCS::ThreadGroupSize; // We are going to dispatch once for all faces NumGroups.X *= 6; // Dispatch with GenerateMips: reading from a slice through SRV and writing into lower mip through UAV. ClearUnusedGraphResources(ComputeShader, PassParameters); GraphBuilder.AddPass( Forward(RDG_EVENT_NAME("MipGen")), PassParameters, ERDGPassFlags::Compute, [PassParameters, ComputeShader, NumGroups](FRHICommandList& RHICmdList) { FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, NumGroups); }); } }; auto RenderCubeFaces_SpecularConvolution = [&](uint32 CubeMipStart, uint32 CubeMipEnd, uint32 FaceStart, uint32 FaceCount, TRefCountPtr& DstRenderTarget, TRefCountPtr& SrcRenderTarget) { check((FaceStart + FaceCount) <= 6); FRDGTextureRef RDGSrcRenderTarget = GraphBuilder.RegisterExternalTexture(SrcRenderTarget); FRDGTextureRef RDGDstRenderTarget = GraphBuilder.RegisterExternalTexture(DstRenderTarget); FRDGTextureSRVRef RDGSrcRenderTargetSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(RDGSrcRenderTarget)); FDownsampleCubeFaceCS::FPermutationDomain PermutationVector; TShaderMapRef ComputeShader(GetGlobalShaderMap(FeatureLevel), PermutationVector); for (uint32 MipIndex = CubeMipStart; MipIndex <= CubeMipEnd; MipIndex++) { const uint32 MipResolution = 1 << (CubeMipCount - MipIndex - 1); FConvolveSpecularFaceCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->MipIndex = MipIndex; PassParameters->NumMips = CubeMipCount; PassParameters->CubeFace = 0; // unused PassParameters->CubeFaceOffset = int(FaceStart); PassParameters->ValidDispatchCoord = FIntPoint(MipResolution, MipResolution); PassParameters->SourceCubemapSampler = TStaticSamplerState::GetRHI(); PassParameters->SourceCubemapTexture = RDGSrcRenderTargetSRV; PassParameters->OutTextureMipColor = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RDGDstRenderTarget, MipIndex)); FIntVector NumGroups = FIntVector::DivideAndRoundUp(FIntVector(MipResolution, MipResolution, 1), FIntVector(FConvolveSpecularFaceCS::ThreadGroupSize, FConvolveSpecularFaceCS::ThreadGroupSize, 1)); // The groupd size per face with padding PassParameters->FaceThreadGroupSize = NumGroups.X * FConvolveSpecularFaceCS::ThreadGroupSize; // We are going to dispatch once for all faces NumGroups.X *= FaceCount; FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("Convolve"), ComputeShader, PassParameters, NumGroups); } }; auto RenderCubeFaces_DiffuseIrradiance = [&](TRefCountPtr& SourceCubemap) { FRDGTextureRef SourceCubemapTexture = GraphBuilder.RegisterExternalTexture(SourceCubemap); FRDGTextureSRVRef SourceCubemapTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(SourceCubemapTexture)); FRDGBuffer* SkyIrradianceEnvironmentMapRDG = GraphBuilder.RegisterExternalBuffer(SkyIrradianceEnvironmentMap); TShaderMapRef ComputeShader(GetGlobalShaderMap(FeatureLevel)); const float SampleCount = FComputeSkyEnvMapDiffuseIrradianceCS::ThreadGroupSizeX * FComputeSkyEnvMapDiffuseIrradianceCS::ThreadGroupSizeY; const float UniformSampleSolidAngle = 4.0f * PI / SampleCount; // uniform distribution FComputeSkyEnvMapDiffuseIrradianceCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->SourceCubemapSampler = TStaticSamplerState::GetRHI(); PassParameters->SourceCubemapTexture = SourceCubemapTextureSRV; PassParameters->OutIrradianceEnvMapSH = GraphBuilder.CreateUAV(SkyIrradianceEnvironmentMapRDG); PassParameters->UniformSampleSolidAngle = UniformSampleSolidAngle; // For 64 uniform samples on the unit sphere, we roughly have 10 samples per face. // Considering mip generation and bilinear sampling, we can assume 10 samples is enough to integrate 10*4=40 texels. // With that, we target integration of 16*16 face. const uint32 Log2_16 = 4; // FMath::Log2(16.0f) PassParameters->MipIndex = uint32(FMath::Log2(float(CapturedSkyRenderTarget->GetDesc().GetSize().X))) - Log2_16; const FIntVector NumGroups = FIntVector(1, 1, 1); FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("ComputeSkyEnvMapDiffuseIrradianceCS"), ComputeShader, PassParameters, NumGroups); ExternalAccessQueue.Add(SkyIrradianceEnvironmentMapRDG, ERHIAccess::SRVMask); }; const uint32 LastMipLevel = CubeMipCount - 1; // Ensure the main view got the full cubemap by running all the capture operations for the first frame. // This ensures a proper initial state when time-slicing the steps. // Update the firt frame detection state variable if (bTimeSlicedRealTimeCapture) { // Go to next state iff this is a new frame if (bIsNewFrame) { switch (RealTimeSlicedReflectionCaptureFirstFrameState) { case ERealTimeSlicedReflectionCaptureFirstFrameState::INIT: RealTimeSlicedReflectionCaptureFirstFrameState = ERealTimeSlicedReflectionCaptureFirstFrameState::FIRST_FRAME; break; case ERealTimeSlicedReflectionCaptureFirstFrameState::FIRST_FRAME: RealTimeSlicedReflectionCaptureFirstFrameState = ERealTimeSlicedReflectionCaptureFirstFrameState::BEYOND_FIRST_FRAME; break; default: break; } } } else { // Reset the time-slicing first frame detection state when not time-slicing. RealTimeSlicedReflectionCaptureFirstFrameState = ERealTimeSlicedReflectionCaptureFirstFrameState::INIT; } if (!bTimeSlicedRealTimeCapture || (RealTimeSlicedReflectionCaptureFirstFrameState < ERealTimeSlicedReflectionCaptureFirstFrameState::BEYOND_FIRST_FRAME)) { // Generate a full cube map in a single frame for the first frame. // Perf number are for a 128x128x6 a cubemap on PS4 with sky and cloud and default settings // Since it is entirely generated each frame when time slicing is not enabled, we always use cubemap index 0 always allocated above ConvolvedSkyRenderTargetReadyIndex = 0; // 0.60ms (0.12ms for faces with the most clouds) RenderCubeFaces_SkyCloud(true, true, CapturedSkyRenderTarget, 0, CubeFace_MAX); // 0.05ms RenderCubeFaces_GenCubeMips(1, LastMipLevel, CapturedSkyRenderTarget); // 0.80ms total (0.30ms for mip0, 0.20ms for mip1+2, 0.30ms for remaining mips) RenderCubeFaces_SpecularConvolution(0, LastMipLevel, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex], CapturedSkyRenderTarget); // 0.015ms RenderCubeFaces_DiffuseIrradiance(ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex]); // Reset Scene time slicing state so that it starts from the beginning if/when we get out of non-time-sliced. RealTimeSlicedReflectionCaptureState = -1; // Value of -1 indicates this is the first time-sliced iteration. // The sky just changed, so invalidate these textures, so that the path tracer can rebuild them PathTracingSkylightTexture.SafeRelease(); PathTracingSkylightPdf.SafeRelease(); } else { // Each frame we capture the sky and work in ProcessedSkyRenderTarget to generate the specular convolution. // Once done, we copy the result into ConvolvedSkyRenderTarget and generate the sky irradiance SH from there. // On the first frame, we always fully initialise the convolution so ConvolvedSkyRenderTargetReadyIndex should already be valid. check(ConvolvedSkyRenderTargetReadyIndex >= 0 && ConvolvedSkyRenderTargetReadyIndex <= 1); const int32 ConvolvedSkyRenderTargetWorkIndex = 1 - ConvolvedSkyRenderTargetReadyIndex; const int32 TimeSliceCount = 12; #define DEBUG_TIME_SLICE 0 #if DEBUG_TIME_SLICE RealTimeSlicedReflectionCaptureState = -1; RealTimeSlicedReflectionCaptureStateStep = 0; while(true) { if (RealTimeSlicedReflectionCaptureState+1 >= TimeSliceCount) { break; } #endif // Update the current time-slicing state if this is a new frame and if the current step is done. // Note: RealTimeSlicedReflectionCaptureState will initially be -1. if (bIsNewFrame && bRealTimeSlicedReflectionCaptureStateStepDone) { if (++RealTimeSlicedReflectionCaptureState >= TimeSliceCount) { RealTimeSlicedReflectionCaptureState = 0; RealTimeSlicedReflectionCaptureStateStep = 0; } } bRealTimeSlicedReflectionCaptureStateStepDone = true; const int32 SkyCloudFrameStepCount = FMath::Clamp(CVarRealTimeReflectionCaptureTimeSlicingSkyCloudCubeFacePerFrame.GetValueOnRenderThread(), int32(1), int32(CubeFace_MAX)); const int32 SkyCloudStartSubStep = FMath::Clamp(RealTimeSlicedReflectionCaptureStateStep, int32(0), int32(CubeFace_MAX - 1)); const int32 SkyCloudEndSubStep = FMath::Clamp(RealTimeSlicedReflectionCaptureStateStep + SkyCloudFrameStepCount, int32(0), int32(CubeFace_MAX)); if (RealTimeSlicedReflectionCaptureState <= 0) { RDG_EVENT_SCOPE(GraphBuilder, "RenderSky StartFace=%d EndFace=%d", SkyCloudStartSubStep, SkyCloudEndSubStep); RenderCubeFaces_SkyCloud(true, false, CapturedSkyRenderTarget, SkyCloudStartSubStep, SkyCloudEndSubStep); bRealTimeSlicedReflectionCaptureStateStepDone = SkyCloudEndSubStep >= CubeFace_MAX; RealTimeSlicedReflectionCaptureStateStep = bRealTimeSlicedReflectionCaptureStateStepDone ? 0 : SkyCloudEndSubStep; } else if (RealTimeSlicedReflectionCaptureState == 1) { RDG_EVENT_SCOPE(GraphBuilder, "RenderCloud StartFace=%d EndFace=%d", SkyCloudStartSubStep, SkyCloudEndSubStep); RenderCubeFaces_SkyCloud(false, true, CapturedSkyRenderTarget, SkyCloudStartSubStep, SkyCloudEndSubStep); bRealTimeSlicedReflectionCaptureStateStepDone = SkyCloudEndSubStep >= CubeFace_MAX; RealTimeSlicedReflectionCaptureStateStep = bRealTimeSlicedReflectionCaptureStateStepDone ? 0 : SkyCloudEndSubStep; } else if (RealTimeSlicedReflectionCaptureState == 2) { RDG_EVENT_SCOPE(GraphBuilder, "GenCubeMips"); RenderCubeFaces_GenCubeMips(1, LastMipLevel, CapturedSkyRenderTarget); } else if (RealTimeSlicedReflectionCaptureState == 3) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip0Face01"); RenderCubeFaces_SpecularConvolution(0, 0, 0, 2, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); // convolution of mip0, face 0, 1 } else if (RealTimeSlicedReflectionCaptureState == 4) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip0Face23"); RenderCubeFaces_SpecularConvolution(0, 0, 2, 2, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); // convolution of mip0, face 2, 3 } else if (RealTimeSlicedReflectionCaptureState == 5) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip0Face45"); RenderCubeFaces_SpecularConvolution(0, 0, 4, 2, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); // convolution of mip0, face 4, 5 } else if (RealTimeSlicedReflectionCaptureState == 6) { if (LastMipLevel >= 1) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip1"); RenderCubeFaces_SpecularConvolution(1, 1, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } } else if (RealTimeSlicedReflectionCaptureState == 7) { if (LastMipLevel >= 2) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip2"); RenderCubeFaces_SpecularConvolution(2, 2, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } } else if (RealTimeSlicedReflectionCaptureState == 8) { if (LastMipLevel >= 3) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip3"); RenderCubeFaces_SpecularConvolution(3, 3, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } } else if (RealTimeSlicedReflectionCaptureState == 9) { if (LastMipLevel >= 5) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip45"); RenderCubeFaces_SpecularConvolution(4, 5, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } else if (LastMipLevel >= 4) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip4"); RenderCubeFaces_SpecularConvolution(4, 4, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } } else if (RealTimeSlicedReflectionCaptureState == 10) { if (LastMipLevel >= 6) { RDG_EVENT_SCOPE(GraphBuilder, "ConvolutionMip6Etc"); RenderCubeFaces_SpecularConvolution(6, LastMipLevel, 0, 6, ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex], CapturedSkyRenderTarget); } } else if (RealTimeSlicedReflectionCaptureState == 11) { RDG_EVENT_SCOPE(GraphBuilder, "DiffuseIrradiance"); // Update the sky irradiance SH buffer. RenderCubeFaces_DiffuseIrradiance(ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetWorkIndex]); // Now use the new cubemap ConvolvedSkyRenderTargetReadyIndex = ConvolvedSkyRenderTargetWorkIndex; // The sky just changed, so invalidate these textures, so that the path tracer can rebuild them PathTracingSkylightTexture.SafeRelease(); PathTracingSkylightPdf.SafeRelease(); } #if DEBUG_TIME_SLICE } #endif } if (ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex]) { ExternalAccessQueue.Add(GraphBuilder.RegisterExternalTexture(ConvolvedSkyRenderTarget[ConvolvedSkyRenderTargetReadyIndex]), ERHIAccess::SRVMask); } ExternalAccessQueue.Submit(GraphBuilder); }