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

1000 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DistanceFieldAmbientOcclusion.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "DeferredShadingRenderer.h"
#include "PostProcess/PostProcessing.h"
#include "PostProcess/SceneFilterRendering.h"
#include "DistanceFieldLightingShared.h"
#include "ScreenRendering.h"
#include "DistanceFieldLightingPost.h"
#include "OneColorShader.h"
#include "GlobalDistanceField.h"
#include "FXSystem.h"
#include "RendererModule.h"
#include "PipelineStateCache.h"
#include "VisualizeTexture.h"
#include "RayTracing/RaytracingOptions.h"
#include "Lumen/Lumen.h"
#include "MegaLights/MegaLights.h"
#include "ScenePrivate.h"
#include "SceneProxies/SkyLightSceneProxy.h"
#include "Substrate/Substrate.h"
DEFINE_LOG_CATEGORY(LogDistanceField);
int32 GDistanceFieldAO = 1;
FAutoConsoleVariableRef CVarDistanceFieldAO(
TEXT("r.DistanceFieldAO"),
GDistanceFieldAO,
TEXT("Whether the distance field AO feature is allowed, which is used to implement shadows of Movable sky lights from static meshes."),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GMobileDistanceFieldAO = 0;
FAutoConsoleVariableRef CVarMobileDistanceFieldAO(
TEXT("r.Mobile.DistanceFieldAO"),
GMobileDistanceFieldAO,
TEXT("Whether the distance field AO feature is allowed for Mobile Platforms, which is used to implement shadows of Movable sky lights from static meshes."),
ECVF_ReadOnly | ECVF_RenderThreadSafe
);
int32 GDistanceFieldAOMultiView = 1;
FAutoConsoleVariableRef CVarDistanceFieldAOMultiView(
TEXT("r.DistanceFieldAO.MultiView"),
GDistanceFieldAOMultiView,
TEXT("Whether the distance field AO feature is allowed when rendering multiple views."),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GDistanceFieldAOQuality = 2;
FAutoConsoleVariableRef CVarDistanceFieldAOQuality(
TEXT("r.AOQuality"),
GDistanceFieldAOQuality,
TEXT("Defines the distance field AO method which allows to adjust for quality or performance.\n")
TEXT(" 0:off, 1:medium, 2:high (default)"),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GDistanceFieldAOApplyToStaticIndirect = 0;
FAutoConsoleVariableRef CVarDistanceFieldAOApplyToStaticIndirect(
TEXT("r.AOApplyToStaticIndirect"),
GDistanceFieldAOApplyToStaticIndirect,
TEXT("Whether to apply DFAO as indirect shadowing even to static indirect sources (lightmaps + stationary skylight + reflection captures)"),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GDistanceFieldAOSpecularOcclusionMode = 1;
FAutoConsoleVariableRef CVarDistanceFieldAOSpecularOcclusionMode(
TEXT("r.AOSpecularOcclusionMode"),
GDistanceFieldAOSpecularOcclusionMode,
TEXT("Determines how specular should be occluded by DFAO\n")
TEXT("0: Apply non-directional AO to specular.\n")
TEXT("1: (default) Intersect the reflection cone with the unoccluded cone produced by DFAO. This gives more accurate occlusion than 0, but can bring out DFAO sampling artifacts.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe
);
float GAOStepExponentScale = .5f;
FAutoConsoleVariableRef CVarAOStepExponentScale(
TEXT("r.AOStepExponentScale"),
GAOStepExponentScale,
TEXT("Exponent used to distribute AO samples along a cone direction."),
ECVF_RenderThreadSafe
);
float GAOMaxViewDistance = 20000;
FAutoConsoleVariableRef CVarAOMaxViewDistance(
TEXT("r.AOMaxViewDistance"),
GAOMaxViewDistance,
TEXT("The maximum distance that AO will be computed at."),
ECVF_RenderThreadSafe
);
int32 GAOComputeShaderNormalCalculation = 0;
FAutoConsoleVariableRef CVarAOComputeShaderNormalCalculation(
TEXT("r.AOComputeShaderNormalCalculation"),
GAOComputeShaderNormalCalculation,
TEXT("Whether to use the compute shader version of the distance field normal computation."),
ECVF_RenderThreadSafe
);
int32 GAOSampleSet = 1;
FAutoConsoleVariableRef CVarAOSampleSet(
TEXT("r.AOSampleSet"),
GAOSampleSet,
TEXT("0 = Original set, 1 = Relaxed set"),
ECVF_RenderThreadSafe
);
int32 GAOOverwriteSceneColor = 0;
FAutoConsoleVariableRef CVarAOOverwriteSceneColor(
TEXT("r.AOOverwriteSceneColor"),
GAOOverwriteSceneColor,
TEXT(""),
ECVF_RenderThreadSafe
);
int32 GAOJitterConeDirections = 0;
FAutoConsoleVariableRef CVarAOJitterConeDirections(
TEXT("r.AOJitterConeDirections"),
GAOJitterConeDirections,
TEXT(""),
ECVF_RenderThreadSafe
);
int32 GAOObjectDistanceField = 1;
FAutoConsoleVariableRef CVarAOObjectDistanceField(
TEXT("r.AOObjectDistanceField"),
GAOObjectDistanceField,
TEXT("Determines whether object distance fields are used to compute ambient occlusion.\n")
TEXT("Only global distance field will be used when this option is disabled.\n"),
ECVF_RenderThreadSafe
);
int32 GAOGlobalDistanceFieldDetailedNecessityCheck = 1;
FAutoConsoleVariableRef CVarAOGlobalDistanceFieldDetailedCheck(
TEXT("r.AOGlobalDistanceField.DetailedNecessityCheck"),
GAOGlobalDistanceFieldDetailedNecessityCheck,
TEXT("Whether to perform detailed necessity check in FSceneRenderer::ShouldPrepareGlobalDistanceField()."),
ECVF_RenderThreadSafe
);
bool UseDistanceFieldAO()
{
return GDistanceFieldAO && GDistanceFieldAOQuality >= 1;
}
bool UseAOObjectDistanceField()
{
return GAOObjectDistanceField && GDistanceFieldAOQuality >= 2;
}
int32 GDistanceFieldAOTileSizeX = 16;
int32 GDistanceFieldAOTileSizeY = 16;
FDistanceFieldAOParameters::FDistanceFieldAOParameters(float InOcclusionMaxDistance, float InContrast)
{
Contrast = FMath::Clamp(InContrast, .01f, 2.0f);
InOcclusionMaxDistance = FMath::Clamp(InOcclusionMaxDistance, 2.0f, 3000.0f);
if (GAOGlobalDistanceField != 0)
{
extern float GAOGlobalDFStartDistance;
ObjectMaxOcclusionDistance = FMath::Min(InOcclusionMaxDistance, GAOGlobalDFStartDistance);
GlobalMaxOcclusionDistance = InOcclusionMaxDistance >= GAOGlobalDFStartDistance ? InOcclusionMaxDistance : 0;
}
else
{
ObjectMaxOcclusionDistance = InOcclusionMaxDistance;
GlobalMaxOcclusionDistance = 0;
}
}
void TileIntersectionModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
{
OutEnvironment.SetDefine(TEXT("CULLED_TILE_DATA_STRIDE"), CulledTileDataStride);
OutEnvironment.SetDefine(TEXT("CULLED_TILE_SIZEX"), GDistanceFieldAOTileSizeX);
extern int32 GConeTraceDownsampleFactor;
OutEnvironment.SetDefine(TEXT("TRACE_DOWNSAMPLE_FACTOR"), GConeTraceDownsampleFactor);
OutEnvironment.SetDefine(TEXT("CONE_TRACE_OBJECTS_THREADGROUP_SIZE"), ConeTraceObjectsThreadGroupSize);
}
FIntPoint GetBufferSizeForAO(const FViewInfo& View)
{
return FIntPoint::DivideAndRoundDown(View.GetSceneTexturesConfig().Extent, GAODownsampleFactor);
}
// Sample set restricted to not self-intersect a surface based on cone angle .475882232
// Coverage of hemisphere = 0.755312979
const FVector SpacedVectors9[] =
{
FVector(-0.573257625, 0.625250816, 0.529563010),
FVector(0.253354192, -0.840093017, 0.479640961),
FVector(-0.421664953, -0.718063235, 0.553700149),
FVector(0.249163717, 0.796005428, 0.551627457),
FVector(0.375082791, 0.295851320, 0.878512800),
FVector(-0.217619032, 0.00193520682, 0.976031899),
FVector(-0.852834642, 0.0111727007, 0.522061586),
FVector(0.745701790, 0.239393353, 0.621787369),
FVector(-0.151036426, -0.465937436, 0.871831656)
};
// Generated from SpacedVectors9 by applying repulsion forces until convergence
const FVector RelaxedSpacedVectors9[] =
{
FVector(-0.467612, 0.739424, 0.484347),
FVector(0.517459, -0.705440, 0.484346),
FVector(-0.419848, -0.767551, 0.484347),
FVector(0.343077, 0.804802, 0.484347),
FVector(0.364239, 0.244290, 0.898695),
FVector(-0.381547, 0.185815, 0.905481),
FVector(-0.870176, -0.090559, 0.484347),
FVector(0.874448, 0.027390, 0.484346),
FVector(0.032967, -0.435625, 0.899524)
};
float TemporalHalton2( int32 Index, int32 Base )
{
float Result = 0.0f;
float InvBase = 1.0f / Base;
float Fraction = InvBase;
while( Index > 0 )
{
Result += ( Index % Base ) * Fraction;
Index /= Base;
Fraction *= InvBase;
}
return Result;
}
void GetSpacedVectors(uint32 FrameNumber, TArray<FVector, TInlineAllocator<9> >& OutVectors)
{
OutVectors.Empty(UE_ARRAY_COUNT(SpacedVectors9));
if (GAOSampleSet == 0)
{
for (int32 i = 0; i < UE_ARRAY_COUNT(SpacedVectors9); i++)
{
OutVectors.Add(SpacedVectors9[i]);
}
}
else
{
for (int32 i = 0; i < UE_ARRAY_COUNT(RelaxedSpacedVectors9); i++)
{
OutVectors.Add(RelaxedSpacedVectors9[i]);
}
}
if (GAOJitterConeDirections)
{
float RandomAngle = TemporalHalton2(FrameNumber & 1023, 2) * 2 * PI;
float CosRandomAngle = FMath::Cos(RandomAngle);
float SinRandomAngle = FMath::Sin(RandomAngle);
for (int32 i = 0; i < OutVectors.Num(); i++)
{
FVector ConeDirection = OutVectors[i];
FVector2D ConeDirectionXY(ConeDirection.X, ConeDirection.Y);
ConeDirectionXY = FVector2D(FVector2D::DotProduct(ConeDirectionXY, FVector2D(CosRandomAngle, -SinRandomAngle)), FVector2D::DotProduct(ConeDirectionXY, FVector2D(SinRandomAngle, CosRandomAngle)));
OutVectors[i].X = ConeDirectionXY.X;
OutVectors[i].Y = ConeDirectionXY.Y;
}
}
}
// Cone half angle derived from each cone covering an equal solid angle
float GAOConeHalfAngle = FMath::Acos(1 - 1.0f / (float)UE_ARRAY_COUNT(SpacedVectors9));
// Number of AO sample positions along each cone
// Must match shader code
uint32 GAONumConeSteps = 10;
extern float GAOViewFadeDistanceScale;
FAOParameters DistanceField::SetupAOShaderParameters(const FDistanceFieldAOParameters& Parameters)
{
const float AOLargestSampleOffset = Parameters.ObjectMaxOcclusionDistance / (1 + FMath::Tan(GAOConeHalfAngle));
FAOParameters ShaderParameters;
ShaderParameters.AOObjectMaxDistance = Parameters.ObjectMaxOcclusionDistance;
ShaderParameters.AOStepScale = AOLargestSampleOffset / FMath::Pow(2.0f, GAOStepExponentScale * (GAONumConeSteps - 1));
ShaderParameters.AOStepExponentScale = GAOStepExponentScale;
ShaderParameters.AOMaxViewDistance = GetMaxAOViewDistance();
ShaderParameters.AOGlobalMaxOcclusionDistance = Parameters.GlobalMaxOcclusionDistance;
return ShaderParameters;
}
FDFAOUpsampleParameters DistanceField::SetupAOUpsampleParameters(const FViewInfo& View, FRDGTextureRef DistanceFieldAOBentNormal)
{
const float DistanceFadeScaleValue = 1.0f / ((1.0f - GAOViewFadeDistanceScale) * GetMaxAOViewDistance());
const FIntPoint AOBufferSize = GetBufferSizeForAO(View);
const FIntPoint AOViewSize = View.ViewRect.Size() / GAODownsampleFactor;
const FVector2f UVMax(
(AOViewSize.X - 0.51f) / AOBufferSize.X, // 0.51 - so bilateral gather4 won't sample invalid texels
(AOViewSize.Y - 0.51f) / AOBufferSize.Y);
FDFAOUpsampleParameters ShaderParameters;
ShaderParameters.BentNormalAOTexture = DistanceFieldAOBentNormal;
ShaderParameters.BentNormalAOSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
ShaderParameters.AOBufferBilinearUVMax = UVMax;
ShaderParameters.DistanceFadeScale = DistanceFadeScaleValue;
ShaderParameters.AOMaxViewDistance = GetMaxAOViewDistance();
return ShaderParameters;
}
bool bListMemoryNextFrame = false;
void OnListMemory(UWorld* InWorld)
{
bListMemoryNextFrame = true;
}
FAutoConsoleCommandWithWorld ListMemoryConsoleCommand(
TEXT("r.AOListMemory"),
TEXT(""),
FConsoleCommandWithWorldDelegate::CreateStatic(OnListMemory)
);
bool bListMeshDistanceFieldsMemoryNextFrame = false;
void OnListMeshDistanceFields(UWorld* InWorld)
{
bListMeshDistanceFieldsMemoryNextFrame = true;
}
FAutoConsoleCommandWithWorld ListMeshDistanceFieldsMemoryConsoleCommand(
TEXT("r.AOListMeshDistanceFields"),
TEXT(""),
FConsoleCommandWithWorldDelegate::CreateStatic(OnListMeshDistanceFields)
);
bool ShouldCompileDFNormalShaders(EShaderPlatform ShaderPlatform)
{
return ShouldCompileDistanceFieldShaders(ShaderPlatform);
}
class FComputeDistanceFieldNormalPS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FComputeDistanceFieldNormalPS);
SHADER_USE_PARAMETER_STRUCT(FComputeDistanceFieldNormalPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FAOParameters, AOParameters)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompileDFNormalShaders(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
OutEnvironment.SetDefine(TEXT("DOWNSAMPLE_FACTOR"), GAODownsampleFactor);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GDistanceFieldAOTileSizeX);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GDistanceFieldAOTileSizeY);
}
};
IMPLEMENT_GLOBAL_SHADER(FComputeDistanceFieldNormalPS, "/Engine/Private/DistanceFieldScreenGridLighting.usf", "ComputeDistanceFieldNormalPS", SF_Pixel);
class FComputeDistanceFieldNormalCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FComputeDistanceFieldNormalCS);
SHADER_USE_PARAMETER_STRUCT(FComputeDistanceFieldNormalCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSubstrateGlobalUniformParameters, Substrate)
SHADER_PARAMETER_STRUCT_INCLUDE(FAOParameters, AOParameters)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, RWDistanceFieldNormal)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShouldCompileDFNormalShaders(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
OutEnvironment.SetDefine(TEXT("DOWNSAMPLE_FACTOR"), GAODownsampleFactor);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GDistanceFieldAOTileSizeX);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GDistanceFieldAOTileSizeY);
}
};
IMPLEMENT_GLOBAL_SHADER(FComputeDistanceFieldNormalCS, "/Engine/Private/DistanceFieldScreenGridLighting.usf", "ComputeDistanceFieldNormalCS", SF_Compute);
void ComputeDistanceFieldNormal(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FSceneTextures& SceneTextures,
FRDGTextureRef DistanceFieldNormal,
const FDistanceFieldAOParameters& Parameters)
{
const FIntPoint DFAOViewSize = View.ViewRect.Size() / GAODownsampleFactor;
if (GAOComputeShaderNormalCalculation)
{
uint32 GroupSizeX = FMath::DivideAndRoundUp(DFAOViewSize.X, GDistanceFieldAOTileSizeX);
uint32 GroupSizeY = FMath::DivideAndRoundUp(DFAOViewSize.Y, GDistanceFieldAOTileSizeY);
auto* PassParameters = GraphBuilder.AllocParameters<FComputeDistanceFieldNormalCS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
PassParameters->SceneTextures = SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel);;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
PassParameters->AOParameters = DistanceField::SetupAOShaderParameters(Parameters);
PassParameters->RWDistanceFieldNormal = GraphBuilder.CreateUAV(DistanceFieldNormal);
TShaderMapRef<FComputeDistanceFieldNormalCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("ComputeNormalCS"), ComputeShader, PassParameters, FIntVector(GroupSizeX, GroupSizeY, 1));
}
else
{
auto* PassParameters = GraphBuilder.AllocParameters<FComputeDistanceFieldNormalPS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
PassParameters->SceneTextures = SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel);;
PassParameters->Substrate = Substrate::BindSubstrateGlobalUniformParameters(View);
PassParameters->AOParameters = DistanceField::SetupAOShaderParameters(Parameters);
PassParameters->RenderTargets[0] = FRenderTargetBinding(DistanceFieldNormal, ERenderTargetLoadAction::EClear);
GraphBuilder.AddPass(
RDG_EVENT_NAME("ComputeNormal"),
PassParameters,
ERDGPassFlags::Raster,
[&View, PassParameters, DFAOViewSize](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
RHICmdList.SetViewport(0, 0, 0.0f, DFAOViewSize.X, DFAOViewSize.Y, 1.0f);
TShaderMapRef<FPostProcessVS> VertexShader(View.ShaderMap);
TShaderMapRef<FComputeDistanceFieldNormalPS> PixelShader(View.ShaderMap);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
DrawRectangle(
RHICmdList,
0, 0,
DFAOViewSize.X, DFAOViewSize.Y,
0, 0,
View.ViewRect.Width(), View.ViewRect.Height(),
DFAOViewSize,
View.GetSceneTexturesConfig().Extent,
VertexShader);
});
}
}
/** Generates a pseudo-random position inside the unit sphere, uniformly distributed over the volume of the sphere. */
FVector GetUnitPosition2(FRandomStream& RandomStream)
{
FVector Result;
// Use rejection sampling to generate a valid sample
do
{
Result.X = RandomStream.GetFraction() * 2 - 1;
Result.Y = RandomStream.GetFraction() * 2 - 1;
Result.Z = RandomStream.GetFraction() * 2 - 1;
} while( Result.SizeSquared() > 1.f );
return Result;
}
/** Generates a pseudo-random unit vector, uniformly distributed over all directions. */
FVector GetUnitVector2(FRandomStream& RandomStream)
{
return GetUnitPosition2(RandomStream).GetUnsafeNormal();
}
void GenerateBestSpacedVectors()
{
static bool bGenerated = false;
bool bApplyRepulsion = false;
if (bApplyRepulsion && !bGenerated)
{
bGenerated = true;
FVector OriginalSpacedVectors9[UE_ARRAY_COUNT(SpacedVectors9)];
for (int32 i = 0; i < UE_ARRAY_COUNT(OriginalSpacedVectors9); i++)
{
OriginalSpacedVectors9[i] = SpacedVectors9[i];
}
float CosHalfAngle = 1 - 1.0f / (float)UE_ARRAY_COUNT(OriginalSpacedVectors9);
// Used to prevent self-shadowing on a plane
float AngleBias = .03f;
float MinAngle = FMath::Acos(CosHalfAngle) + AngleBias;
float MinZ = FMath::Sin(MinAngle);
// Relaxation iterations by repulsion
for (int32 Iteration = 0; Iteration < 10000; Iteration++)
{
for (int32 i = 0; i < UE_ARRAY_COUNT(OriginalSpacedVectors9); i++)
{
FVector Force(0.0f, 0.0f, 0.0f);
for (int32 j = 0; j < UE_ARRAY_COUNT(OriginalSpacedVectors9); j++)
{
if (i != j)
{
FVector Distance = OriginalSpacedVectors9[i] - OriginalSpacedVectors9[j];
float Dot = OriginalSpacedVectors9[i] | OriginalSpacedVectors9[j];
if (Dot > 0)
{
// Repulsion force
Force += .001f * Distance.GetSafeNormal() * Dot * Dot * Dot * Dot;
}
}
}
FVector NewPosition = OriginalSpacedVectors9[i] + Force;
NewPosition.Z = FMath::Max<FVector::FReal>(NewPosition.Z, MinZ);
NewPosition = NewPosition.GetSafeNormal();
OriginalSpacedVectors9[i] = NewPosition;
}
}
for (int32 i = 0; i < UE_ARRAY_COUNT(OriginalSpacedVectors9); i++)
{
UE_LOG(LogDistanceField, Log, TEXT("FVector(%f, %f, %f),"), OriginalSpacedVectors9[i].X, OriginalSpacedVectors9[i].Y, OriginalSpacedVectors9[i].Z);
}
int32 temp = 0;
}
bool bBruteForceGenerateConeDirections = false;
if (bBruteForceGenerateConeDirections)
{
FVector BestSpacedVectors9[9];
float BestCoverage = 0;
// Each cone covers an area of ConeSolidAngle = HemisphereSolidAngle / NumCones
// HemisphereSolidAngle = 2 * PI
// ConeSolidAngle = 2 * PI * (1 - cos(ConeHalfAngle))
// cos(ConeHalfAngle) = 1 - 1 / NumCones
float CosHalfAngle = 1 - 1.0f / (float)UE_ARRAY_COUNT(BestSpacedVectors9);
// Prevent self-intersection in sample set
float MinAngle = FMath::Acos(CosHalfAngle);
float MinZ = FMath::Sin(MinAngle);
FRandomStream RandomStream(123567);
// Super slow random brute force search
for (int i = 0; i < 1000000; i++)
{
FVector CandidateSpacedVectors[UE_ARRAY_COUNT(BestSpacedVectors9)];
for (int j = 0; j < UE_ARRAY_COUNT(CandidateSpacedVectors); j++)
{
FVector NewSample;
// Reject invalid directions until we get a valid one
do
{
NewSample = GetUnitVector2(RandomStream);
}
while (NewSample.Z <= MinZ);
CandidateSpacedVectors[j] = NewSample;
}
float Coverage = 0;
int NumSamples = 10000;
// Determine total cone coverage with monte carlo estimation
for (int sample = 0; sample < NumSamples; sample++)
{
FVector NewSample;
do
{
NewSample = GetUnitVector2(RandomStream);
}
while (NewSample.Z <= 0);
bool bIntersects = false;
for (int j = 0; j < UE_ARRAY_COUNT(CandidateSpacedVectors); j++)
{
if (FVector::DotProduct(CandidateSpacedVectors[j], NewSample) > CosHalfAngle)
{
bIntersects = true;
break;
}
}
Coverage += bIntersects ? 1 / (float)NumSamples : 0;
}
if (Coverage > BestCoverage)
{
BestCoverage = Coverage;
for (int j = 0; j < UE_ARRAY_COUNT(CandidateSpacedVectors); j++)
{
BestSpacedVectors9[j] = CandidateSpacedVectors[j];
}
}
}
int32 temp = 0;
}
}
void AllocateTileIntersectionBuffers(
FRDGBuilder& GraphBuilder,
FIntPoint TileListGroupSize,
uint32 MaxSceneObjects,
bool bAllow16BitIndices,
FRDGBufferRef& OutObjectTilesIndirectArguments,
FTileIntersectionParameters& OutParameters)
{
// Can only use 16 bit for CulledTileDataArray if few enough objects and tiles
const bool b16BitObjectIndices = MaxSceneObjects < (1 << 16);
const bool b16BitCulledTileIndexBuffer = bAllow16BitIndices && b16BitObjectIndices && (TileListGroupSize.X * TileListGroupSize.Y < (1 << 16));
int32 TileCount = TileListGroupSize.X * TileListGroupSize.Y;
FRDGBufferRef TileConeAxisAndCos = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FVector4f), TileCount), TEXT("TileConeAxisAndCos"));
OutParameters.RWTileConeAxisAndCos = GraphBuilder.CreateUAV(TileConeAxisAndCos);
OutParameters.TileConeAxisAndCos = GraphBuilder.CreateSRV(TileConeAxisAndCos);
FRDGBufferRef TileConeDepthRanges = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FVector4f), TileCount), TEXT("TileConeDepthRanges"));
OutParameters.RWTileConeDepthRanges = GraphBuilder.CreateUAV(TileConeDepthRanges);
OutParameters.TileConeDepthRanges = GraphBuilder.CreateSRV(TileConeDepthRanges);
FRDGBufferRef NumCulledTilesArray = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), MaxSceneObjects), TEXT("NumCulledTilesArray"));
OutParameters.RWNumCulledTilesArray = GraphBuilder.CreateUAV(NumCulledTilesArray);
OutParameters.NumCulledTilesArray = GraphBuilder.CreateSRV(NumCulledTilesArray);
FRDGBufferRef CulledTilesStartOffsetArray = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), MaxSceneObjects), TEXT("CulledTilesStartOffsetArray"));
OutParameters.RWCulledTilesStartOffsetArray = GraphBuilder.CreateUAV(CulledTilesStartOffsetArray);
OutParameters.CulledTilesStartOffsetArray = GraphBuilder.CreateSRV(CulledTilesStartOffsetArray);
extern int32 GAverageDistanceFieldObjectsPerCullTile;
FRDGBufferRef CulledTileDataArray = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateBufferDesc(b16BitCulledTileIndexBuffer ? sizeof(uint16) : sizeof(uint32), GAverageDistanceFieldObjectsPerCullTile * TileCount * CulledTileDataStride),
TEXT("CulledTileDataArray"));
OutParameters.RWCulledTileDataArray = GraphBuilder.CreateUAV(CulledTileDataArray, b16BitCulledTileIndexBuffer ? PF_R16_UINT : PF_R32_UINT);
OutParameters.CulledTileDataArray = GraphBuilder.CreateSRV(CulledTileDataArray, b16BitCulledTileIndexBuffer ? PF_R16_UINT : PF_R32_UINT);
OutObjectTilesIndirectArguments = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<FRHIDispatchIndirectParameters>(), TEXT("ObjectTilesIndirectArguments"));
OutParameters.RWObjectTilesIndirectArguments = GraphBuilder.CreateUAV(OutObjectTilesIndirectArguments, PF_R32_UINT);
OutParameters.TileListGroupSize = TileListGroupSize;
}
void ListDistanceFieldLightingMemory(const FScene* Scene, FSceneRenderer& SceneRenderer)
{
#if !NO_LOGGING
UE_LOG(LogRenderer, Log, TEXT("Shared GPU memory (excluding render targets)"));
if (Scene->DistanceFieldSceneData.NumObjectsInBuffer > 0)
{
UE_LOG(LogRenderer, Log, TEXT(" Scene Object data %.3fMb"), Scene->DistanceFieldSceneData.GetCurrentObjectBuffers()->GetSizeBytes() / 1024.0f / 1024.0f);
}
#endif // !NO_LOGGING
}
bool SupportsDistanceFieldAO(ERHIFeatureLevel::Type FeatureLevel, EShaderPlatform ShaderPlatform)
{
return GDistanceFieldAO && GDistanceFieldAOQuality > 0
// Pre-GCN AMD cards have a driver bug that prevents the global distance field from being generated correctly
// Better to disable entirely than to display garbage
&& !GRHIDeviceIsAMDPreGCNArchitecture
// In case of iGPU we use the maximum feature level to differentiate between older and newer more capable iGPU
&& (!GRHIDeviceIsIntegrated || GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM6)
&& DoesPlatformSupportDistanceFieldAO(ShaderPlatform)
&& IsUsingDistanceFields(ShaderPlatform);
}
bool ShouldRenderDeferredDynamicSkyLight(const FScene* Scene, const FSceneViewFamily& ViewFamily)
{
return Scene->SkyLight
&& (Scene->SkyLight->ProcessedTexture || Scene->SkyLight->bRealTimeCaptureEnabled)
&& !Scene->SkyLight->bWantsStaticShadowing
&& !Scene->SkyLight->bHasStaticLighting
&& ViewFamily.EngineShowFlags.SkyLighting
&& !IsForwardShadingEnabled(Scene->GetShaderPlatform())
&& !ViewFamily.EngineShowFlags.VisualizeLightCulling
&& !ShouldRenderRayTracingSkyLight(Scene->SkyLight, Scene->GetShaderPlatform()); // Disable diffuse sky contribution if evaluated by RT Sky;
}
bool ShouldDoReflectionEnvironment(const FScene* Scene, const FSceneViewFamily& ViewFamily)
{
const ERHIFeatureLevel::Type SceneFeatureLevel = Scene->GetFeatureLevel();
return IsReflectionEnvironmentAvailable(SceneFeatureLevel)
&& Scene->ReflectionSceneData.RegisteredReflectionCaptures.Num()
&& ViewFamily.EngineShowFlags.ReflectionEnvironment;
}
bool ShouldPrepareForDistanceFieldAO(const FScene* Scene, const FSceneViewFamily& ViewFamily, bool bAnyViewHasGIMethodSupportingDFAO)
{
if (SupportsDistanceFieldAO(Scene->GetFeatureLevel(), Scene->GetShaderPlatform()))
{
if (bAnyViewHasGIMethodSupportingDFAO && ViewFamily.EngineShowFlags.DistanceFieldAO)
{
if (GDistanceFieldAOApplyToStaticIndirect)
{
return true;
}
if (ShouldRenderDeferredDynamicSkyLight(Scene, ViewFamily) && Scene->SkyLight->bCastShadows)
{
return true;
}
}
if (ViewFamily.EngineShowFlags.VisualizeDistanceFieldAO)
{
return true;
}
}
return false;
}
bool ShouldPrepareForDistanceFieldAO(const FSceneRenderUpdateInputs& SceneUpdateInputs)
{
for (const FSceneRenderer* Renderer : SceneUpdateInputs.Renderers)
{
if (ShouldPrepareForDistanceFieldAO(SceneUpdateInputs.Scene, Renderer->ViewFamily, Renderer->AnyViewHasGIMethodSupportingDFAO()))
{
return true;
}
}
return false;
}
bool ShouldPrepareGlobalDistanceField(const FSceneRenderer* Renderer)
{
if (!DoesProjectSupportDistanceFields() || !UseGlobalDistanceField())
{
return false;
}
const FScene* Scene = Renderer->Scene;
const FFXSystemInterface* FXSystem = Scene->FXSystem;
const FViewInfo& View = Renderer->Views[0];
const FSceneViewFamily& ViewFamily = Renderer->ViewFamily;
const bool bShouldPrepareForMaterialsOrNiagara = !GAOGlobalDistanceFieldDetailedNecessityCheck
|| View.PrevViewInfo.bUsesGlobalDistanceField // use previous frame relevance because this frame relevance flags haven't been calculate yet
|| ((FXSystem != nullptr) && FXSystem->UsesGlobalDistanceField());
if (ViewFamily.EngineShowFlags.PathTracing)
{
return bShouldPrepareForMaterialsOrNiagara;
}
if (SupportsDistanceFieldAO(Renderer->FeatureLevel, Renderer->ShaderPlatform) && (bShouldPrepareForMaterialsOrNiagara || ShouldPrepareForDistanceFieldAO(Scene, ViewFamily, Renderer->AnyViewHasGIMethodSupportingDFAO())))
{
return true;
}
if (Renderer->IsLumenEnabled(View) && Lumen::UseGlobalSDFObjectGrid(ViewFamily))
{
return true;
}
if (MegaLights::IsUsingGlobalSDF(ViewFamily))
{
return true;
}
if (ViewFamily.EngineShowFlags.VisualizeGlobalDistanceField)
{
return true;
}
return false;
}
bool ShouldPrepareDistanceFieldScene(const FSceneRenderUpdateInputs& SceneUpdateInputs)
{
extern bool ShouldPrepareForDFInsetIndirectShadow(EShaderPlatform, const FEngineShowFlags&);
extern bool ShouldPrepareForDistanceFieldShadows(const FSceneRenderUpdateInputs& SceneUpdateInputs);
if (!DoesProjectSupportDistanceFields())
{
return false;
}
if (GRHIDeviceIsIntegrated && GMaxRHIFeatureLevel < ERHIFeatureLevel::SM6)
{
// In case of iGPU we use the maximum feature level to differentiate between older and newer more capable iGPU
return false;
}
if (ShouldPrepareForDistanceFieldAO(SceneUpdateInputs))
{
return true;
}
if (ShouldPrepareForDistanceFieldShadows(SceneUpdateInputs))
{
return true;
}
for (const FSceneRenderer* Renderer : SceneUpdateInputs.Renderers)
{
const FViewFamilyInfo& ViewFamily = Renderer->ViewFamily;
if (ShouldPrepareGlobalDistanceField(Renderer))
{
return true;
}
if (ShouldPrepareForDFInsetIndirectShadow(SceneUpdateInputs.ShaderPlatform, ViewFamily.EngineShowFlags))
{
return true;
}
if (ViewFamily.EngineShowFlags.VisualizeGlobalDistanceField || ViewFamily.EngineShowFlags.VisualizeMeshDistanceFields)
{
return true;
}
}
return false;
}
bool ShouldRenderDistanceFieldLighting(const FDistanceFieldSceneData& SceneData, TConstArrayView<FViewInfo> Views)
{
if (!GDistanceFieldAOMultiView && Views.Num() > 1)
{
return false;
}
bool bSupportsDistanceFieldAO = true;
for (int32_t ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
if (!SupportsDistanceFieldAO(View.GetFeatureLevel(), View.GetShaderPlatform()))
{
bSupportsDistanceFieldAO = false;
break;
}
}
return bSupportsDistanceFieldAO && SceneData.NumObjectsInBuffer;
}
void FDeferredShadingSceneRenderer::RenderDFAOAsIndirectShadowing(
FRDGBuilder& GraphBuilder,
const FSceneTextures& SceneTextures,
TArray<FRDGTextureRef>& DynamicBentNormalAOTextures)
{
if (GDistanceFieldAOApplyToStaticIndirect && ShouldRenderDistanceFieldAO(Views, ViewFamily.EngineShowFlags) && ShouldRenderDistanceFieldLighting(Scene->DistanceFieldSceneData, Views))
{
// Use the skylight's max distance if there is one, to be consistent with DFAO shadowing on the skylight
const float OcclusionMaxDistance = Scene->SkyLight && !Scene->SkyLight->bWantsStaticShadowing ? Scene->SkyLight->OcclusionMaxDistance : Scene->DefaultMaxDistanceFieldOcclusionDistance;
RenderDistanceFieldLighting(GraphBuilder, SceneTextures, FDistanceFieldAOParameters(OcclusionMaxDistance), DynamicBentNormalAOTextures, true, false);
}
}
void FSceneRenderer::RenderDistanceFieldLighting(
FRDGBuilder& GraphBuilder,
const FSceneTextures& SceneTextures,
const FDistanceFieldAOParameters& Parameters,
TArray<FRDGTextureRef>& OutDynamicBentNormalAOTextures,
bool bModulateToSceneColor,
bool bVisualizeAmbientOcclusion,
bool bModulateToScreenSpaceAO)
{
check(ShouldRenderDistanceFieldLighting(Scene->DistanceFieldSceneData, Views));
check(!Scene->DistanceFieldSceneData.HasPendingOperations());
RDG_EVENT_SCOPE(GraphBuilder, "DistanceFieldLighting");
QUICK_SCOPE_CYCLE_COUNTER(STAT_RenderDistanceFieldLighting);
GenerateBestSpacedVectors();
if (bListMemoryNextFrame)
{
bListMemoryNextFrame = false;
ListDistanceFieldLightingMemory(Scene, *this);
}
if (bListMeshDistanceFieldsMemoryNextFrame)
{
bListMeshDistanceFieldsMemoryNextFrame = false;
Scene->DistanceFieldSceneData.ListMeshDistanceFields(true);
}
// We only need this texture if we need to copy multiple view outputs to an texture atlas
OutDynamicBentNormalAOTextures.Reset();
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
FRDGTextureRef DistanceFieldNormal = nullptr;
{
const FIntPoint BufferSize = GetBufferSizeForAO(View);
const FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(BufferSize, PF_FloatRGBA, FClearValueBinding::Transparent, GFastVRamConfig.DistanceFieldNormal | TexCreate_RenderTargetable | TexCreate_UAV | TexCreate_ShaderResource);
DistanceFieldNormal = GraphBuilder.CreateTexture(Desc, TEXT("DistanceFieldNormal"));
}
ComputeDistanceFieldNormal(GraphBuilder, View, SceneTextures, DistanceFieldNormal, Parameters);
const FIntPoint TileListGroupSize = GetTileListGroupSizeForView(View);
const int32 MaxSceneObjects = FMath::DivideAndRoundUp(Scene->DistanceFieldSceneData.NumObjectsInBuffer, 256) * 256;
const bool bAllow16BitIndices = !IsMetalPlatform(GShaderPlatformForFeatureLevel[View.FeatureLevel]);
FRDGBufferRef ObjectTilesIndirectArguments = nullptr;
FTileIntersectionParameters TileIntersectionParameters;
AllocateTileIntersectionBuffers(GraphBuilder, TileListGroupSize, MaxSceneObjects, bAllow16BitIndices, ObjectTilesIndirectArguments, TileIntersectionParameters);
FRDGBufferRef ObjectIndirectArguments = nullptr;
FDistanceFieldCulledObjectBufferParameters CulledObjectBufferParameters;
if (UseAOObjectDistanceField())
{
AllocateDistanceFieldCulledObjectBuffers(
GraphBuilder,
MaxSceneObjects,
ObjectIndirectArguments,
CulledObjectBufferParameters);
CullObjectsToView(GraphBuilder, *Scene, View, Parameters, CulledObjectBufferParameters);
BuildTileObjectLists(GraphBuilder, *Scene, View, SceneTextures, ObjectIndirectArguments, CulledObjectBufferParameters, TileIntersectionParameters, DistanceFieldNormal, Parameters);
}
// Render to a per-view BentNormal first because it also needs to be stored by the view history.
// The per-view output can be copied back to the appropriate region in the output BentNormal texture.
FRDGTextureRef PerViewBentNormal = nullptr;
RenderDistanceFieldAOScreenGrid(
GraphBuilder,
SceneTextures,
View,
CulledObjectBufferParameters,
ObjectTilesIndirectArguments,
TileIntersectionParameters,
Parameters,
DistanceFieldNormal,
PerViewBentNormal);
RenderCapsuleShadowsForMovableSkylight(GraphBuilder, View, PerViewBentNormal);
// Upsample to full resolution, write to output in case of debug AO visualization or scene color modulation (standard upsampling is done later together with sky lighting and reflection environment)
if (bModulateToSceneColor || bModulateToScreenSpaceAO || bVisualizeAmbientOcclusion)
{
UpsampleBentNormalAO(GraphBuilder, View, SceneTextures, bModulateToScreenSpaceAO ? SceneTextures.ScreenSpaceAO: SceneTextures.Color.Target, PerViewBentNormal, bModulateToSceneColor && !bVisualizeAmbientOcclusion, bModulateToScreenSpaceAO && !bVisualizeAmbientOcclusion);
}
OutDynamicBentNormalAOTextures.Add(PerViewBentNormal);
}
}
bool ShouldRenderDistanceFieldAO(TConstArrayView<FViewInfo> Views, const FEngineShowFlags& EngineShowFlags)
{
bool bShouldRenderRTAO = false;
for (int ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
bShouldRenderRTAO = bShouldRenderRTAO || ShouldRenderRayTracingAmbientOcclusion(Views[ViewIndex]);
}
return EngineShowFlags.DistanceFieldAO
&& !bShouldRenderRTAO
&& !EngineShowFlags.VisualizeDistanceFieldAO
&& !EngineShowFlags.VisualizeMeshDistanceFields
&& !EngineShowFlags.VisualizeGlobalDistanceField
&& !EngineShowFlags.PathTracing;
}