From dad07796eeb4c558aa0e51b702c8d9ca0dd5969e Mon Sep 17 00:00:00 2001 From: BlueRose <378100977@qq.com> Date: Thu, 8 Feb 2024 15:13:04 +0800 Subject: [PATCH] vault backup: 2024-02-08 15:13:04 --- .../剖析虚幻渲染体系(04)- 延迟渲染管线.md | 1046 ++++++++++++++++- 1 file changed, 1045 insertions(+), 1 deletion(-) diff --git a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(04)- 延迟渲染管线.md b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(04)- 延迟渲染管线.md index 6297b0f..d0e191b 100644 --- a/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(04)- 延迟渲染管线.md +++ b/03-UnrealEngine/Rendering/RenderingPipeline/向往渲染系列文章阅读笔记/剖析虚幻渲染体系(04)- 延迟渲染管线.md @@ -364,4 +364,1048 @@ void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) UpdateGPUScene()代码略…… -### InitViews(5.3已经变成了BeginInitViews() & EndInitViews()) \ No newline at end of file +### InitViews(5.3已经变成了BeginInitViews() & EndInitViews()) +- InitViews()(BeginInitViews()) + - PreVisibilityFrameSetup():做了大量的初始化工作,如静态网格、Groom、SkinCache、特效、TAA、ViewState等等。 + - 初始化特效系统(FXSystem)。 + - ComputeViewVisibility():最重要的功能就是处理图元的可见性,包含视椎体裁剪、遮挡剔除,以及收集动态网格信息、创建光源信息等。 + - FPrimitiveSceneInfo::UpdateStaticMeshes:更新静态网格数据。 + - ViewState::GetPrecomputedVisibilityData:获取预计算的可见性数据。 + - FrustumCull:视锥体裁剪。 + - **ComputeAndMarkRelevanceForViewParallel**:计算和标记视图并行处理的关联数据。 + - **GatherDynamicMeshElements**:收集view的动态可见元素,上一篇中已经解析过。 + - **SetupMeshPass**:设置网格Pass的数据,将FMeshBatch转换成FMeshDrawCommand,上一篇中已经解析过。 + - CreateIndirectCapsuleShadows:创建胶囊体阴影。 + - UpdateSkyIrradianceGpuBuffer:更新天空体环境光照的GPU数据。 + - InitSkyAtmosphereForViews:初始化大气效果。 + - **PostVisibilityFrameSetup()**:利用view的视锥裁剪光源,防止视线外或屏幕占比很小或没有光照强度的光源进入shader计算。此外,还会处理贴花排序、调整之前帧的RT和雾效、光束等。 + - FViewInfo::InitRHIResources():初始化每个View的UniformBuffer。 + - SetupVolumetricFog:初始化和设置体积雾。 + - FSceneRenderer::OnStartRender():通知RHI已经开启了渲染,以初始化视图相关的数据和资源。 + +以下是旧版本代码: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp + +bool FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, struct FILCUpdatePrimTaskData& ILCTaskData, FGraphEventArray& UpdateViewCustomDataEvents) +{ + // 创建可见性帧设置预备阶段. + PreVisibilityFrameSetup(RHICmdList); + + RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread); + + // 特效系统初始化 + { + if (Scene->FXSystem && Scene->FXSystem->RequiresEarlyViewUniformBuffer() && Views.IsValidIndex(0)) + { + // 保证第一个view的RHI资源已被初始化. + Views[0].InitRHIResources(); + Scene->FXSystem->PostInitViews(RHICmdList, Views[0].ViewUniformBuffer, Views[0].AllowGPUParticleUpdate() && !ViewFamily.EngineShowFlags.HitProxies); + } + } + + // 创建可见性网格指令. + FViewVisibleCommandsPerView ViewCommandsPerView; + ViewCommandsPerView.SetNum(Views.Num()); + + // 计算可见性. + ComputeViewVisibility(RHICmdList, BasePassDepthStencilAccess, ViewCommandsPerView, DynamicIndexBufferForInitViews, DynamicVertexBufferForInitViews, DynamicReadBufferForInitViews); + + RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread); + + // 胶囊阴影 + CreateIndirectCapsuleShadows(); + RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread); + + // 初始化大气效果. + if (ShouldRenderSkyAtmosphere(Scene, ViewFamily.EngineShowFlags)) + { + InitSkyAtmosphereForViews(RHICmdList); + } + + // 创建可见性帧设置后置阶段. + PostVisibilityFrameSetup(ILCTaskData); + RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread); + + (......) + + // 是否可能在Prepass之后初始化view,由GDoInitViewsLightingAfterPrepass决定,而GDoInitViewsLightingAfterPrepass又可通过控制台命令r.DoInitViewsLightingAfterPrepass设定。 + bool bDoInitViewAftersPrepass = !!GDoInitViewsLightingAfterPrepass; + if (!bDoInitViewAftersPrepass) + { + InitViewsPossiblyAfterPrepass(RHICmdList, ILCTaskData, UpdateViewCustomDataEvents); + } + + { + // 初始化所有view的uniform buffer和RHI资源. + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + + if (View.ViewState) + { + if (!View.ViewState->ForwardLightingResources) + { + View.ViewState->ForwardLightingResources.Reset(new FForwardLightingViewResources()); + } + + View.ForwardLightingResources = View.ViewState->ForwardLightingResources.Get(); + } + else + { + View.ForwardLightingResourcesStorage.Reset(new FForwardLightingViewResources()); + View.ForwardLightingResources = View.ForwardLightingResourcesStorage.Get(); + } + +#if RHI_RAYTRACING + View.IESLightProfileResource = View.ViewState ? &View.ViewState->IESLightProfileResources : nullptr; +#endif + // Set the pre-exposure before initializing the constant buffers. + if (View.ViewState) + { + View.ViewState->UpdatePreExposure(View); + } + + // Initialize the view's RHI resources. + View.InitRHIResources(); + } + } + + // 体积雾 + SetupVolumetricFog(); + + // 发送开始渲染事件. + OnStartRender(RHICmdList); + + return bDoInitViewAftersPrepass; +} +``` +上面的代码似乎没有做太多逻辑,然而实际上很多逻辑分散在了上面的一些重要接口中,先分析`PreVisibilityFrameSetup`: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp + +void FDeferredShadingSceneRenderer::PreVisibilityFrameSetup(FRHICommandListImmediate& RHICmdList) +{ + // Possible stencil dither optimization approach + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + View.bAllowStencilDither = bDitheredLODTransitionsUseStencil; + } + + FSceneRenderer::PreVisibilityFrameSetup(RHICmdList); +} + +void FSceneRenderer::PreVisibilityFrameSetup(FRHICommandListImmediate& RHICmdList) +{ + // 通知RHI已经开始渲染场景了. + RHICmdList.BeginScene(); + + { + static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DoLazyStaticMeshUpdate")); + const bool DoLazyStaticMeshUpdate = (CVar->GetInt() && !GIsEditor); + + // 延迟的静态网格更新. + if (DoLazyStaticMeshUpdate) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_PreVisibilityFrameSetup_EvictionForLazyStaticMeshUpdate); + static int32 RollingRemoveIndex = 0; + static int32 RollingPassShrinkIndex = 0; + if (RollingRemoveIndex >= Scene->Primitives.Num()) + { + RollingRemoveIndex = 0; + RollingPassShrinkIndex++; + if (RollingPassShrinkIndex >= UE_ARRAY_COUNT(Scene->CachedDrawLists)) + { + RollingPassShrinkIndex = 0; + } + // Periodically shrink the SparseArray containing cached mesh draw commands which we are causing to be regenerated with UpdateStaticMeshes + Scene->CachedDrawLists[RollingPassShrinkIndex].MeshDrawCommands.Shrink(); + } + const int32 NumRemovedPerFrame = 10; + TArray> SceneInfos; + for (int32 NumRemoved = 0; NumRemoved < NumRemovedPerFrame && RollingRemoveIndex < Scene->Primitives.Num(); NumRemoved++, RollingRemoveIndex++) + { + SceneInfos.Add(Scene->Primitives[RollingRemoveIndex]); + } + FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, SceneInfos, false); + } + } + + // 转换Skin Cache + RunGPUSkinCacheTransition(RHICmdList, Scene, EGPUSkinCacheTransition::FrameSetup); + + // 初始化Groom头发 + if (IsHairStrandsEnable(Scene->GetShaderPlatform()) && Views.Num() > 0) + { + const EWorldType::Type WorldType = Views[0].Family->Scene->GetWorld()->WorldType; + const FShaderDrawDebugData* ShaderDrawData = &Views[0].ShaderDrawData; + auto ShaderMap = GetGlobalShaderMap(FeatureLevel); + RunHairStrandsInterpolation(RHICmdList, WorldType, Scene->GetGPUSkinCache(), ShaderDrawData, ShaderMap, EHairStrandsInterpolationType::SimulationStrands, nullptr); + } + + // 特效系统 + if (Scene->FXSystem && Views.IsValidIndex(0)) + { + Scene->FXSystem->PreInitViews(RHICmdList, Views[0].AllowGPUParticleUpdate() && !ViewFamily.EngineShowFlags.HitProxies); + } + + (......) + + // 设置运动模糊参数(包含TAA的处理) + for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + FSceneViewState* ViewState = View.ViewState; + + check(View.VerifyMembersChecks()); + + // Once per render increment the occlusion frame counter. + if (ViewState) + { + ViewState->OcclusionFrameCounter++; + } + + // HighResScreenshot should get best results so we don't do the occlusion optimization based on the former frame + extern bool GIsHighResScreenshot; + const bool bIsHitTesting = ViewFamily.EngineShowFlags.HitProxies; + // Don't test occlusion queries in collision viewmode as they can be bigger then the rendering bounds. + const bool bCollisionView = ViewFamily.EngineShowFlags.CollisionVisibility || ViewFamily.EngineShowFlags.CollisionPawn; + if (GIsHighResScreenshot || !DoOcclusionQueries(FeatureLevel) || bIsHitTesting || bCollisionView) + { + View.bDisableQuerySubmissions = true; + View.bIgnoreExistingQueries = true; + } + FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList); + + // set up the screen area for occlusion + float NumPossiblePixels = SceneContext.UseDownsizedOcclusionQueries() && IsValidRef(SceneContext.GetSmallDepthSurface()) ? + (float)View.ViewRect.Width() / SceneContext.GetSmallColorDepthDownsampleFactor() * (float)View.ViewRect.Height() / SceneContext.GetSmallColorDepthDownsampleFactor() : + View.ViewRect.Width() * View.ViewRect.Height(); + View.OneOverNumPossiblePixels = NumPossiblePixels > 0.0 ? 1.0f / NumPossiblePixels : 0.0f; + + // Still need no jitter to be set for temporal feedback on SSR (it is enabled even when temporal AA is off). + check(View.TemporalJitterPixels.X == 0.0f); + check(View.TemporalJitterPixels.Y == 0.0f); + + // Cache the projection matrix b + // Cache the projection matrix before AA is applied + View.ViewMatrices.SaveProjectionNoAAMatrix(); + + if (ViewState) + { + check(View.bStatePrevViewInfoIsReadOnly); + View.bStatePrevViewInfoIsReadOnly = ViewFamily.bWorldIsPaused || ViewFamily.EngineShowFlags.HitProxies || bFreezeTemporalHistories; + + ViewState->SetupDistanceFieldTemporalOffset(ViewFamily); + + if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences) + { + ViewState->FrameIndex++; + } + + if (View.OverrideFrameIndexValue.IsSet()) + { + ViewState->FrameIndex = View.OverrideFrameIndexValue.GetValue(); + } + } + + // Subpixel jitter for temporal AA + int32 CVarTemporalAASamplesValue = CVarTemporalAASamples.GetValueOnRenderThread(); + + bool bTemporalUpsampling = View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale; + + // Apply a sub pixel offset to the view. + if (View.AntiAliasingMethod == AAM_TemporalAA && ViewState && (CVarTemporalAASamplesValue > 0 || bTemporalUpsampling) && View.bAllowTemporalJitter) + { + float EffectivePrimaryResolutionFraction = float(View.ViewRect.Width()) / float(View.GetSecondaryViewRectSize().X); + + // Compute number of TAA samples. + int32 TemporalAASamples = CVarTemporalAASamplesValue; + { + if (Scene->GetFeatureLevel() < ERHIFeatureLevel::SM5) + { + // Only support 2 samples for mobile temporal AA. + TemporalAASamples = 2; + } + else if (bTemporalUpsampling) + { + // When doing TAA upsample with screen percentage < 100%, we need extra temporal samples to have a + // constant temporal sample density for final output pixels to avoid output pixel aligned converging issues. + TemporalAASamples = float(TemporalAASamples) * FMath::Max(1.f, 1.f / (EffectivePrimaryResolutionFraction * EffectivePrimaryResolutionFraction)); + } + else if (CVarTemporalAASamplesValue == 5) + { + TemporalAASamples = 4; + } + + TemporalAASamples = FMath::Clamp(TemporalAASamples, 1, 255); + } + + // Compute the new sample index in the temporal sequence. + int32 TemporalSampleIndex = ViewState->TemporalAASampleIndex + 1; + if(TemporalSampleIndex >= TemporalAASamples || View.bCameraCut) + { + TemporalSampleIndex = 0; + } + + // Updates view state. + if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences) + { + ViewState->TemporalAASampleIndex = TemporalSampleIndex; + ViewState->TemporalAASampleIndexUnclamped = ViewState->TemporalAASampleIndexUnclamped+1; + } + + // 根据不同的TAA采样策略和参数, 选择和计算对应的采样参数. + float SampleX, SampleY; + if (Scene->GetFeatureLevel() < ERHIFeatureLevel::SM5) + { + float SamplesX[] = { -8.0f/16.0f, 0.0/16.0f }; + float SamplesY[] = { /* - */ 0.0f/16.0f, 8.0/16.0f }; + check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); + SampleX = SamplesX[ TemporalSampleIndex ]; + SampleY = SamplesY[ TemporalSampleIndex ]; + } + else if (View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale) + { + // Uniformly distribute temporal jittering in [-.5; .5], because there is no longer any alignement of input and output pixels. + SampleX = Halton(TemporalSampleIndex + 1, 2) - 0.5f; + SampleY = Halton(TemporalSampleIndex + 1, 3) - 0.5f; + + View.MaterialTextureMipBias = -(FMath::Max(-FMath::Log2(EffectivePrimaryResolutionFraction), 0.0f) ) + CVarMinAutomaticViewMipBiasOffset.GetValueOnRenderThread(); + View.MaterialTextureMipBias = FMath::Max(View.MaterialTextureMipBias, CVarMinAutomaticViewMipBias.GetValueOnRenderThread()); + } + else if( CVarTemporalAASamplesValue == 2 ) + { + // 2xMSAA + // Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx + // N. + // .S + float SamplesX[] = { -4.0f/16.0f, 4.0/16.0f }; + float SamplesY[] = { -4.0f/16.0f, 4.0/16.0f }; + check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); + SampleX = SamplesX[ TemporalSampleIndex ]; + SampleY = SamplesY[ TemporalSampleIndex ]; + } + else if( CVarTemporalAASamplesValue == 3 ) + { + // 3xMSAA + // A.. + // ..B + // .C. + // Rolling circle pattern (A,B,C). + float SamplesX[] = { -2.0f/3.0f, 2.0/3.0f, 0.0/3.0f }; + float SamplesY[] = { -2.0f/3.0f, 0.0/3.0f, 2.0/3.0f }; + check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); + SampleX = SamplesX[ TemporalSampleIndex ]; + SampleY = SamplesY[ TemporalSampleIndex ]; + } + else if( CVarTemporalAASamplesValue == 4 ) + { + // 4xMSAA + // Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx + // .N.. + // ...E + // W... + // ..S. + // Rolling circle pattern (N,E,S,W). + float SamplesX[] = { -2.0f/16.0f, 6.0/16.0f, 2.0/16.0f, -6.0/16.0f }; + float SamplesY[] = { -6.0f/16.0f, -2.0/16.0f, 6.0/16.0f, 2.0/16.0f }; + check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); + SampleX = SamplesX[ TemporalSampleIndex ]; + SampleY = SamplesY[ TemporalSampleIndex ]; + } + else if( CVarTemporalAASamplesValue == 5 ) + { + // Compressed 4 sample pattern on same vertical and horizontal line (less temporal flicker). + // Compressed 1/2 works better than correct 2/3 (reduced temporal flicker). + // . N . + // W . E + // . S . + // Rolling circle pattern (N,E,S,W). + float SamplesX[] = { 0.0f/2.0f, 1.0/2.0f, 0.0/2.0f, -1.0/2.0f }; + float SamplesY[] = { -1.0f/2.0f, 0.0/2.0f, 1.0/2.0f, 0.0/2.0f }; + check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); + SampleX = SamplesX[ TemporalSampleIndex ]; + SampleY = SamplesY[ TemporalSampleIndex ]; + } + else + { + float u1 = Halton( TemporalSampleIndex + 1, 2 ); + float u2 = Halton( TemporalSampleIndex + 1, 3 ); + + // Generates samples in normal distribution + // exp( x^2 / Sigma^2 ) + + static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TemporalAAFilterSize")); + float FilterSize = CVar->GetFloat(); + + // Scale distribution to set non-unit variance + // Variance = Sigma^2 + float Sigma = 0.47f * FilterSize; + + // Window to [-0.5, 0.5] output + // Without windowing we could generate samples far away on the infinite tails. + float OutWindow = 0.5f; + float InWindow = FMath::Exp( -0.5 * FMath::Square( OutWindow / Sigma ) ); + + // Box-Muller transform + float Theta = 2.0f * PI * u2; + float r = Sigma * FMath::Sqrt( -2.0f * FMath::Loge( (1.0f - u1) * InWindow + u1 ) ); + + SampleX = r * FMath::Cos( Theta ); + SampleY = r * FMath::Sin( Theta ); + } + + View.TemporalJitterSequenceLength = TemporalAASamples; + View.TemporalJitterIndex = TemporalSampleIndex; + View.TemporalJitterPixels.X = SampleX; + View.TemporalJitterPixels.Y = SampleY; + + View.ViewMatrices.HackAddTemporalAAProjectionJitter(FVector2D(SampleX * 2.0f / View.ViewRect.Width(), SampleY * -2.0f / View.ViewRect.Height())); + } + + // Setup a new FPreviousViewInfo from current frame infos. + FPreviousViewInfo NewPrevViewInfo; + { + NewPrevViewInfo.ViewMatrices = View.ViewMatrices; + } + + // 初始化view state + if ( ViewState ) + { + // update previous frame matrices in case world origin was rebased on this frame + if (!View.OriginOffsetThisFrame.IsZero()) + { + ViewState->PrevFrameViewInfo.ViewMatrices.ApplyWorldOffset(View.OriginOffsetThisFrame); + } + + // determine if we are initializing or we should reset the persistent state + const float DeltaTime = View.Family->CurrentRealTime - ViewState->LastRenderTime; + const bool bFirstFrameOrTimeWasReset = DeltaTime < -0.0001f || ViewState->LastRenderTime < 0.0001f; + const bool bIsLargeCameraMovement = IsLargeCameraMovement( + View, + ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(), + ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(), + 45.0f, 10000.0f); + const bool bResetCamera = (bFirstFrameOrTimeWasReset || View.bCameraCut || bIsLargeCameraMovement || View.bForceCameraVisibilityReset); + + (......) + + if (bResetCamera) + { + View.PrevViewInfo = NewPrevViewInfo; + + // PT: If the motion blur shader is the last shader in the post-processing chain then it is the one that is + // adjusting for the viewport offset. So it is always required and we can't just disable the work the + // shader does. The correct fix would be to disable the effect when we don't need it and to properly mark + // the uber-postprocessing effect as the last effect in the chain. + + View.bPrevTransformsReset = true; + } + else + { + View.PrevViewInfo = ViewState->PrevFrameViewInfo; + } + + // Replace previous view info of the view state with this frame, clearing out references over render target. + if (!View.bStatePrevViewInfoIsReadOnly) + { + ViewState->PrevFrameViewInfo = NewPrevViewInfo; + } + + // If the view has a previous view transform, then overwrite the previous view info for the _current_ frame. + if (View.PreviousViewTransform.IsSet()) + { + // Note that we must ensure this transform ends up in ViewState->PrevFrameViewInfo else it will be used to calculate the next frame's motion vectors as well + View.PrevViewInfo.ViewMatrices.UpdateViewMatrix(View.PreviousViewTransform->GetTranslation(), View.PreviousViewTransform->GetRotation().Rotator()); + } + + // detect conditions where we should reset occlusion queries + if (bFirstFrameOrTimeWasReset || + ViewState->LastRenderTime + GEngine->PrimitiveProbablyVisibleTime < View.Family->CurrentRealTime || + View.bCameraCut || + View.bForceCameraVisibilityReset || + IsLargeCameraMovement( + View, + ViewState->PrevViewMatrixForOcclusionQuery, + ViewState->PrevViewOriginForOcclusionQuery, + GEngine->CameraRotationThreshold, GEngine->CameraTranslationThreshold)) + { + View.bIgnoreExistingQueries = true; + View.bDisableDistanceBasedFadeTransitions = true; + } + + // Turn on/off round-robin occlusion querying in the ViewState + static const auto CVarRROCC = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.RoundRobinOcclusion")); + const bool bEnableRoundRobin = CVarRROCC ? (CVarRROCC->GetValueOnAnyThread() != false) : false; + if (bEnableRoundRobin != ViewState->IsRoundRobinEnabled()) + { + ViewState->UpdateRoundRobin(bEnableRoundRobin); + View.bIgnoreExistingQueries = true; + } + + ViewState->PrevViewMatrixForOcclusionQuery = View.ViewMatrices.GetViewMatrix(); + ViewState->PrevViewOriginForOcclusionQuery = View.ViewMatrices.GetViewOrigin(); + + (......) + + // we don't use DeltaTime as it can be 0 (in editor) and is computed by subtracting floats (loses precision over time) + // Clamp DeltaWorldTime to reasonable values for the purposes of motion blur, things like TimeDilation can make it very small + // Offline renders always control the timestep for the view and always need the timescales calculated. + if (!ViewFamily.bWorldIsPaused || View.bIsOfflineRender) + { + ViewState->UpdateMotionBlurTimeScale(View); + } + + + ViewState->PrevFrameNumber = ViewState->PendingPrevFrameNumber; + ViewState->PendingPrevFrameNumber = View.Family->FrameNumber; + + // This finishes the update of view state + ViewState->UpdateLastRenderTime(*View.Family); + + ViewState->UpdateTemporalLODTransition(View); + } + else + { + // Without a viewstate, we just assume that camera has not moved. + View.PrevViewInfo = NewPrevViewInfo; + } + } + + // 设置全局抖动参数和过渡uniform buffer. + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + + FDitherUniformShaderParameters DitherUniformShaderParameters; + DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition(); + View.DitherFadeOutUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame); + + DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition() - 1.0f; + View.DitherFadeInUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame); + } +} +``` +由此可知,`PreVisibilityFrameSetup`做了大量的初始化工作,如静态网格、Groom、SkinCache、特效、TAA、ViewState等等。接着继续分析`ComputeViewVisibility`: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp + +void FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FViewVisibleCommandsPerView& ViewCommandsPerView, FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer) +{ + // 分配可见光源信息列表。 + if (Scene->Lights.GetMaxIndex() > 0) + { + VisibleLightInfos.AddZeroed(Scene->Lights.GetMaxIndex()); + } + + int32 NumPrimitives = Scene->Primitives.Num(); + float CurrentRealTime = ViewFamily.CurrentRealTime; + + FPrimitiveViewMasks HasDynamicMeshElementsMasks; + HasDynamicMeshElementsMasks.AddZeroed(NumPrimitives); + + FPrimitiveViewMasks HasViewCustomDataMasks; + HasViewCustomDataMasks.AddZeroed(NumPrimitives); + + FPrimitiveViewMasks HasDynamicEditorMeshElementsMasks; + + if (GIsEditor) + { + HasDynamicEditorMeshElementsMasks.AddZeroed(NumPrimitives); + } + + const bool bIsInstancedStereo = (Views.Num() > 0) ? (Views[0].IsInstancedStereoPass() || Views[0].bIsMobileMultiViewEnabled) : false; + UpdateReflectionSceneData(Scene); + + // 更新不判断可见性的静态网格. + { + Scene->ConditionalMarkStaticMeshElementsForUpdate(); + + TArray UpdatedSceneInfos; + for (TSet::TIterator It(Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck); It; ++It) + { + FPrimitiveSceneInfo* Primitive = *It; + if (Primitive->NeedsUpdateStaticMeshes()) + { + UpdatedSceneInfos.Add(Primitive); + } + } + if (UpdatedSceneInfos.Num() > 0) + { + FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, UpdatedSceneInfos); + } + Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck.Reset(); + } + + // 初始化所有view的数据. + uint8 ViewBit = 0x1; + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex, ViewBit <<= 1) + { + STAT(NumProcessedPrimitives += NumPrimitives); + + FViewInfo& View = Views[ViewIndex]; + FViewCommands& ViewCommands = ViewCommandsPerView[ViewIndex]; + FSceneViewState* ViewState = (FSceneViewState*)View.State; + + // Allocate the view's visibility maps. + View.PrimitiveVisibilityMap.Init(false,Scene->Primitives.Num()); + // we don't initialized as we overwrite the whole array (in GatherDynamicMeshElements) + View.DynamicMeshEndIndices.SetNumUninitialized(Scene->Primitives.Num()); + View.PrimitiveDefinitelyUnoccludedMap.Init(false,Scene->Primitives.Num()); + View.PotentiallyFadingPrimitiveMap.Init(false,Scene->Primitives.Num()); + View.PrimitiveFadeUniformBuffers.AddZeroed(Scene->Primitives.Num()); + View.PrimitiveFadeUniformBufferMap.Init(false, Scene->Primitives.Num()); + View.StaticMeshVisibilityMap.Init(false,Scene->StaticMeshes.GetMaxIndex()); + View.StaticMeshFadeOutDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex()); + View.StaticMeshFadeInDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex()); + View.StaticMeshBatchVisibility.AddZeroed(Scene->StaticMeshBatchVisibility.GetMaxIndex()); + View.PrimitivesLODMask.Init(FLODMask(), Scene->Primitives.Num()); + + View.PrimitivesCustomData.Init(nullptr, Scene->Primitives.Num()); + + // We must reserve to prevent realloc otherwise it will cause memory leak if we Execute In Parallel + const bool WillExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0; + View.PrimitiveCustomDataMemStack.Reserve(WillExecuteInParallel ? FMath::CeilToInt(((float)View.PrimitiveVisibilityMap.Num() / (float)FRelevancePrimSet::MaxInputPrims)) + 1 : 1); + + View.AllocateCustomDataMemStack(); + + View.VisibleLightInfos.Empty(Scene->Lights.GetMaxIndex()); + + View.DirtyIndirectLightingCacheBufferPrimitives.Reserve(Scene->Primitives.Num()); + + // 创建光源信息. + for(int32 LightIndex = 0;LightIndex < Scene->Lights.GetMaxIndex();LightIndex++) + { + if( LightIndex+2 < Scene->Lights.GetMaxIndex() ) + { + if (LightIndex > 2) + { + FLUSH_CACHE_LINE(&View.VisibleLightInfos(LightIndex-2)); + } + } + new(View.VisibleLightInfos) FVisibleLightViewInfo(); + } + + View.PrimitiveViewRelevanceMap.Empty(Scene->Primitives.Num()); + View.PrimitiveViewRelevanceMap.AddZeroed(Scene->Primitives.Num()); + + const bool bIsParent = ViewState && ViewState->IsViewParent(); + if ( bIsParent ) + { + ViewState->ParentPrimitives.Empty(); + } + + if (ViewState) + { + // 获取并解压上一帧的遮挡数据. + View.PrecomputedVisibilityData = ViewState->GetPrecomputedVisibilityData(View, Scene); + } + else + { + View.PrecomputedVisibilityData = NULL; + } + + if (View.PrecomputedVisibilityData) + { + bUsedPrecomputedVisibility = true; + } + + bool bNeedsFrustumCulling = true; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if( ViewState ) + { + // 冻结可见性 + if(ViewState->bIsFrozen) + { + bNeedsFrustumCulling = false; + for (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt) + { + if (ViewState->FrozenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()])) + { + BitIt.GetValue() = true; + } + } + } + } +#endif + + // 平截头体裁剪. + if (bNeedsFrustumCulling) + { + // Update HLOD transition/visibility states to allow use during distance culling + FLODSceneTree& HLODTree = Scene->SceneLODHierarchy; + if (HLODTree.IsActive()) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_HLODUpdate); + HLODTree.UpdateVisibilityStates(View); + } + else + { + HLODTree.ClearVisibilityState(View); + } + + int32 NumCulledPrimitivesForView; + const bool bUseFastIntersect = (View.ViewFrustum.PermutedPlanes.Num() == 8) && CVarUseFastIntersect.GetValueOnRenderThread(); + if (View.CustomVisibilityQuery && View.CustomVisibilityQuery->Prepare()) + { + if (CVarAlsoUseSphereForFrustumCull.GetValueOnRenderThread()) + { + NumCulledPrimitivesForView = bUseFastIntersect ? FrustumCull(Scene, View) : FrustumCull(Scene, View); + } + else + { + NumCulledPrimitivesForView = bUseFastIntersect ? FrustumCull(Scene, View) : FrustumCull(Scene, View); + } + } + else + { + if (CVarAlsoUseSphereForFrustumCull.GetValueOnRenderThread()) + { + NumCulledPrimitivesForView = bUseFastIntersect ? FrustumCull(Scene, View) : FrustumCull(Scene, View); + } + else + { + NumCulledPrimitivesForView = bUseFastIntersect ? FrustumCull(Scene, View) : FrustumCull(Scene, View); + } + } + STAT(NumCulledPrimitives += NumCulledPrimitivesForView); + UpdatePrimitiveFading(Scene, View); + } + + // 处理隐藏物体. + if (View.HiddenPrimitives.Num()) + { + for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt) + { + if (View.HiddenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()])) + { + View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false; + } + } + } + + (......) + + // 处理静态场景. + if (View.bStaticSceneOnly) + { + for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt) + { + // Reflection captures should only capture objects that won't move, since reflection captures won't update at runtime + if (!Scene->Primitives[BitIt.GetIndex()]->Proxy->HasStaticLighting()) + { + View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false; + } + } + } + + (......) + + // 非线框模式, 则只需遮挡剔除. + if (!View.Family->EngineShowFlags.Wireframe) + { + int32 NumOccludedPrimitivesInView = OcclusionCull(RHICmdList, Scene, View, DynamicVertexBuffer); + STAT(NumOccludedPrimitives += NumOccludedPrimitivesInView); + } + + // 处理判断可见性的静态模型. + { + TArray AddedSceneInfos; + for (TConstDualSetBitIterator BitIt(View.PrimitiveVisibilityMap, Scene->PrimitivesNeedingStaticMeshUpdate); BitIt; ++BitIt) + { + int32 PrimitiveIndex = BitIt.GetIndex(); + AddedSceneInfos.Add(Scene->Primitives[PrimitiveIndex]); + } + + if (AddedSceneInfos.Num() > 0) + { + FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, AddedSceneInfos); + } + } + + (......) + } + + (......) + + // 收集所有view的动态网格元素. 上一篇已经详细解析过了. + { + GatherDynamicMeshElements(Views, Scene, ViewFamily, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer, + HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks, HasViewCustomDataMasks, MeshCollector); + } + + // 创建每个view的MeshPass数据. + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + if (!View.ShouldRenderView()) + { + continue; + } + + FViewCommands& ViewCommands = ViewCommandsPerView[ViewIndex]; + SetupMeshPass(View, BasePassDepthStencilAccess, ViewCommands); + } +} +``` +`ComputeViewVisibility`最重要的功能就是处理图元的可见性,包含平截头体裁剪、遮挡剔除,以及收集动态网格信息、创建光源信息等。下面继续粗略剖预计算可见性的过程: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneOcclusion.cpp + +const uint8* FSceneViewState::GetPrecomputedVisibilityData(FViewInfo& View, const FScene* Scene) +{ + const uint8* PrecomputedVisibilityData = NULL; + if (Scene->PrecomputedVisibilityHandler && GAllowPrecomputedVisibility && View.Family->EngineShowFlags.PrecomputedVisibility) + { + const FPrecomputedVisibilityHandler& Handler = *Scene->PrecomputedVisibilityHandler; + FViewElementPDI VisibilityCellsPDI(&View, nullptr, nullptr); + + // 绘制调试用的遮挡剔除方格. + if ((GShowPrecomputedVisibilityCells || View.Family->EngineShowFlags.PrecomputedVisibilityCells) && !GShowRelevantPrecomputedVisibilityCells) + { + for (int32 BucketIndex = 0; BucketIndex < Handler.PrecomputedVisibilityCellBuckets.Num(); BucketIndex++) + { + for (int32 CellIndex = 0; CellIndex < Handler.PrecomputedVisibilityCellBuckets[BucketIndex].Cells.Num(); CellIndex++) + { + const FPrecomputedVisibilityCell& CurrentCell = Handler.PrecomputedVisibilityCellBuckets[BucketIndex].Cells[CellIndex]; + // Construct the cell's bounds + const FBox CellBounds(CurrentCell.Min, CurrentCell.Min + FVector(Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeZ)); + if (View.ViewFrustum.IntersectBox(CellBounds.GetCenter(), CellBounds.GetExtent())) + { + DrawWireBox(&VisibilityCellsPDI, CellBounds, FColor(50, 50, 255), SDPG_World); + } + } + } + } + + // 计算哈希值和桶索引, 以减少搜索时间. + const float FloatOffsetX = (View.ViewMatrices.GetViewOrigin().X - Handler.PrecomputedVisibilityCellBucketOriginXY.X) / Handler.PrecomputedVisibilityCellSizeXY; + // FMath::TruncToInt rounds toward 0, we want to always round down + const int32 BucketIndexX = FMath::Abs((FMath::TruncToInt(FloatOffsetX) - (FloatOffsetX < 0.0f ? 1 : 0)) / Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets); + const float FloatOffsetY = (View.ViewMatrices.GetViewOrigin().Y -Handler.PrecomputedVisibilityCellBucketOriginXY.Y) / Handler.PrecomputedVisibilityCellSizeXY; + const int32 BucketIndexY = FMath::Abs((FMath::TruncToInt(FloatOffsetY) - (FloatOffsetY < 0.0f ? 1 : 0)) / Handler.PrecomputedVisibilityCellBucketSizeXY % Handler.PrecomputedVisibilityNumCellBuckets); + const int32 PrecomputedVisibilityBucketIndex = BucketIndexY * Handler.PrecomputedVisibilityCellBucketSizeXY + BucketIndexX; + + // 绘制可见性物体对应的包围盒. + const FPrecomputedVisibilityBucket& CurrentBucket = Handler.PrecomputedVisibilityCellBuckets[PrecomputedVisibilityBucketIndex]; + for (int32 CellIndex = 0; CellIndex < CurrentBucket.Cells.Num(); CellIndex++) + { + const FPrecomputedVisibilityCell& CurrentCell = CurrentBucket.Cells[CellIndex]; + // 创建cell的包围盒. + const FBox CellBounds(CurrentCell.Min, CurrentCell.Min + FVector(Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeXY, Handler.PrecomputedVisibilityCellSizeZ)); + // Check if ViewOrigin is inside the current cell + if (CellBounds.IsInside(View.ViewMatrices.GetViewOrigin())) + { + // 检测是否可重复使用已缓存的数据. + if (CachedVisibilityChunk + && CachedVisibilityHandlerId == Scene->PrecomputedVisibilityHandler->GetId() + && CachedVisibilityBucketIndex == PrecomputedVisibilityBucketIndex + && CachedVisibilityChunkIndex == CurrentCell.ChunkIndex) + { + PrecomputedVisibilityData = &(*CachedVisibilityChunk)[CurrentCell.DataOffset]; + } + else + { + const FCompressedVisibilityChunk& CompressedChunk = Handler.PrecomputedVisibilityCellBuckets[PrecomputedVisibilityBucketIndex].CellDataChunks[CurrentCell.ChunkIndex]; + CachedVisibilityBucketIndex = PrecomputedVisibilityBucketIndex; + CachedVisibilityChunkIndex = CurrentCell.ChunkIndex; + CachedVisibilityHandlerId = Scene->PrecomputedVisibilityHandler->GetId(); + + // 解压遮挡数据. + if (CompressedChunk.bCompressed) + { + // Decompress the needed visibility data chunk + DecompressedVisibilityChunk.Reset(); + DecompressedVisibilityChunk.AddUninitialized(CompressedChunk.UncompressedSize); + verify(FCompression::UncompressMemory( + NAME_Zlib, + DecompressedVisibilityChunk.GetData(), + CompressedChunk.UncompressedSize, + CompressedChunk.Data.GetData(), + CompressedChunk.Data.Num())); + CachedVisibilityChunk = &DecompressedVisibilityChunk; + } + else + { + CachedVisibilityChunk = &CompressedChunk.Data; + } + + // Return a pointer to the cell containing ViewOrigin's decompressed visibility data + PrecomputedVisibilityData = &(*CachedVisibilityChunk)[CurrentCell.DataOffset]; + } + + (......) + } + + (......) + } + + return PrecomputedVisibilityData; +} +``` +从代码中得知,可见性判定可以绘制出一些调试信息,如每个物体实际用于剔除时的包围盒大小,也可以冻结(Frozen)剔除结果,以便观察遮挡的效率。 + +由于可见性判定包含预计算、平截头体裁剪、遮挡剔除等,单单遮挡剔除涉及的知识点比较多(绘制、获取、数据压缩解压、存储结构、多线程传递、帧间交互、可见性判定、HLOD等等),此处只对可见性判定做了粗略的浅析,更多详细的机制和过程将会在后续的渲染优化专题中深入剖析。 + +接下来继续分析`InitViews`内的`PostVisibilityFrameSetup`: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp + +void FSceneRenderer::PostVisibilityFrameSetup(FILCUpdatePrimTaskData& OutILCTaskData) +{ + // 处理贴花排序和调整历史RT. + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Sort); + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FViewInfo& View = Views[ViewIndex]; + + View.MeshDecalBatches.Sort(); + + if (View.State) + { + ((FSceneViewState*)View.State)->TrimHistoryRenderTargets(Scene); + } + } + } + + (......) + + // 处理光源可见性. + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Light_Visibility); + // 遍历所有光源, 结合view的视锥判断可见性(同一个光源可能在有的view可见但其它view不可见). + for(TSparseArray::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt) + { + const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt; + const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo; + + // 利用每个view内的镜头视锥裁剪光源. + for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++) + { + const FLightSceneProxy* Proxy = LightSceneInfo->Proxy; + FViewInfo& View = Views[ViewIndex]; + FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()]; + // 平行方向光不需要裁剪, 只有局部光源才需要裁剪 + if( Proxy->GetLightType() == LightType_Point || + Proxy->GetLightType() == LightType_Spot || + Proxy->GetLightType() == LightType_Rect ) + { + FSphere const& BoundingSphere = Proxy->GetBoundingSphere(); + // 判断view的视锥是否和光源包围盒相交. + if (View.ViewFrustum.IntersectSphere(BoundingSphere.Center, BoundingSphere.W)) + { + // 透视视锥需要针对最大距离做校正, 剔除距离视点太远的光源. + if (View.IsPerspectiveProjection()) + { + FSphere Bounds = Proxy->GetBoundingSphere(); + float DistanceSquared = (Bounds.Center - View.ViewMatrices.GetViewOrigin()).SizeSquared(); + float MaxDistSquared = Proxy->GetMaxDrawDistance() * Proxy->GetMaxDrawDistance() * GLightMaxDrawDistanceScale * GLightMaxDrawDistanceScale; + // 考量了光源的半径、视图的LOD因子、最小光源屏幕半径等因素来决定最终光源是否需要绘制,以便剔除掉远距离屏幕占比很小的光源。 + const bool bDrawLight = (FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / Bounds.W) * View.LODDistanceFactor) * DistanceSquared < 1.0f) + && (MaxDistSquared == 0 || DistanceSquared < MaxDistSquared); + + VisibleLightViewInfo.bInViewFrustum = bDrawLight; + } + else + { + VisibleLightViewInfo.bInViewFrustum = true; + } + } + } + else + { + // 平行方向光一定可见. + VisibleLightViewInfo.bInViewFrustum = true; + + (......) + } + + (......) +} +``` +在`InitViews`末期,会初始化每个view的RHI资源以及通知RHICommandList渲染开始事件。先看看`FViewInfo::InitRHIResources`: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneRendering.cpp + +void FViewInfo::InitRHIResources() +{ + FBox VolumeBounds[TVC_MAX]; + + // 创建和设置缓存的视图Uniform Buffer. + CachedViewUniformShaderParameters = MakeUnique(); + + FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(FRHICommandListExecutor::GetImmediateCommandList()); + + SetupUniformBufferParameters( + SceneContext, + VolumeBounds, + TVC_MAX, + *CachedViewUniformShaderParameters); + + // 创建和设置视图的Uniform Buffer. + ViewUniformBuffer = TUniformBufferRef::CreateUniformBufferImmediate(*CachedViewUniformShaderParameters, UniformBuffer_SingleFrame); + + const int32 TranslucencyLightingVolumeDim = GetTranslucencyLightingVolumeDim(); + + // 重置缓存的Uniform Buffer. + FScene* Scene = Family->Scene ? Family->Scene->GetRenderScene() : nullptr; + if (Scene) + { + Scene->UniformBuffers.InvalidateCachedView(); + } + + // 初始化透明体积光照参数. + for (int32 CascadeIndex = 0; CascadeIndex < TVC_MAX; CascadeIndex++) + { + TranslucencyLightingVolumeMin[CascadeIndex] = VolumeBounds[CascadeIndex].Min; + TranslucencyVolumeVoxelSize[CascadeIndex] = (VolumeBounds[CascadeIndex].Max.X - VolumeBounds[CascadeIndex].Min.X) / TranslucencyLightingVolumeDim; + TranslucencyLightingVolumeSize[CascadeIndex] = VolumeBounds[CascadeIndex].Max - VolumeBounds[CascadeIndex].Min; + } +} +``` +继续解析`InitViews`末尾的`OnStartRender`: +```c++ +// Engine\Source\Runtime\Renderer\Private\SceneRendering.cpp + +void FSceneRenderer::OnStartRender(FRHICommandListImmediate& RHICmdList) +{ + // 场景MRT的初始化. + FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList); + + FVisualizeTexturePresent::OnStartRender(Views[0]); + CompositionGraph_OnStartFrame(); + SceneContext.bScreenSpaceAOIsValid = false; + SceneContext.bCustomDepthIsValid = false; + + // 通知ViewState初始化. + for (FViewInfo& View : Views) + { + if (View.ViewState) + { + View.ViewState->OnStartRender(View, ViewFamily); + } + } +} + +// Engine\Source\Runtime\Renderer\Private\ScenePrivate.h + +void FSceneViewState::OnStartRender(FViewInfo& View, FSceneViewFamily& ViewFamily) +{ + // 初始化和设置光照传输体积. + if(!(View.FinalPostProcessSettings.IndirectLightingColor * View.FinalPostProcessSettings.IndirectLightingIntensity).IsAlmostBlack()) + { + SetupLightPropagationVolume(View, ViewFamily); + } + // 分配软件级场景遮挡剔除(针对不支持硬件遮挡剔除的低端机) + ConditionallyAllocateSceneSoftwareOcclusion(View.GetFeatureLevel()); +} +``` + +### PrePass +PrePass又被称为提前深度Pass、Depth Only Pass、Early-Z Pass,用来渲染不透明物体的深度。此Pass只会写入深度而不会写入颜色,写入深度时有disabled、occlusion only、complete depths三种模式,视不同的平台和Feature Level决定。 + +PrePass可以由DBuffer发起,也可由Forward Shading触发,通常用来建立Hierarchical-Z,以便能够开启硬件的Early-Z技术,还可被用于遮挡剔除,提升Base Pass的渲染效率。 \ No newline at end of file