715 lines
24 KiB
C++
715 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
Reflection Environment - feature that provides HDR glossy reflections on any surfaces, leveraging precomputation to prefilter cubemaps of the scene
|
|
=============================================================================*/
|
|
|
|
#include "ReflectionEnvironment.h"
|
|
#include "Components/ReflectionCaptureComponent.h"
|
|
#include "Stats/Stats.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "RHI.h"
|
|
#include "UniformBuffer.h"
|
|
#include "ShaderParameters.h"
|
|
#include "RendererInterface.h"
|
|
#include "Shader.h"
|
|
#include "StaticBoundShaderState.h"
|
|
#include "SceneUtils.h"
|
|
#include "RHIStaticStates.h"
|
|
#include "PostProcess/SceneRenderTargets.h"
|
|
#include "GlobalShader.h"
|
|
#include "SceneRenderTargetParameters.h"
|
|
#include "DeferredShadingRenderer.h"
|
|
#include "BasePassRendering.h"
|
|
#include "ScenePrivate.h"
|
|
#include "SceneProxies/SkyLightSceneProxy.h"
|
|
#include "PostProcess/SceneFilterRendering.h"
|
|
#include "PostProcess/PostProcessing.h"
|
|
#include "PostProcess/PostProcessSubsurface.h"
|
|
#include "LightRendering.h"
|
|
#include "PipelineStateCache.h"
|
|
#include "DistanceFieldAmbientOcclusion.h"
|
|
#include "SceneTextureParameters.h"
|
|
#include "ScreenSpaceDenoise.h"
|
|
#include "ScreenSpaceRayTracing.h"
|
|
#include "RayTracing/RaytracingOptions.h"
|
|
#include "RenderGraph.h"
|
|
#include "PixelShaderUtils.h"
|
|
#include "SceneTextureParameters.h"
|
|
#include "HairStrands/HairStrandsRendering.h"
|
|
|
|
static TAutoConsoleVariable<int32> CVarReflectionEnvironment(
|
|
TEXT("r.ReflectionEnvironment"),
|
|
1,
|
|
TEXT("Whether to render the reflection environment feature, which implements local reflections through Reflection Capture actors.\n")
|
|
TEXT(" 0: off\n")
|
|
TEXT(" 1: on and blend with scene (default)")
|
|
TEXT(" 2: on and overwrite scene (only in non-shipping builds)"),
|
|
ECVF_RenderThreadSafe | ECVF_Scalability);
|
|
|
|
int32 GReflectionEnvironmentLightmapMixing = 1;
|
|
FAutoConsoleVariableRef CVarReflectionEnvironmentLightmapMixing(
|
|
TEXT("r.ReflectionEnvironmentLightmapMixing"),
|
|
GReflectionEnvironmentLightmapMixing,
|
|
TEXT("Whether to mix indirect specular from reflection captures with indirect diffuse from lightmaps for rough surfaces."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GReflectionEnvironmentLightmapMixBasedOnRoughness = 1;
|
|
FAutoConsoleVariableRef CVarReflectionEnvironmentLightmapMixBasedOnRoughness(
|
|
TEXT("r.ReflectionEnvironmentLightmapMixBasedOnRoughness"),
|
|
GReflectionEnvironmentLightmapMixBasedOnRoughness,
|
|
TEXT("Whether to reduce lightmap mixing with reflection captures for very smooth surfaces. This is useful to make sure reflection captures match SSR / planar reflections in brightness."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GReflectionEnvironmentBeginMixingRoughness = .1f;
|
|
FAutoConsoleVariableRef CVarReflectionEnvironmentBeginMixingRoughness(
|
|
TEXT("r.ReflectionEnvironmentBeginMixingRoughness"),
|
|
GReflectionEnvironmentBeginMixingRoughness,
|
|
TEXT("Min roughness value at which to begin mixing reflection captures with lightmap indirect diffuse."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GReflectionEnvironmentEndMixingRoughness = .3f;
|
|
FAutoConsoleVariableRef CVarReflectionEnvironmentEndMixingRoughness(
|
|
TEXT("r.ReflectionEnvironmentEndMixingRoughness"),
|
|
GReflectionEnvironmentEndMixingRoughness,
|
|
TEXT("Min roughness value at which to end mixing reflection captures with lightmap indirect diffuse."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GReflectionEnvironmentLightmapMixLargestWeight = 10000;
|
|
FAutoConsoleVariableRef CVarReflectionEnvironmentLightmapMixLargestWeight(
|
|
TEXT("r.ReflectionEnvironmentLightmapMixLargestWeight"),
|
|
GReflectionEnvironmentLightmapMixLargestWeight,
|
|
TEXT("When set to 1 can be used to clamp lightmap mixing such that only darkening from lightmaps are applied to reflection captures."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarDoTiledReflections(
|
|
TEXT("r.DoTiledReflections"),
|
|
1,
|
|
TEXT("Compute Reflection Environment with Tiled compute shader..\n")
|
|
TEXT(" 0: off\n")
|
|
TEXT(" 1: on (default)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
|
|
// to avoid having direct access from many places
|
|
int GetReflectionEnvironmentCVar()
|
|
{
|
|
int32 RetVal = CVarReflectionEnvironment.GetValueOnAnyThread();
|
|
|
|
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// Disabling the debug part of this CVar when in shipping
|
|
if (RetVal == 2)
|
|
{
|
|
RetVal = 1;
|
|
}
|
|
#endif
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
FVector GetReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight()
|
|
{
|
|
float RoughnessMixingRange = 1.0f / FMath::Max(GReflectionEnvironmentEndMixingRoughness - GReflectionEnvironmentBeginMixingRoughness, .001f);
|
|
|
|
if (GReflectionEnvironmentLightmapMixing == 0)
|
|
{
|
|
return FVector(0, 0, GReflectionEnvironmentLightmapMixLargestWeight);
|
|
}
|
|
|
|
if (GReflectionEnvironmentEndMixingRoughness == 0.0f && GReflectionEnvironmentBeginMixingRoughness == 0.0f)
|
|
{
|
|
// Make sure a Roughness of 0 results in full mixing when disabling roughness-based mixing
|
|
return FVector(0, 1, GReflectionEnvironmentLightmapMixLargestWeight);
|
|
}
|
|
|
|
if (!GReflectionEnvironmentLightmapMixBasedOnRoughness)
|
|
{
|
|
return FVector(0, 1, GReflectionEnvironmentLightmapMixLargestWeight);
|
|
}
|
|
|
|
return FVector(RoughnessMixingRange, -GReflectionEnvironmentBeginMixingRoughness * RoughnessMixingRange, GReflectionEnvironmentLightmapMixLargestWeight);
|
|
}
|
|
|
|
bool IsReflectionEnvironmentAvailable(ERHIFeatureLevel::Type InFeatureLevel)
|
|
{
|
|
return (SupportsTextureCubeArray(InFeatureLevel)) && (GetReflectionEnvironmentCVar() != 0);
|
|
}
|
|
|
|
bool IsReflectionCaptureAvailable()
|
|
{
|
|
return IsStaticLightingAllowed();
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::InitRHI(FRHICommandListBase&)
|
|
{
|
|
if (SupportsTextureCubeArray(GetFeatureLevel()))
|
|
{
|
|
check(MaxCubemaps > 0);
|
|
check(CubemapSize > 0);
|
|
|
|
const int32 NumReflectionCaptureMips = FMath::CeilLogTwo(CubemapSize) + 1;
|
|
|
|
ReleaseCubeArray();
|
|
|
|
FPooledRenderTargetDesc Desc(
|
|
FPooledRenderTargetDesc::CreateCubemapDesc(
|
|
CubemapSize,
|
|
// Alpha stores sky mask
|
|
PF_FloatRGBA,
|
|
FClearValueBinding::None,
|
|
TexCreate_None,
|
|
TexCreate_None,
|
|
false,
|
|
// Cubemap array of 1 produces a regular cubemap, so guarantee it will be allocated as an array
|
|
FMath::Max<uint32>(MaxCubemaps, 2),
|
|
NumReflectionCaptureMips
|
|
)
|
|
);
|
|
|
|
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
|
|
|
|
// Allocate TextureCubeArray for the scene's reflection captures
|
|
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, ReflectionEnvs, TEXT("ReflectionEnvs"));
|
|
}
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::ReleaseCubeArray()
|
|
{
|
|
// it's unlikely we can reuse the TextureCubeArray so when we release it we want to really remove it
|
|
GRenderTargetPool.FreeUnusedResource(ReflectionEnvs);
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::ReleaseRHI()
|
|
{
|
|
ReleaseCubeArray();
|
|
}
|
|
|
|
|
|
const FCaptureComponentSceneState* FReflectionCaptureCache::Find(const FGuid& MapBuildDataId) const
|
|
{
|
|
const FReflectionCaptureCacheEntry* Entry = CaptureData.Find(MapBuildDataId);
|
|
if (Entry == nullptr)
|
|
return nullptr;
|
|
|
|
return &(Entry->SceneState);
|
|
}
|
|
|
|
FCaptureComponentSceneState* FReflectionCaptureCache::Find(const FGuid& MapBuildDataId)
|
|
{
|
|
FReflectionCaptureCacheEntry* Entry = CaptureData.Find(MapBuildDataId);
|
|
if (Entry == nullptr)
|
|
return nullptr;
|
|
|
|
return &(Entry->SceneState);
|
|
}
|
|
|
|
const FCaptureComponentSceneState* FReflectionCaptureCache::Find(const UReflectionCaptureComponent* Component) const
|
|
{
|
|
if (!Component) // Intentionally not IsValid(Component), as this often occurs when Component is explicitly PendingKill.
|
|
return nullptr;
|
|
|
|
const FCaptureComponentSceneState* SceneState = Find(Component->MapBuildDataId);
|
|
if (SceneState)
|
|
{
|
|
return SceneState;
|
|
}
|
|
|
|
const FGuid* Guid = RegisteredComponentMapBuildDataIds.Find(Component);
|
|
if (Guid)
|
|
{
|
|
return Find(*Guid);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FCaptureComponentSceneState* FReflectionCaptureCache::Find(const UReflectionCaptureComponent* Component)
|
|
{
|
|
if (!Component) // Intentionally not IsValid(Component), as this often occurs when Component is explicitly PendingKill.
|
|
return nullptr;
|
|
|
|
FCaptureComponentSceneState* SceneState = Find(Component->MapBuildDataId);
|
|
if (SceneState)
|
|
{
|
|
return SceneState;
|
|
}
|
|
|
|
const FGuid* Guid = RegisteredComponentMapBuildDataIds.Find(Component);
|
|
if (Guid)
|
|
{
|
|
return Find(*Guid);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
const FCaptureComponentSceneState& FReflectionCaptureCache::FindChecked(const UReflectionCaptureComponent* Component) const
|
|
{
|
|
const FCaptureComponentSceneState* Found = Find(Component);
|
|
check(Found);
|
|
|
|
return *Found;
|
|
}
|
|
|
|
FCaptureComponentSceneState& FReflectionCaptureCache::FindChecked(const UReflectionCaptureComponent* Component)
|
|
{
|
|
FCaptureComponentSceneState* Found = Find(Component);
|
|
check(Found);
|
|
|
|
return *Found;
|
|
}
|
|
|
|
FCaptureComponentSceneState& FReflectionCaptureCache::Add(const UReflectionCaptureComponent* Component, const FCaptureComponentSceneState& Value)
|
|
{
|
|
// During Reflection Capture Placement in editor, this is potentially not IsValid
|
|
// So just check to make sure that the pointer is non-null
|
|
check(Component)
|
|
|
|
FCaptureComponentSceneState* Existing = AddReference(Component);
|
|
if (Existing != nullptr)
|
|
{
|
|
return *Existing;
|
|
}
|
|
else
|
|
{
|
|
FReflectionCaptureCacheEntry& Entry = CaptureData.Add(Component->MapBuildDataId, { 1, Value });
|
|
RegisterComponentMapBuildDataId(Component);
|
|
return Entry.SceneState;
|
|
}
|
|
}
|
|
|
|
FCaptureComponentSceneState* FReflectionCaptureCache::AddReference(const UReflectionCaptureComponent* Component)
|
|
{
|
|
// During Reflection Capture Placement in editor, this is potentially not IsValid
|
|
// So just check to make sure that the pointer is non-null
|
|
check(Component)
|
|
|
|
bool Remap = RemapRegisteredComponentMapBuildDataId(Component);
|
|
FReflectionCaptureCacheEntry* Found = CaptureData.Find(Component->MapBuildDataId);
|
|
if (Found == nullptr)
|
|
return nullptr;
|
|
|
|
// Should not add reference count if this is caused by capture rebuilt
|
|
if (!Remap)
|
|
{
|
|
Found->RefCount += 1;
|
|
}
|
|
|
|
return &(Found->SceneState);
|
|
}
|
|
|
|
int32 FReflectionCaptureCache::GetKeys(TArray<FGuid>& OutKeys) const
|
|
{
|
|
return CaptureData.GetKeys(OutKeys);
|
|
}
|
|
|
|
int32 FReflectionCaptureCache::GetKeys(TSet<FGuid>& OutKeys) const
|
|
{
|
|
return CaptureData.GetKeys(OutKeys);
|
|
}
|
|
|
|
void FReflectionCaptureCache::Empty()
|
|
{
|
|
CaptureData.Empty();
|
|
RegisteredComponentMapBuildDataIds.Empty();
|
|
}
|
|
|
|
int32 FReflectionCaptureCache::Prune(const TSet<FGuid> KeysToKeep, TArray<int32>& ReleasedIndices)
|
|
{
|
|
TSet<FGuid> ExistingKeys;
|
|
CaptureData.GetKeys(ExistingKeys);
|
|
|
|
TSet<FGuid> KeysToRemove = ExistingKeys.Difference(KeysToKeep);
|
|
ReleasedIndices.Empty();
|
|
ReleasedIndices.Reserve(KeysToRemove.Num());
|
|
|
|
for (const FGuid& Key : KeysToRemove)
|
|
{
|
|
FReflectionCaptureCacheEntry* Found = CaptureData.Find(Key);
|
|
if (Found == nullptr)
|
|
continue;
|
|
|
|
int32 CubemapIndex = Found->SceneState.CubemapIndex;
|
|
if (CubemapIndex != -1)
|
|
ReleasedIndices.Add(CubemapIndex);
|
|
|
|
CaptureData.Remove(Key);
|
|
}
|
|
|
|
return ReleasedIndices.Num();
|
|
}
|
|
|
|
bool FReflectionCaptureCache::Remove(const UReflectionCaptureComponent* Component)
|
|
{
|
|
if (!Component) // Intentionally not IsValid(Component), as this often occurs when Component is explicitly PendingKill.
|
|
return false;
|
|
|
|
RemapRegisteredComponentMapBuildDataId(Component);
|
|
FReflectionCaptureCacheEntry* Found = CaptureData.Find(Component->MapBuildDataId);
|
|
if (Found == nullptr)
|
|
return false;
|
|
|
|
if (Found->RefCount > 1)
|
|
{
|
|
Found->RefCount -= 1;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
CaptureData.Remove(Component->MapBuildDataId);
|
|
UnregisterComponentMapBuildDataId(Component);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void FReflectionCaptureCache::RegisterComponentMapBuildDataId(const UReflectionCaptureComponent* Component)
|
|
{
|
|
RegisteredComponentMapBuildDataIds.Add(Component, Component->MapBuildDataId);
|
|
}
|
|
|
|
bool FReflectionCaptureCache::RemapRegisteredComponentMapBuildDataId(const UReflectionCaptureComponent* Component)
|
|
{
|
|
check(Component);
|
|
|
|
// Remap old guid to new guid when the component is the same pointer.
|
|
FGuid* OldBuildId = RegisteredComponentMapBuildDataIds.Find(Component);
|
|
if (OldBuildId &&
|
|
*OldBuildId != Component->MapBuildDataId)
|
|
{
|
|
FGuid OldBuildIdCopy = *OldBuildId;
|
|
const FReflectionCaptureCacheEntry* Current = CaptureData.Find(OldBuildIdCopy);
|
|
if (Current == nullptr)
|
|
return false; // No current entry to remap to, so no remap to perform
|
|
|
|
// Remap all pointers that point to the old guid to the new one.
|
|
int32 ReferenceCount = 0;
|
|
for (TPair<const UReflectionCaptureComponent*, FGuid>& item : RegisteredComponentMapBuildDataIds)
|
|
{
|
|
if (item.Value == OldBuildIdCopy)
|
|
{
|
|
item.Value = Component->MapBuildDataId;
|
|
ReferenceCount++;
|
|
}
|
|
}
|
|
|
|
FReflectionCaptureCacheEntry Entry = *Current;
|
|
Entry.RefCount = ReferenceCount;
|
|
|
|
CaptureData.Remove(OldBuildIdCopy);
|
|
CaptureData.Shrink();
|
|
CaptureData.Add(Component->MapBuildDataId, Entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FReflectionCaptureCache::UnregisterComponentMapBuildDataId(const UReflectionCaptureComponent* Component)
|
|
{
|
|
RegisteredComponentMapBuildDataIds.Remove(Component);
|
|
}
|
|
|
|
void FReflectionEnvironmentSceneData::SetGameThreadTrackingData(int32 MaxAllocatedCubemaps, int32 CaptureSize, int32 DesiredCaptureSize)
|
|
{
|
|
MaxAllocatedReflectionCubemapsGameThread = MaxAllocatedCubemaps;
|
|
ReflectionCaptureSizeGameThread = CaptureSize;
|
|
DesiredReflectionCaptureSizeGameThread = DesiredCaptureSize;
|
|
}
|
|
|
|
bool FReflectionEnvironmentSceneData::DoesAllocatedDataNeedUpdate(int32 DesiredMaxCubemaps, int32 DesiredCaptureSize) const
|
|
{
|
|
if (DesiredMaxCubemaps != MaxAllocatedReflectionCubemapsGameThread)
|
|
return true;
|
|
|
|
if (DesiredCaptureSize != ReflectionCaptureSizeGameThread)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void FReflectionEnvironmentSceneData::ResizeCubemapArrayGPU(uint32 InMaxCubemaps, int32 InCubemapSize)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
// If the cubemap array isn't setup yet then no copying/reallocation is necessary. Just go through the old path
|
|
if (!CubemapArray.IsInitialized())
|
|
{
|
|
CubemapArraySlotsUsed.Init(false, InMaxCubemaps);
|
|
CubemapArray.UpdateMaxCubemaps(InMaxCubemaps, InCubemapSize);
|
|
return;
|
|
}
|
|
|
|
// Generate a remapping table for the elements
|
|
TArray<int32> IndexRemapping;
|
|
int32 Count = 0;
|
|
for (int i = 0; i < CubemapArray.GetMaxCubemaps(); i++)
|
|
{
|
|
bool bUsed = i < CubemapArraySlotsUsed.Num() ? CubemapArraySlotsUsed[i] : false;
|
|
if (bUsed)
|
|
{
|
|
IndexRemapping.Add(Count);
|
|
Count++;
|
|
}
|
|
else
|
|
{
|
|
IndexRemapping.Add(-1);
|
|
}
|
|
}
|
|
|
|
// Reset the CubemapArraySlotsUsed array (we'll recompute it below)
|
|
CubemapArraySlotsUsed.Init(false, InMaxCubemaps);
|
|
|
|
// Spin through the AllocatedReflectionCaptureState map and remap the indices based on the LUT
|
|
TArray<FGuid> Components;
|
|
AllocatedReflectionCaptureState.GetKeys(Components);
|
|
int32 UsedCubemapCount = 0;
|
|
for (int32 i=0; i<Components.Num(); i++ )
|
|
{
|
|
FCaptureComponentSceneState* ComponentStatePtr = AllocatedReflectionCaptureState.Find(Components[i]);
|
|
check(ComponentStatePtr->CubemapIndex < IndexRemapping.Num());
|
|
int32 NewIndex = IndexRemapping[ComponentStatePtr->CubemapIndex];
|
|
CubemapArraySlotsUsed[NewIndex] = true;
|
|
ComponentStatePtr->CubemapIndex = NewIndex;
|
|
check(ComponentStatePtr->CubemapIndex > -1);
|
|
UsedCubemapCount = FMath::Max(UsedCubemapCount, ComponentStatePtr->CubemapIndex + 1);
|
|
}
|
|
|
|
// Clear elements in the remapping array which are outside the range of the used components (these were allocated but not used)
|
|
for (int i = 0; i < IndexRemapping.Num(); i++)
|
|
{
|
|
if (IndexRemapping[i] >= UsedCubemapCount)
|
|
{
|
|
IndexRemapping[i] = -1;
|
|
}
|
|
}
|
|
|
|
CubemapArray.ResizeCubemapArrayGPU(InMaxCubemaps, InCubemapSize, IndexRemapping);
|
|
}
|
|
|
|
void FReflectionEnvironmentSceneData::Reset(FScene* Scene)
|
|
{
|
|
for (TSparseArray<UReflectionCaptureComponent*>::TIterator It(AllocatedReflectionCapturesGameThread); It; ++It)
|
|
{
|
|
UReflectionCaptureComponent* CurrentCapture = *It;
|
|
|
|
// Unused slots will have a null component pointer
|
|
if (CurrentCapture)
|
|
{
|
|
Scene->RemoveReflectionCapture(CurrentCapture);
|
|
}
|
|
}
|
|
|
|
// Fields to reset in the game thread
|
|
AllocatedReflectionCapturesGameThread.Empty();
|
|
MaxAllocatedReflectionCubemapsGameThread = 0;
|
|
ReflectionCaptureSizeGameThread = 0;
|
|
DesiredReflectionCaptureSizeGameThread = 0;
|
|
|
|
ENQUEUE_RENDER_COMMAND(ReflectionEnvironmentSceneDataReset)(
|
|
[this](FRHICommandList& RHICmdList)
|
|
{
|
|
// Fields to reset in the render thread
|
|
bRegisteredReflectionCapturesHasChanged = true;
|
|
AllocatedReflectionCaptureStateHasChanged = false;
|
|
|
|
ReflectionCaptureUniformBuffer.SafeRelease();
|
|
MobileReflectionCaptureUniformBuffer.SafeRelease();
|
|
|
|
RegisteredReflectionCaptures.Empty();
|
|
RegisteredReflectionCapturePositionAndRadius.Empty();
|
|
CubemapArray.Reset();
|
|
AllocatedReflectionCaptureState.Empty();
|
|
CubemapArraySlotsUsed.Empty();
|
|
SortedCaptures.Empty();
|
|
NumBoxCaptures = 0;
|
|
NumSphereCaptures = 0;
|
|
});
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::ResizeCubemapArrayGPU(uint32 InMaxCubemaps, int32 InCubemapSize, const TArray<int32>& IndexRemapping)
|
|
{
|
|
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
|
|
|
|
check(InMaxCubemaps > 0);
|
|
check(InCubemapSize > 0);
|
|
check(IsInitialized());
|
|
check(InCubemapSize == CubemapSize);
|
|
|
|
// Take a reference to the old cubemap array and then release it to prevent it getting destroyed during InitRHI
|
|
TRefCountPtr<IPooledRenderTarget> OldReflectionEnvs = ReflectionEnvs;
|
|
ReflectionEnvs = nullptr;
|
|
int OldMaxCubemaps = MaxCubemaps;
|
|
MaxCubemaps = InMaxCubemaps;
|
|
|
|
InitRHI(RHICmdList);
|
|
|
|
FTextureRHIRef TexRef = OldReflectionEnvs->GetRHI();
|
|
const int32 NumMips = FMath::CeilLogTwo(InCubemapSize) + 1;
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ReflectionEnvironment_ResizeCubemapArray);
|
|
|
|
RHICmdList.Transition({
|
|
FRHITransitionInfo(OldReflectionEnvs->GetRHI(), ERHIAccess::Unknown, ERHIAccess::CopySrc),
|
|
FRHITransitionInfo(ReflectionEnvs->GetRHI(), ERHIAccess::Unknown, ERHIAccess::CopyDest)
|
|
});
|
|
|
|
// Copy the cubemaps, remapping the elements as necessary
|
|
for (int32 SourceCubemapIndex = 0; SourceCubemapIndex < OldMaxCubemaps; SourceCubemapIndex++)
|
|
{
|
|
int32 DestCubemapIndex = IndexRemapping[SourceCubemapIndex];
|
|
if (DestCubemapIndex != -1)
|
|
{
|
|
check(SourceCubemapIndex < OldMaxCubemaps);
|
|
check(DestCubemapIndex < (int32)MaxCubemaps);
|
|
|
|
for (int32 Face = 0; Face < CubeFace_MAX; Face++)
|
|
{
|
|
FRHICopyTextureInfo CopyInfo;
|
|
CopyInfo.SourceSliceIndex = SourceCubemapIndex * CubeFace_MAX + Face;
|
|
CopyInfo.DestSliceIndex = DestCubemapIndex * CubeFace_MAX + Face;
|
|
CopyInfo.NumMips = NumMips;
|
|
|
|
RHICmdList.CopyTexture(OldReflectionEnvs->GetRHI(), ReflectionEnvs->GetRHI(), CopyInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
RHICmdList.Transition({
|
|
FRHITransitionInfo(OldReflectionEnvs->GetRHI(), ERHIAccess::CopySrc, ERHIAccess::SRVMask),
|
|
FRHITransitionInfo(ReflectionEnvs->GetRHI(), ERHIAccess::CopyDest, ERHIAccess::SRVMask)
|
|
});
|
|
}
|
|
GRenderTargetPool.FreeUnusedResource(OldReflectionEnvs);
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::UpdateMaxCubemaps(uint32 InMaxCubemaps, int32 InCubemapSize)
|
|
{
|
|
check(InMaxCubemaps > 0);
|
|
check(InCubemapSize > 0);
|
|
MaxCubemaps = InMaxCubemaps;
|
|
CubemapSize = InCubemapSize;
|
|
|
|
FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get();
|
|
|
|
// Reallocate the cubemap array
|
|
if (IsInitialized())
|
|
{
|
|
UpdateRHI(RHICmdList);
|
|
}
|
|
else
|
|
{
|
|
InitResource(RHICmdList);
|
|
}
|
|
}
|
|
|
|
void FReflectionEnvironmentCubemapArray::Reset()
|
|
{
|
|
ReleaseRHI();
|
|
MaxCubemaps = 0;
|
|
CubemapSize = 0;
|
|
}
|
|
|
|
IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(ReflectionCapture);
|
|
IMPLEMENT_STATIC_AND_SHADER_UNIFORM_BUFFER_STRUCT(FReflectionCaptureShaderData, "ReflectionCaptureSM5", ReflectionCapture);
|
|
IMPLEMENT_STATIC_AND_SHADER_UNIFORM_BUFFER_STRUCT_EX(FMobileReflectionCaptureShaderData, "ReflectionCaptureES31", ReflectionCapture, FShaderParametersMetadata::EUsageFlags::NoEmulatedUniformBuffer);
|
|
|
|
void SetupSkyIrradianceEnvironmentMapConstantsFromSkyIrradiance(FVector4f* OutSkyIrradianceEnvironmentMap, const FSHVectorRGB3 SkyIrradiance)
|
|
{
|
|
const float SqrtPI = FMath::Sqrt(PI);
|
|
const float Coefficient0 = 1.0f / (2 * SqrtPI);
|
|
const float Coefficient1 = FMath::Sqrt(3.f) / (3 * SqrtPI);
|
|
const float Coefficient2 = FMath::Sqrt(15.f) / (8 * SqrtPI);
|
|
const float Coefficient3 = FMath::Sqrt(5.f) / (16 * SqrtPI);
|
|
const float Coefficient4 = .5f * Coefficient2;
|
|
|
|
// Pack the SH coefficients in a way that makes applying the lighting use the least shader instructions
|
|
// This has the diffuse convolution coefficients baked in
|
|
// See "Stupid Spherical Harmonics (SH) Tricks"
|
|
OutSkyIrradianceEnvironmentMap[0].X = -Coefficient1 * SkyIrradiance.R.V[3];
|
|
OutSkyIrradianceEnvironmentMap[0].Y = -Coefficient1 * SkyIrradiance.R.V[1];
|
|
OutSkyIrradianceEnvironmentMap[0].Z = Coefficient1 * SkyIrradiance.R.V[2];
|
|
OutSkyIrradianceEnvironmentMap[0].W = Coefficient0 * SkyIrradiance.R.V[0] - Coefficient3 * SkyIrradiance.R.V[6];
|
|
|
|
OutSkyIrradianceEnvironmentMap[1].X = -Coefficient1 * SkyIrradiance.G.V[3];
|
|
OutSkyIrradianceEnvironmentMap[1].Y = -Coefficient1 * SkyIrradiance.G.V[1];
|
|
OutSkyIrradianceEnvironmentMap[1].Z = Coefficient1 * SkyIrradiance.G.V[2];
|
|
OutSkyIrradianceEnvironmentMap[1].W = Coefficient0 * SkyIrradiance.G.V[0] - Coefficient3 * SkyIrradiance.G.V[6];
|
|
|
|
OutSkyIrradianceEnvironmentMap[2].X = -Coefficient1 * SkyIrradiance.B.V[3];
|
|
OutSkyIrradianceEnvironmentMap[2].Y = -Coefficient1 * SkyIrradiance.B.V[1];
|
|
OutSkyIrradianceEnvironmentMap[2].Z = Coefficient1 * SkyIrradiance.B.V[2];
|
|
OutSkyIrradianceEnvironmentMap[2].W = Coefficient0 * SkyIrradiance.B.V[0] - Coefficient3 * SkyIrradiance.B.V[6];
|
|
|
|
OutSkyIrradianceEnvironmentMap[3].X = Coefficient2 * SkyIrradiance.R.V[4];
|
|
OutSkyIrradianceEnvironmentMap[3].Y = -Coefficient2 * SkyIrradiance.R.V[5];
|
|
OutSkyIrradianceEnvironmentMap[3].Z = 3 * Coefficient3 * SkyIrradiance.R.V[6];
|
|
OutSkyIrradianceEnvironmentMap[3].W = -Coefficient2 * SkyIrradiance.R.V[7];
|
|
|
|
OutSkyIrradianceEnvironmentMap[4].X = Coefficient2 * SkyIrradiance.G.V[4];
|
|
OutSkyIrradianceEnvironmentMap[4].Y = -Coefficient2 * SkyIrradiance.G.V[5];
|
|
OutSkyIrradianceEnvironmentMap[4].Z = 3 * Coefficient3 * SkyIrradiance.G.V[6];
|
|
OutSkyIrradianceEnvironmentMap[4].W = -Coefficient2 * SkyIrradiance.G.V[7];
|
|
|
|
OutSkyIrradianceEnvironmentMap[5].X = Coefficient2 * SkyIrradiance.B.V[4];
|
|
OutSkyIrradianceEnvironmentMap[5].Y = -Coefficient2 * SkyIrradiance.B.V[5];
|
|
OutSkyIrradianceEnvironmentMap[5].Z = 3 * Coefficient3 * SkyIrradiance.B.V[6];
|
|
OutSkyIrradianceEnvironmentMap[5].W = -Coefficient2 * SkyIrradiance.B.V[7];
|
|
|
|
OutSkyIrradianceEnvironmentMap[6].X = Coefficient4 * SkyIrradiance.R.V[8];
|
|
OutSkyIrradianceEnvironmentMap[6].Y = Coefficient4 * SkyIrradiance.G.V[8];
|
|
OutSkyIrradianceEnvironmentMap[6].Z = Coefficient4 * SkyIrradiance.B.V[8];
|
|
OutSkyIrradianceEnvironmentMap[6].W = 1;
|
|
}
|
|
|
|
void UpdateSkyIrradianceGpuBuffer(FRDGBuilder& GraphBuilder, const FEngineShowFlags& EngineShowFlags, const FSkyLightSceneProxy* SkyLight, TRefCountPtr<FRDGPooledBuffer>& Buffer)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateSkyIrradianceGpuBuffer);
|
|
|
|
const bool bUploadIrradiance =
|
|
SkyLight
|
|
// Skylights with static lighting already had their diffuse contribution baked into lightmaps
|
|
&& !SkyLight->bHasStaticLighting
|
|
&& EngineShowFlags.SkyLighting
|
|
&& !SkyLight->bRealTimeCaptureEnabled; // When bRealTimeCaptureEnabled is true, the buffer will be setup on GPU directly in this case
|
|
|
|
FRDGBufferDesc BufferDesc = FRDGBufferDesc::CreateStructuredDesc(sizeof(FVector4f), SKY_IRRADIANCE_ENVIRONMENT_MAP_VEC4_COUNT);
|
|
BufferDesc.Usage |= EBufferUsageFlags::SourceCopy;
|
|
const bool bNewAlloc = AllocatePooledBuffer(
|
|
BufferDesc,
|
|
Buffer,
|
|
TEXT("SkyIrradianceEnvironmentMap"),
|
|
ERDGPooledBufferAlignment::None);
|
|
|
|
FRDGBufferRef BufferRDG = GraphBuilder.RegisterExternalBuffer(Buffer, TEXT("SkyIrradianceEnvironmentMap"));
|
|
const uint32 BufferSizeInBytes = BufferRDG->GetSize();
|
|
check(BufferRDG);
|
|
|
|
if (bNewAlloc && !bUploadIrradiance)
|
|
{
|
|
// Ensure that sky irradiance SH buffer contains sensible initial values (zero init).
|
|
// If there is no sky in the level, then nothing else may fill this buffer.
|
|
void* InitialValue = GraphBuilder.Alloc(BufferSizeInBytes);
|
|
FMemory::Memset(InitialValue, 0, BufferSizeInBytes);
|
|
GraphBuilder.QueueBufferUpload(BufferRDG, InitialValue, BufferSizeInBytes, ERDGInitialDataFlags::NoCopy);
|
|
}
|
|
|
|
if (bUploadIrradiance)
|
|
{
|
|
FVector4f* UploadData = (FVector4f*)GraphBuilder.Alloc(BufferSizeInBytes);
|
|
SetupSkyIrradianceEnvironmentMapConstantsFromSkyIrradiance(UploadData, SkyLight->IrradianceEnvironmentMap);
|
|
|
|
const float SkyLightAverageBrightness = SkyLight->AverageBrightness;
|
|
UploadData[7] = FVector4f(SkyLightAverageBrightness, SkyLightAverageBrightness, SkyLightAverageBrightness, SkyLightAverageBrightness);
|
|
|
|
GraphBuilder.QueueBufferUpload(BufferRDG, UploadData, BufferSizeInBytes, ERDGInitialDataFlags::NoCopy);
|
|
}
|
|
|
|
Buffer = ConvertToExternalAccessBuffer(GraphBuilder, BufferRDG, ERHIAccess::SRVMask, ERHIPipeline::All);
|
|
} |