Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/Lumen/LumenSceneCardCapture.cpp
2025-05-18 13:04:45 +08:00

892 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LumenSceneCardCapture.h"
#include "RendererPrivate.h"
#include "ScenePrivate.h"
#include "SceneUtils.h"
#include "NaniteSceneProxy.h"
#include "NaniteVertexFactory.h"
#include "../Nanite/NaniteShading.h"
#include "StaticMeshBatch.h"
#include "MeshPassProcessor.inl"
#include "MeshCardRepresentation.h"
#include "Materials/Material.h"
#include "Materials/MaterialRenderProxy.h"
#include "RenderUtils.h"
#include "MeshPassUtils.h"
static TAutoConsoleVariable<float> GLumenSceneSurfaceCacheMeshTargetScreenSize(
TEXT("r.LumenScene.SurfaceCache.MeshTargetScreenSize"),
0.15f,
TEXT("Controls which LOD level will be used to capture static meshes into surface cache."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
Lumen::DebugResetSurfaceCache();
}),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> GLumenSceneSurfaceCacheNaniteLODScaleFactor(
TEXT("r.LumenScene.SurfaceCache.NaniteLODScaleFactor"),
1.0f,
TEXT("Controls which LOD level will be used to capture Nanite meshes into surface cache."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
Lumen::DebugResetSurfaceCache();
}),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<float> GLumenSceneSurfaceCacheNaniteLandscapeLODScaleFactor(
TEXT("r.LumenScene.SurfaceCache.NaniteLandscapeLODScaleFactor"),
1.0f,
TEXT("Controls which LOD level will be used to capture Nanite landscape meshes into surface cache."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
Lumen::DebugResetSurfaceCache();
}),
ECVF_RenderThreadSafe | ECVF_Scalability);
namespace LumenCardCapture
{
constexpr int32 LandscapeLOD = 0;
};
// Called at runtime and during cook.
bool ShouldCompileLumenMeshCardShaders(EMaterialDomain Domain, EBlendMode BlendMode, const FVertexFactoryType* VertexFactoryType, EShaderPlatform Platform)
{
// We compile shader for opaque and translucent shaders for translucent refraction with hardware ray tracing and hit lighting
return Domain == MD_Surface
&& ShouldIncludeDomainInMeshPass(Domain)
&& (DoesProjectSupportLumenRayTracedTranslucentRefraction() || IsOpaqueOrMaskedBlendMode(BlendMode))
&& VertexFactoryType->SupportsLumenMeshCards()
&& DoesPlatformSupportLumenGI(Platform);
}
class FLumenCardVS : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(FLumenCardVS, MeshMaterial);
protected:
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
// Everything supporting Nanite through the FLumenCardCS. Need to allow Landscape here, as Lumen doesn't support Nanite Landscape yet.
if (Parameters.VertexFactoryType->SupportsNaniteRendering() && !Parameters.VertexFactoryType->SupportsLandscape())
{
return false;
}
return ShouldCompileLumenMeshCardShaders(Parameters.MaterialParameters.MaterialDomain, Parameters.MaterialParameters.BlendMode, Parameters.VertexFactoryType, Parameters.Platform);
}
FLumenCardVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{}
FLumenCardVS() = default;
};
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLumenCardVS, TEXT("/Engine/Private/Lumen/LumenCardVertexShader.usf"), TEXT("Main"), SF_Vertex);
class FLumenCardPS : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(FLumenCardPS, MeshMaterial);
public:
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
// Everything supporting Nanite through the FLumenCardCS. Need to allow Landscape here, as Lumen doesn't support Nanite Landscape yet.
if (Parameters.VertexFactoryType->SupportsNaniteRendering() && !Parameters.VertexFactoryType->SupportsLandscape())
{
return false;
}
return ShouldCompileLumenMeshCardShaders(Parameters.MaterialParameters.MaterialDomain, Parameters.MaterialParameters.BlendMode, Parameters.VertexFactoryType, Parameters.Platform);
}
FLumenCardPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{}
FLumenCardPS() = default;
static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSTRATE_INLINE_SHADING"), 1);
// Use fully simplified material for less complex shaders when multiple slabs are used.
OutEnvironment.SetDefine(TEXT("SUBSTRATE_USE_FULLYSIMPLIFIED_MATERIAL"), 1);
// Card should not be able to sample form the scene textures, this is needed for translucent materials card capture which can request the sampling of SceneTextures.
OutEnvironment.SetDefine(TEXT("SCENE_TEXTURES_DISABLED"), 1);
}
};
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLumenCardPS, TEXT("/Engine/Private/Lumen/LumenCardPixelShader.usf"), TEXT("Main"), SF_Pixel);
IMPLEMENT_UNIFORM_BUFFER_STRUCT_EX(FLumenCardOutputs, "LumenCardOutputs", FShaderParametersMetadata::EUsageFlags::ManuallyBoundByPass);
class FLumenCardCS : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(FLumenCardCS, MeshMaterial);
public:
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
if (!Parameters.VertexFactoryType->SupportsNaniteRendering())
{
return false;
}
if (!Parameters.VertexFactoryType->SupportsComputeShading())
{
return false;
}
return IsOpaqueOrMaskedBlendMode(Parameters.MaterialParameters.BlendMode)
&& ShouldCompileLumenMeshCardShaders(Parameters.MaterialParameters.MaterialDomain, Parameters.MaterialParameters.BlendMode, Parameters.VertexFactoryType, Parameters.Platform);
}
FLumenCardCS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
PassDataParam.Bind(Initializer.ParameterMap, TEXT("PassData"));
LumenCardOutputsParam.Bind(Initializer.ParameterMap, TEXT("LumenCardOutputs"), SPF_Mandatory);
}
FLumenCardCS() = default;
static void ModifyCompilationEnvironment(const FMeshMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SUBSTRATE_INLINE_SHADING"), 1);
// Use fully simplified material for less complex shaders when multiple slabs are used.
OutEnvironment.SetDefine(TEXT("SUBSTRATE_USE_FULLYSIMPLIFIED_MATERIAL"), 1);
// Card should not be able to sample from the scene textures, this is needed for translucent materials card capture which can request the sampling of SceneTextures.
OutEnvironment.SetDefine(TEXT("SCENE_TEXTURES_DISABLED"), 1);
// Force shader model 6.0+
OutEnvironment.CompilerFlags.Add(CFLAG_ForceDXC);
OutEnvironment.CompilerFlags.Add(CFLAG_HLSL2021);
OutEnvironment.CompilerFlags.Add(CFLAG_RootConstants);
OutEnvironment.CompilerFlags.Add(CFLAG_CheckForDerivativeOps);
OutEnvironment.CompilerFlags.Add(CFLAG_SupportsMinimalBindless);
}
static EShaderCompileJobPriority GetOverrideJobPriority()
{
// FLumenCardCS takes up to 12s on average
return EShaderCompileJobPriority::ExtraHigh;
}
void SetPassParameters(FRHIBatchedShaderParameters& BatchedParameters, const FUintVector4& PassData, FRHIUniformBuffer* Outputs)
{
SetShaderValue(BatchedParameters, PassDataParam, PassData);
SetUniformBufferParameter(BatchedParameters, LumenCardOutputsParam, Outputs);
}
private:
LAYOUT_FIELD(FShaderParameter, PassDataParam);
LAYOUT_FIELD(FShaderUniformBufferParameter, LumenCardOutputsParam);
};
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLumenCardCS, TEXT("/Engine/Private/Lumen/LumenCardComputeShader.usf"), TEXT("Main"), SF_Compute);
struct FNaniteLumenCardData
{
TShaderRef<FLumenCardCS> TypedShader;
};
namespace Nanite
{
void CollectLumenCardPSOInitializers(
const FSceneTexturesConfig& SceneTexturesConfig,
const FPSOPrecacheVertexFactoryData& VertexFactoryData,
const FMaterial& Material,
const FPSOPrecacheParams& PreCacheParams,
ERHIFeatureLevel::Type FeatureLevel,
EShaderPlatform ShaderPlatform,
int32 PSOCollectorIndex,
TArray<FPSOPrecacheData>& PSOInitializers)
{
FMaterialShaderTypes ShaderTypes;
ShaderTypes.AddShaderType<FLumenCardCS>();
FMaterialShaders Shaders;
if (!Material.TryGetShaders(ShaderTypes, VertexFactoryData.VertexFactoryType, Shaders))
{
return;
}
TShaderRef<FLumenCardCS> LumenCardComputeShader;
if (!Shaders.TryGetComputeShader(LumenCardComputeShader))
{
return;
}
FPSOPrecacheData ComputePSOPrecacheData;
ComputePSOPrecacheData.Type = FPSOPrecacheData::EType::Compute;
ComputePSOPrecacheData.SetComputeShader(LumenCardComputeShader);
#if PSO_PRECACHING_VALIDATE
ComputePSOPrecacheData.PSOCollectorIndex = PSOCollectorIndex;
ComputePSOPrecacheData.VertexFactoryType = VertexFactoryData.VertexFactoryType;
if (PSOCollectorStats::IsFullPrecachingValidationEnabled())
{
ComputePSOPrecacheData.bDefaultMaterial = Material.IsDefaultMaterial();
ConditionalBreakOnPSOPrecacheShader(ComputePSOPrecacheData.ComputeShader);
}
#endif // PSO_PRECACHING_VALIDATE
PSOInitializers.Add(MoveTemp(ComputePSOPrecacheData));
}
void RecordLumenCardParameters(
FRHIBatchedShaderParameters& ShaderParameters,
FNaniteShadingCommand& ShadingCommand,
TUniformBufferRef<FLumenCardOutputs> Outputs
)
{
FRHIComputeShader* ComputeShaderRHI = ShadingCommand.Pipeline->ComputeShader;
const bool bNoDerivativeOps = !!ShadingCommand.Pipeline->bNoDerivativeOps;
ShadingCommand.PassData.X = ShadingCommand.ShadingBin; // Active Shading Bin
ShadingCommand.PassData.Y = bNoDerivativeOps ? 0 /* Pixel Binning */ : 1 /* Quad Binning */;
ShadingCommand.PassData.Z = uint32(ENaniteMeshPass::LumenCardCapture);
ShadingCommand.PassData.W = 0; // Unused
ShadingCommand.Pipeline->ShaderBindings->SetParameters(ShaderParameters);
if (ComputeShaderRHI)
{
ShadingCommand.Pipeline->LumenCardData->TypedShader->SetPassParameters(
ShaderParameters,
ShadingCommand.PassData,
Outputs.GetReference()
);
}
}
bool LoadLumenCardPipeline(
const FScene& Scene,
FSceneProxyBase* SceneProxy,
FSceneProxyBase::FMaterialSection& Section,
FNaniteShadingPipeline& ShadingPipeline
)
{
const ERHIFeatureLevel::Type FeatureLevel = Scene.GetFeatureLevel();
FNaniteVertexFactory* NaniteVertexFactory = Nanite::GVertexFactoryResource.GetVertexFactory();
FVertexFactoryType* NaniteVertexFactoryType = NaniteVertexFactory->GetType();
const FMaterialRenderProxy* MaterialProxy = Section.ShadingMaterialProxy;
while (MaterialProxy)
{
const FMaterial* Material = MaterialProxy->GetMaterialNoFallback(FeatureLevel);
if (Material)
{
break;
}
MaterialProxy = MaterialProxy->GetFallback(FeatureLevel);
}
check(MaterialProxy);
TShaderRef<FLumenCardCS> LumenCardComputeShader;
auto LoadShadingMaterial = [&](const FMaterialRenderProxy* MaterialProxyPtr)
{
const FMaterial& ShadingMaterial = MaterialProxy->GetIncompleteMaterialWithFallback(FeatureLevel);
check(Nanite::IsSupportedMaterialDomain(ShadingMaterial.GetMaterialDomain()));
check(Nanite::IsSupportedBlendMode(ShadingMaterial));
const FMaterialShadingModelField ShadingModels = ShadingMaterial.GetShadingModels();
FMaterialShaderTypes ShaderTypes;
ShaderTypes.AddShaderType<FLumenCardCS>();
FMaterialShaders Shaders;
if (!ShadingMaterial.TryGetShaders(ShaderTypes, NaniteVertexFactoryType, Shaders))
{
return false;
}
return Shaders.TryGetComputeShader(LumenCardComputeShader);
};
bool bLoaded = LoadShadingMaterial(MaterialProxy);
if (!bLoaded)
{
MaterialProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
bLoaded = LoadShadingMaterial(MaterialProxy);
}
if (bLoaded)
{
ShadingPipeline.MaterialProxy = MaterialProxy;
ShadingPipeline.Material = MaterialProxy->GetMaterialNoFallback(FeatureLevel);
ShadingPipeline.BoundTargetMask = 0x0u; //LumenCardComputeShader->GetBoundTargetMask();
ShadingPipeline.ComputeShader = LumenCardComputeShader.GetComputeShader();
ShadingPipeline.bIsTwoSided = !!Section.MaterialRelevance.bTwoSided; // TODO: Force off?
ShadingPipeline.bIsMasked = !!Section.MaterialRelevance.bMasked; // TODO: Force off?
ShadingPipeline.bNoDerivativeOps = HasNoDerivativeOps(ShadingPipeline.ComputeShader);
ShadingPipeline.MaterialBitFlags = PackMaterialBitFlags(*ShadingPipeline.Material, ShadingPipeline.BoundTargetMask, ShadingPipeline.bNoDerivativeOps);
ShadingPipeline.LumenCardData = MakePimpl<FNaniteLumenCardData, EPimplPtrMode::DeepCopy>();
ShadingPipeline.LumenCardData->TypedShader = LumenCardComputeShader;
check(ShadingPipeline.ComputeShader);
ShadingPipeline.ShaderBindings = MakePimpl<FMeshDrawShaderBindings, EPimplPtrMode::DeepCopy>();
UE::MeshPassUtils::SetupComputeBindings(LumenCardComputeShader, &Scene, FeatureLevel, SceneProxy, *MaterialProxy, *ShadingPipeline.Material, *ShadingPipeline.ShaderBindings);
ShadingPipeline.ShaderBindingsHash = ShadingPipeline.ShaderBindings->GetDynamicInstancingHash();
}
return bLoaded;
}
} // Nanite
class FLumenCardMeshProcessor : public FSceneRenderingAllocatorObject<FLumenCardMeshProcessor>, public FMeshPassProcessor
{
public:
FLumenCardMeshProcessor(const FScene* Scene, ERHIFeatureLevel::Type FeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, const FMeshPassProcessorRenderState& InPassDrawRenderState, FMeshPassDrawListContext* InDrawListContext);
virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) override final;
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers) override final;
FMeshPassProcessorRenderState PassDrawRenderState;
};
bool GetLumenCardShaders(
const FMaterial& Material,
const FVertexFactoryType* VertexFactoryType,
TShaderRef<FLumenCardVS>& VertexShader,
TShaderRef<FLumenCardPS>& PixelShader)
{
FMaterialShaderTypes ShaderTypes;
ShaderTypes.AddShaderType<FLumenCardVS>();
ShaderTypes.AddShaderType<FLumenCardPS>();
FMaterialShaders Shaders;
if (!Material.TryGetShaders(ShaderTypes, VertexFactoryType, Shaders))
{
return false;
}
Shaders.TryGetVertexShader(VertexShader);
Shaders.TryGetPixelShader(PixelShader);
return true;
}
void FLumenCardMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId)
{
LLM_SCOPE_BYTAG(Lumen);
EShaderPlatform Platform = GetFeatureLevelShaderPlatform(FeatureLevel);
if ((MeshBatch.bUseForMaterial || MeshBatch.bUseForLumenSurfaceCacheCapture)
&& DoesPlatformSupportLumenGI(Platform)
&& LumenDiffuseIndirect::IsAllowed()
&& (PrimitiveSceneProxy && PrimitiveSceneProxy->ShouldRenderInMainPass() && PrimitiveSceneProxy->AffectsDynamicIndirectLighting()))
{
const FMaterialRenderProxy* MaterialRenderProxy = MeshBatch.MaterialRenderProxy;
while (MaterialRenderProxy)
{
const FMaterial* Material = MaterialRenderProxy->GetMaterialNoFallback(FeatureLevel);
if (Material)
{
auto TryAddMeshBatch = [this, Platform](const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId, const FMaterialRenderProxy& MaterialRenderProxy, const FMaterial& Material) -> bool
{
const FMaterialShadingModelField ShadingModels = Material.GetShadingModels();
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(Material, OverrideSettings);
const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;
if (ShouldCompileLumenMeshCardShaders(Material.GetMaterialDomain(), Material.GetBlendMode(), VertexFactory->GetType(), Platform))
{
FVertexFactoryType* VertexFactoryType = VertexFactory->GetType();
TMeshProcessorShaders<FLumenCardVS, FLumenCardPS> PassShaders;
if (!GetLumenCardShaders(
Material,
VertexFactory->GetType(),
PassShaders.VertexShader,
PassShaders.PixelShader))
{
return false;
}
FMeshMaterialShaderElementData ShaderElementData;
ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, false);
const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(PassShaders.VertexShader, PassShaders.PixelShader);
BuildMeshDrawCommands(
MeshBatch,
BatchElementMask,
PrimitiveSceneProxy,
MaterialRenderProxy,
Material,
PassDrawRenderState,
PassShaders,
MeshFillMode,
MeshCullMode,
SortKey,
EMeshPassFeatures::Default,
ShaderElementData);
}
return true;
};
if (TryAddMeshBatch(MeshBatch, BatchElementMask, PrimitiveSceneProxy, StaticMeshId, *MaterialRenderProxy, *Material))
{
break;
}
};
MaterialRenderProxy = MaterialRenderProxy->GetFallback(FeatureLevel);
}
}
}
void SetupCardCaptureRenderTargetsInfo(FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo, EShaderPlatform ShaderPlatform)
{
RenderTargetsInfo.NumSamples = 1;
RenderTargetsInfo.RenderTargetsEnabled = 3;
// Albedo
RenderTargetsInfo.RenderTargetFormats[0] = PF_R8G8B8A8;
RenderTargetsInfo.RenderTargetFlags[0] = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_NoFastClear;
// Normal
RenderTargetsInfo.RenderTargetFormats[1] = PF_R8G8B8A8;
RenderTargetsInfo.RenderTargetFlags[1] = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_NoFastClear;
// Emissive
RenderTargetsInfo.RenderTargetFormats[2] = PF_FloatR11G11B10;
RenderTargetsInfo.RenderTargetFlags[2] = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_NoFastClear;
if (DoesPlatformSupportNanite(ShaderPlatform, true))
{
for (uint32 TargetIndex = 0; TargetIndex < RenderTargetsInfo.RenderTargetsEnabled; ++TargetIndex)
{
RenderTargetsInfo.RenderTargetFlags[TargetIndex] |= TexCreate_UAV;
}
}
if (GetSurfaceCacheCompression() == ESurfaceCacheCompression::FramebufferCompression)
{
for (uint32 i = 0; i < RenderTargetsInfo.RenderTargetsEnabled; ++i)
{
if (UE::PixelFormat::HasCapabilities(static_cast<EPixelFormat>(RenderTargetsInfo.RenderTargetFormats[i]), EPixelFormatCapabilities::LossyCompressible))
{
RenderTargetsInfo.RenderTargetFlags[i] |= TexCreate_LossyCompression;
}
}
}
// Setup depth stencil state
RenderTargetsInfo.DepthStencilTargetFormat = PF_DepthStencil;
RenderTargetsInfo.DepthStencilTargetFlag = TexCreate_ShaderResource | TexCreate_DepthStencilTargetable | TexCreate_NoFastClear;
// See setup of FDeferredShadingSceneRenderer::UpdateLumenScene (needs to be shared)
RenderTargetsInfo.DepthTargetLoadAction = ERenderTargetLoadAction::ELoad;
RenderTargetsInfo.StencilTargetLoadAction = ERenderTargetLoadAction::ENoAction;
RenderTargetsInfo.DepthStencilAccess = FExclusiveDepthStencil::DepthWrite_StencilNop;
// Derive store actions
const ERenderTargetStoreAction StoreAction = EnumHasAnyFlags(RenderTargetsInfo.DepthStencilTargetFlag, TexCreate_Memoryless) ? ERenderTargetStoreAction::ENoAction : ERenderTargetStoreAction::EStore;
RenderTargetsInfo.DepthTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingDepth() ? StoreAction : ERenderTargetStoreAction::ENoAction;
RenderTargetsInfo.StencilTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingStencil() ? StoreAction : ERenderTargetStoreAction::ENoAction;
}
void LumenScene::AllocateCardCaptureAtlas(
FRDGBuilder& GraphBuilder,
FIntPoint CardCaptureAtlasSize,
FCardCaptureAtlas& CardCaptureAtlas,
EShaderPlatform ShaderPlatform
)
{
// Collect info from SetupCardCaptureRenderTargetsInfo
FGraphicsPipelineRenderTargetsInfo RenderTargetsInfo;
SetupCardCaptureRenderTargetsInfo(RenderTargetsInfo, ShaderPlatform);
check(RenderTargetsInfo.RenderTargetsEnabled == 3);
CardCaptureAtlas.Size = CardCaptureAtlasSize;
CardCaptureAtlas.Albedo = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
CardCaptureAtlasSize,
(EPixelFormat)RenderTargetsInfo.RenderTargetFormats[0],
FClearValueBinding::Black,
RenderTargetsInfo.RenderTargetFlags[0]),
TEXT("Lumen.CardCaptureAlbedoAtlas"));
CardCaptureAtlas.Normal = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
CardCaptureAtlasSize,
(EPixelFormat)RenderTargetsInfo.RenderTargetFormats[1],
FClearValueBinding::Black,
RenderTargetsInfo.RenderTargetFlags[1]),
TEXT("Lumen.CardCaptureNormalAtlas"));
CardCaptureAtlas.Emissive = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
CardCaptureAtlasSize,
(EPixelFormat)RenderTargetsInfo.RenderTargetFormats[2],
FClearValueBinding::Black,
RenderTargetsInfo.RenderTargetFlags[2]),
TEXT("Lumen.CardCaptureEmissiveAtlas"));
CardCaptureAtlas.DepthStencil = GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(
CardCaptureAtlasSize,
PF_DepthStencil,
FClearValueBinding::DepthZero,
RenderTargetsInfo.DepthStencilTargetFlag),
TEXT("Lumen.CardCaptureDepthStencilAtlas"));
}
void FLumenCardMeshProcessor::CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers)
{
LLM_SCOPE_BYTAG(Lumen);
EShaderPlatform Platform = GetFeatureLevelShaderPlatform(FeatureLevel);
if (!PreCacheParams.bRenderInMainPass || !PreCacheParams.bAffectDynamicIndirectLighting ||
!Lumen::ShouldPrecachePSOs(Platform))
{
return;
}
const FMaterialShadingModelField ShadingModels = Material.GetShadingModels();
const bool bIsTranslucent = IsTranslucentBlendMode(Material);
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(PreCacheParams);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(Material, OverrideSettings);
if (ShouldCompileLumenMeshCardShaders(Material.GetMaterialDomain(), Material.GetBlendMode(), VertexFactoryData.VertexFactoryType, Platform))
{
TMeshProcessorShaders<FLumenCardVS, FLumenCardPS> PassShaders;
if (!GetLumenCardShaders(
Material,
VertexFactoryData.VertexFactoryType,
PassShaders.VertexShader,
PassShaders.PixelShader))
{
return;
}
FGraphicsPipelineRenderTargetsInfo RenderTargetsInfo;
SetupCardCaptureRenderTargetsInfo(RenderTargetsInfo, Platform);
AddGraphicsPipelineStateInitializer(
VertexFactoryData,
Material,
PassDrawRenderState,
RenderTargetsInfo,
PassShaders,
MeshFillMode,
MeshCullMode,
(EPrimitiveType)PreCacheParams.PrimitiveType,
EMeshPassFeatures::Default,
true /*bRequired*/,
PSOInitializers);
}
}
FLumenCardMeshProcessor::FLumenCardMeshProcessor(const FScene* Scene, ERHIFeatureLevel::Type FeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, const FMeshPassProcessorRenderState& InPassDrawRenderState, FMeshPassDrawListContext* InDrawListContext)
: FMeshPassProcessor(EMeshPass::LumenCardCapture, Scene, FeatureLevel, InViewIfDynamicMeshCommand, InDrawListContext)
, PassDrawRenderState(InPassDrawRenderState)
{}
FMeshPassProcessor* CreateLumenCardCapturePassProcessor(ERHIFeatureLevel::Type FeatureLevel, const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
{
LLM_SCOPE_BYTAG(Lumen);
FMeshPassProcessorRenderState PassState;
// Write and test against depth
PassState.SetDepthStencilState(TStaticDepthStencilState<true, CF_Greater>::GetRHI());
PassState.SetBlendState(TStaticBlendState<>::GetRHI());
return new FLumenCardMeshProcessor(Scene, FeatureLevel, InViewIfDynamicMeshCommand, PassState, InDrawListContext);
}
REGISTER_MESHPASSPROCESSOR_AND_PSOCOLLECTOR(LumenCardCapturePass, CreateLumenCardCapturePassProcessor, EShadingPath::Deferred, EMeshPass::LumenCardCapture, EMeshPassFlags::CachedMeshCommands);
FCardPageRenderData::FCardPageRenderData(
const FViewInfo& InMainView,
const FLumenCard& InLumenCard,
FVector4f InCardUVRect,
FIntRect InCardCaptureAtlasRect,
FIntRect InSurfaceCacheAtlasRect,
int32 InPrimitiveGroupIndex,
int32 InCardIndex,
int32 InPageTableIndex,
bool bInResampleLastLighting,
bool bInAxisXFlipped,
int32 InCopyCardIndex)
: PrimitiveGroupIndex(InPrimitiveGroupIndex)
, CardIndex(InCardIndex)
, PageTableIndex(InPageTableIndex)
, CardUVRect(InCardUVRect)
, CardCaptureAtlasRect(InCardCaptureAtlasRect)
, SurfaceCacheAtlasRect(InSurfaceCacheAtlasRect)
, CardWorldOBB(InLumenCard.WorldOBB)
, bHeightField(InLumenCard.bHeightfield)
, bResampleLastLighting(bInResampleLastLighting)
, DilationMode(InLumenCard.DilationMode)
, bAxisXFlipped(bInAxisXFlipped)
, CopyCardIndex(InCopyCardIndex)
{
ensure(CardIndex >= 0 && PageTableIndex >= 0);
NaniteLODScaleFactor = InLumenCard.bHeightfield ?
GLumenSceneSurfaceCacheNaniteLandscapeLODScaleFactor.GetValueOnRenderThread() :
GLumenSceneSurfaceCacheNaniteLODScaleFactor.GetValueOnRenderThread();
UpdateViewMatrices(InMainView);
}
FCardPageRenderData::~FCardPageRenderData() = default;
void FCardPageRenderData::UpdateViewMatrices(const FViewInfo& MainView)
{
ensureMsgf(FVector::DotProduct(CardWorldOBB.AxisX, FVector::CrossProduct(CardWorldOBB.AxisY, CardWorldOBB.AxisZ)) < 0.0f, TEXT("Card has wrong handedness"));
FMatrix ViewRotationMatrix = FMatrix::Identity;
ViewRotationMatrix.SetColumn(0, CardWorldOBB.AxisX);
ViewRotationMatrix.SetColumn(1, CardWorldOBB.AxisY);
ViewRotationMatrix.SetColumn(2, -CardWorldOBB.AxisZ);
FVector ViewLocation(CardWorldOBB.Origin);
FVector FaceLocalExtent(CardWorldOBB.Extent);
// Pull the view location back so the entire box is in front of the near plane
ViewLocation += FVector(FaceLocalExtent.Z * CardWorldOBB.AxisZ);
const float NearPlane = 0.0f;
const float FarPlane = FaceLocalExtent.Z * 2.0f;
const float ZScale = 1.0f / (FarPlane - NearPlane);
const float ZOffset = -NearPlane;
const FVector4f ProjectionRect = FVector4f(2.0f, 2.0f, 2.0f, 2.0f) * CardUVRect - FVector4f(1.0f, 1.0f, 1.0f, 1.0f);
const float ProjectionL = ProjectionRect.X * 0.5f * FaceLocalExtent.X;
const float ProjectionR = ProjectionRect.Z * 0.5f * FaceLocalExtent.X;
const float ProjectionB = -ProjectionRect.W * 0.5f * FaceLocalExtent.Y;
const float ProjectionT = -ProjectionRect.Y * 0.5f * FaceLocalExtent.Y;
const FMatrix ProjectionMatrix = FReversedZOrthoMatrix(
ProjectionL,
ProjectionR,
ProjectionB,
ProjectionT,
ZScale,
ZOffset);
ProjectionMatrixUnadjustedForRHI = ProjectionMatrix;
FViewMatrices::FMinimalInitializer Initializer;
Initializer.ViewRotationMatrix = ViewRotationMatrix;
Initializer.ViewOrigin = ViewLocation;
Initializer.ProjectionMatrix = ProjectionMatrix;
Initializer.ConstrainedViewRect = MainView.SceneViewInitOptions.GetConstrainedViewRect();
Initializer.StereoPass = MainView.SceneViewInitOptions.StereoPass;
ViewMatrices = FViewMatrices(Initializer);
}
void FCardPageRenderData::PatchView(const FScene* Scene, FViewInfo* View) const
{
View->ProjectionMatrixUnadjustedForRHI = ProjectionMatrixUnadjustedForRHI;
View->ViewMatrices = ViewMatrices;
View->ViewRect = CardCaptureAtlasRect;
FBox VolumeBounds[TVC_MAX];
View->SetupUniformBufferParameters(
VolumeBounds,
TVC_MAX,
*View->CachedViewUniformShaderParameters);
View->CachedViewUniformShaderParameters->NearPlane = 0;
View->CachedViewUniformShaderParameters->FarShadowStaticMeshLODBias = 0;
}
void LumenScene::AddCardCaptureDraws(
const FScene* Scene,
FCardPageRenderData& CardPageRenderData,
const FLumenPrimitiveGroup& PrimitiveGroup,
TConstArrayView<const FPrimitiveSceneInfo*> SceneInfoPrimitives,
FMeshCommandOneFrameArray& VisibleMeshCommands,
TArray<int32, SceneRenderingAllocator>& PrimitiveIds)
{
LLM_SCOPE_BYTAG(Lumen);
const EMeshPass::Type MeshPass = EMeshPass::LumenCardCapture;
const ENaniteMeshPass::Type NaniteMeshPass = ENaniteMeshPass::LumenCardCapture;
const FBox WorldSpaceCardBox = CardPageRenderData.CardWorldOBB.GetBox();
uint32 MaxVisibleMeshDrawCommands = 0;
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfoPrimitives)
{
if (PrimitiveSceneInfo
&& PrimitiveSceneInfo->Proxy->AffectsDynamicIndirectLighting()
&& WorldSpaceCardBox.Intersect(PrimitiveSceneInfo->Proxy->GetBounds().GetBox())
&& !PrimitiveSceneInfo->Proxy->IsNaniteMesh())
{
MaxVisibleMeshDrawCommands += PrimitiveSceneInfo->StaticMeshRelevances.Num();
}
}
CardPageRenderData.InstanceRuns.Reserve(2 * MaxVisibleMeshDrawCommands);
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfoPrimitives)
{
if (PrimitiveSceneInfo
&& PrimitiveSceneInfo->Proxy->AffectsDynamicIndirectLighting()
&& WorldSpaceCardBox.Intersect(PrimitiveSceneInfo->Proxy->GetBounds().GetBox()))
{
if (PrimitiveGroup.bHeightfield)
{
// Capture using the Nanite proxy if there is matching one
FPrimitiveSceneInfo const* const* Found = Scene->LandscapeToNaniteProxyMap.Find(PrimitiveSceneInfo->PrimitiveComponentId);
if (Found)
{
PrimitiveSceneInfo = *Found;
}
}
if (PrimitiveSceneInfo->Proxy->IsNaniteMesh())
{
if (PrimitiveGroup.PrimitiveInstanceIndex >= 0)
{
CardPageRenderData.NaniteInstanceIds.Add(PrimitiveSceneInfo->GetInstanceSceneDataOffset() + PrimitiveGroup.PrimitiveInstanceIndex);
}
else
{
// Render all instances
const int32 NumInstances = PrimitiveSceneInfo->GetNumInstanceSceneDataEntries();
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
CardPageRenderData.NaniteInstanceIds.Add(PrimitiveSceneInfo->GetInstanceSceneDataOffset() + InstanceIndex);
}
}
for (const FNaniteShadingBin& ShadingBin : PrimitiveSceneInfo->NaniteShadingBins[ENaniteMeshPass::LumenCardCapture])
{
CardPageRenderData.NaniteShadingBins.Add(ShadingBin);
}
}
else
{
int32 LODToRender = 0;
if (PrimitiveGroup.bHeightfield)
{
// Landscape can't use last LOD, as it's a single quad with only 4 distinct heightfield values
// Also selected LOD needs to to match FLandscapeSectionLODUniformParameters uniform buffers
LODToRender = LumenCardCapture::LandscapeLOD;
}
else
{
const float TargetScreenSize = GLumenSceneSurfaceCacheMeshTargetScreenSize.GetValueOnRenderThread();
int32 PrevLODToRender = INT_MAX;
int32 NextLODToRender = -1;
for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshRelevances.Num(); ++MeshIndex)
{
const FStaticMeshBatchRelevance& Mesh = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
if (Mesh.ScreenSize >= TargetScreenSize)
{
NextLODToRender = FMath::Max(NextLODToRender, (int32)Mesh.GetLODIndex());
}
else
{
PrevLODToRender = FMath::Min(PrevLODToRender, (int32)Mesh.GetLODIndex());
}
}
LODToRender = NextLODToRender >= 0 ? NextLODToRender : PrevLODToRender;
const int32 CurFirstLODIdx = (int32)PrimitiveSceneInfo->Proxy->GetCurrentFirstLODIdx_RenderThread();
LODToRender = FMath::Max(LODToRender, CurFirstLODIdx);
}
const FMeshDrawCommandPrimitiveIdInfo IdInfo = PrimitiveSceneInfo->GetMDCIdInfo();
for (int32 MeshIndex = 0; MeshIndex < PrimitiveSceneInfo->StaticMeshRelevances.Num(); MeshIndex++)
{
const FStaticMeshBatchRelevance& StaticMeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
const FStaticMeshBatch& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
bool bBuildMeshDrawCommands = (PrimitiveGroup.bHeightfield ? StaticMeshRelevance.bUseForLumenSceneCapture : StaticMeshRelevance.bUseForMaterial) && StaticMeshRelevance.GetLODIndex() == LODToRender;
if (bBuildMeshDrawCommands)
{
const int32 StaticMeshCommandInfoIndex = StaticMeshRelevance.GetStaticMeshCommandInfoIndex(MeshPass);
if (StaticMeshCommandInfoIndex >= 0)
{
const FCachedMeshDrawCommandInfo& CachedMeshDrawCommand = PrimitiveSceneInfo->StaticMeshCommandInfos[StaticMeshCommandInfoIndex];
const FCachedPassMeshDrawList& SceneDrawList = Scene->CachedDrawLists[MeshPass];
const FMeshDrawCommand* MeshDrawCommand = nullptr;
if (CachedMeshDrawCommand.StateBucketId >= 0)
{
MeshDrawCommand = &Scene->CachedMeshDrawCommandStateBuckets[MeshPass].GetByElementId(CachedMeshDrawCommand.StateBucketId).Key;
}
else
{
MeshDrawCommand = &SceneDrawList.MeshDrawCommands[CachedMeshDrawCommand.CommandIndex];
}
const uint32* InstanceRunArray = nullptr;
uint32 NumInstanceRuns = 0;
if (MeshDrawCommand->NumInstances > 1 && PrimitiveGroup.PrimitiveInstanceIndex >= 0)
{
// Render only a single specified instance, by specifying an inclusive [x;x] range
ensure(CardPageRenderData.InstanceRuns.Num() + 2 <= CardPageRenderData.InstanceRuns.Max());
InstanceRunArray = CardPageRenderData.InstanceRuns.GetData() + CardPageRenderData.InstanceRuns.Num();
NumInstanceRuns = 1;
CardPageRenderData.InstanceRuns.Add(PrimitiveGroup.PrimitiveInstanceIndex);
CardPageRenderData.InstanceRuns.Add(PrimitiveGroup.PrimitiveInstanceIndex);
}
FVisibleMeshDrawCommand NewVisibleMeshDrawCommand;
NewVisibleMeshDrawCommand.Setup(
MeshDrawCommand,
IdInfo,
CachedMeshDrawCommand.StateBucketId,
CachedMeshDrawCommand.MeshFillMode,
CachedMeshDrawCommand.MeshCullMode,
CachedMeshDrawCommand.Flags,
CachedMeshDrawCommand.SortKey,
CachedMeshDrawCommand.CullingPayload,
EMeshDrawCommandCullingPayloadFlags::NoScreenSizeCull,
InstanceRunArray,
NumInstanceRuns);
VisibleMeshCommands.Add(NewVisibleMeshDrawCommand);
PrimitiveIds.Add(PrimitiveSceneInfo->GetIndex());
}
}
}
}
}
}
}