3#include "IVSmokeCSMRenderer.h"
4#include "IVSmokeSettings.h"
5#include "Components/SceneCaptureComponent2D.h"
6#include "Engine/TextureRenderTarget2D.h"
7#include "Engine/World.h"
8#include "GameFramework/Actor.h"
10DEFINE_LOG_CATEGORY_STATIC(LogIVSmokeCSM, Log, All);
15FIVSmokeCSMRenderer::FIVSmokeCSMRenderer()
19FIVSmokeCSMRenderer::~FIVSmokeCSMRenderer()
31 UE_LOG(LogIVSmokeCSM, Error, TEXT(
"[FIVSmokeCSMRenderer::Initialize] World is null"));
39 NumCascades = FMath::Clamp(NumCascades, 1, 8);
40 Resolution = FMath::Clamp(Resolution, 256, 2048);
41 MaxDistance = FMath::Max(MaxDistance, 1000.0f);
43 CurrentResolution = Resolution;
44 MaxShadowDistance = MaxDistance;
51 bEnablePriorityUpdate = Settings->bEnablePriorityUpdate;
52 NearCascadeUpdateInterval = Settings->NearCascadeUpdateInterval;
53 FarCascadeUpdateInterval = Settings->FarCascadeUpdateInterval;
57 FActorSpawnParameters SpawnParams;
58 SpawnParams.ObjectFlags |= RF_Transient;
61 AActor* Owner = World->SpawnActor<AActor>(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
64 UE_LOG(LogIVSmokeCSM, Error, TEXT(
"[FIVSmokeCSMRenderer::Initialize] Failed to create capture owner actor"));
70 Cascades.SetNum(NumCascades);
71 for (int32 i = 0; i < NumCascades; i++)
77 USceneCaptureComponent2D* CaptureComp = NewObject<USceneCaptureComponent2D>(Owner);
78 CaptureComp->RegisterComponent();
79 CaptureComp->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
80 ConfigureCaptureComponent(CaptureComp);
84 CreateCascadeRenderTargets(Cascade, Resolution);
87 CaptureComp->TextureTarget = Cascade.
DepthRT;
91 CalculateCascadeSplits(NearPlaneDistance, MaxDistance, LogLinearBlend);
93 bIsInitialized =
true;
95 UE_LOG(LogIVSmokeCSM, Log, TEXT(
"[FIVSmokeCSMRenderer::Initialize] Initialized with %d cascades, %dx%d resolution, %.0f max distance"),
96 NumCascades, Resolution, Resolution, MaxDistance);
106 Cascade.DepthRT->RemoveFromRoot();
107 Cascade.DepthRT =
nullptr;
111 Cascade.VSMRT->RemoveFromRoot();
112 Cascade.VSMRT =
nullptr;
116 Cascade.CaptureComponent =
nullptr;
122 if (CaptureOwner.IsValid())
124 CaptureOwner->Destroy();
125 CaptureOwner =
nullptr;
128 bIsInitialized =
false;
130 UE_LOG(LogIVSmokeCSM, Log, TEXT(
"[FIVSmokeCSMRenderer::Shutdown] CSM renderer shut down"));
137 const FVector& CameraPosition,
138 const FVector& CameraForward,
139 const FVector& LightDirection,
142 if (!bIsInitialized || Cascades.Num() == 0)
148 MainCameraPosition = CameraPosition;
154 if (!CaptureOwner.IsValid())
156 UE_LOG(LogIVSmokeCSM, Warning, TEXT(
"[FIVSmokeCSMRenderer::Update] CaptureOwner invalidated (PIE restart?), shutting down for reinitialization"));
162 bool bAnyValidComponent =
false;
165 if (IsValid(Cascade.CaptureComponent))
167 bAnyValidComponent =
true;
171 if (!bAnyValidComponent)
173 UE_LOG(LogIVSmokeCSM, Warning, TEXT(
"[FIVSmokeCSMRenderer::Update] All CaptureComponents invalidated, shutting down for reinitialization"));
179 UpdateCascadePriorities(FrameNumber);
182 for (int32 i = 0; i < Cascades.Num(); i++)
184 if (Cascades[i].bNeedsCapture)
186 UpdateCascadeCapture(i, CameraPosition, LightDirection);
187 Cascades[i].LastCaptureFrame = FrameNumber;
192void FIVSmokeCSMRenderer::UpdateCascadePriorities(uint32 FrameNumber)
201 Cascade.bNeedsCapture =
true;
205void FIVSmokeCSMRenderer::UpdateCascadeCapture(
207 const FVector& CameraPosition,
208 const FVector& LightDirection)
210 if (!Cascades.IsValidIndex(CascadeIndex))
218 UE_LOG(LogIVSmokeCSM, Warning, TEXT(
"[FIVSmokeCSMRenderer::UpdateCascadeCapture] CaptureComponent for cascade %d is invalid"), CascadeIndex);
223 FVector NormalizedLightDir = LightDirection.GetSafeNormal();
224 if (NormalizedLightDir.IsNearlyZero())
226 NormalizedLightDir = FVector(0.0f, 0.0f, 1.0f);
234 float NewOrthoWidth = Cascade.FarPlane * 2.0f;
237 FVector LightForward = -NormalizedLightDir;
238 FVector LightRight = FVector::CrossProduct(NormalizedLightDir, FVector::UpVector);
239 if (LightRight.IsNearlyZero())
241 LightRight = FVector::CrossProduct(NormalizedLightDir, FVector::ForwardVector);
243 LightRight.Normalize();
244 FVector LightUp = FVector::CrossProduct(LightRight, LightForward).GetSafeNormal();
247 float CaptureDistance = MaxShadowDistance * 1.5f;
248 FVector BaseCapturePosition = CameraPosition + NormalizedLightDir * CaptureDistance;
259 double SmallestOrthoWidth = (double)Cascades[0].FarPlane * 2.0;
260 double TexelSize = SmallestOrthoWidth / (double)CurrentResolution;
263 double PlayerRightOffset = FVector::DotProduct(CameraPosition, LightRight);
264 double PlayerUpOffset = FVector::DotProduct(CameraPosition, LightUp);
267 double SnappedRight = FMath::FloorToDouble(PlayerRightOffset / TexelSize) * TexelSize;
268 double SnappedUp = FMath::FloorToDouble(PlayerUpOffset / TexelSize) * TexelSize;
271 FVector SnapAdjustment = LightRight * (float)(SnappedRight - PlayerRightOffset)
272 + LightUp * (float)(SnappedUp - PlayerUpOffset);
274 FVector SnappedPosition = BaseCapturePosition + SnapAdjustment;
283 FRotator CaptureRotation = LightForward.Rotation();
284 Cascade.
CaptureComponent->SetWorldLocationAndRotation(SnappedPosition, CaptureRotation);
288 CalculateViewProjectionMatrix(Cascade);
294 UE_LOG(LogIVSmokeCSM, Verbose, TEXT(
"[FIVSmokeCSMRenderer::UpdateCascadeCapture] Cascade %d: Near=%.0f, Far=%.0f, OrthoWidth=%.0f"),
295 CascadeIndex, Cascade.
NearPlane, Cascade.FarPlane, NewOrthoWidth);
301void FIVSmokeCSMRenderer::CalculateCascadeSplits(
float NearPlane,
float FarPlane,
float LogLinearBlendFactor)
303 const int32 NumCascades = Cascades.Num();
304 if (NumCascades == 0)
310 NearPlane = FMath::Max(NearPlane, 1.0f);
312 for (int32 i = 0; i < NumCascades; i++)
314 float t = (float)(i + 1) / (float)NumCascades;
317 float Linear = NearPlane + (FarPlane - NearPlane) * t;
320 float Log = NearPlane * FMath::Pow(FarPlane / NearPlane, t);
323 float SplitDistance = FMath::Lerp(Linear, Log, LogLinearBlendFactor);
325 Cascades[i].FarPlane = SplitDistance;
326 Cascades[i].NearPlane = (i == 0) ? NearPlane : Cascades[i - 1].FarPlane;
329 UE_LOG(LogIVSmokeCSM, Log, TEXT(
"[FIVSmokeCSMRenderer::CalculateCascadeSplits] Splits calculated (LogLinear=%.2f):"), LogLinearBlendFactor);
330 for (int32 i = 0; i < NumCascades; i++)
332 UE_LOG(LogIVSmokeCSM, Log, TEXT(
" Cascade %d: %.0f - %.0f cm"), i, Cascades[i].NearPlane, Cascades[i].FarPlane);
339FVector FIVSmokeCSMRenderer::ApplyTexelSnapping(
340 const FVector& LightViewOrigin,
343 const FVector& LightRight,
344 const FVector& LightUp)
const
348 double TexelSize = (double)OrthoWidth / (
double)Resolution;
352 double RightOffset = FVector::DotProduct(LightViewOrigin, LightRight);
353 double UpOffset = FVector::DotProduct(LightViewOrigin, LightUp);
357 double SnappedRight = FMath::FloorToDouble(RightOffset / TexelSize) * TexelSize;
358 double SnappedUp = FMath::FloorToDouble(UpOffset / TexelSize) * TexelSize;
361 double DeltaRight = SnappedRight - RightOffset;
362 double DeltaUp = SnappedUp - UpOffset;
365 FVector Snapped = LightViewOrigin;
366 Snapped += LightRight * (float)DeltaRight;
367 Snapped += LightUp * (float)DeltaUp;
401 FMatrix ViewRotationMatrix = FInverseRotationMatrix(CameraRotation) * FMatrix(
407 FMatrix ViewMatrix = FTranslationMatrix(-CameraLocation) * ViewRotationMatrix;
412 float HalfWidth = OrthoWidth * 0.5f;
413 float HalfHeight = HalfWidth;
417 float FarZ = MaxShadowDistance * 3.0f;
420 FMatrix ProjectionMatrix = FReversedZOrthoMatrix(HalfWidth, HalfHeight, 1.0f / (FarZ - NearZ), 0.0f);
433void FIVSmokeCSMRenderer::CreateCascadeRenderTargets(
FIVSmokeCascadeData& Cascade, int32 Resolution)
436 Cascade.
DepthRT = NewObject<UTextureRenderTarget2D>();
438 Cascade.
DepthRT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_R32f;
439 Cascade.
DepthRT->InitAutoFormat(Resolution, Resolution);
440 Cascade.
DepthRT->AddressX = TextureAddress::TA_Clamp;
441 Cascade.
DepthRT->AddressY = TextureAddress::TA_Clamp;
442 Cascade.
DepthRT->ClearColor = FLinearColor::Black;
443 Cascade.
DepthRT->UpdateResourceImmediate(
true);
449 Cascade.
VSMRT = NewObject<UTextureRenderTarget2D>();
450 Cascade.
VSMRT->AddToRoot();
451 Cascade.
VSMRT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RG32f;
452 Cascade.
VSMRT->bCanCreateUAV =
true;
453 Cascade.
VSMRT->InitAutoFormat(Resolution, Resolution);
454 Cascade.
VSMRT->AddressX = TextureAddress::TA_Clamp;
455 Cascade.
VSMRT->AddressY = TextureAddress::TA_Clamp;
456 Cascade.
VSMRT->ClearColor = FLinearColor::Black;
457 Cascade.
VSMRT->UpdateResourceImmediate(
true);
460 UE_LOG(LogIVSmokeCSM, Verbose, TEXT(
"[FIVSmokeCSMRenderer::CreateCascadeRenderTargets] Created RTs for cascade %d: %dx%d"),
464void FIVSmokeCSMRenderer::ConfigureCaptureComponent(USceneCaptureComponent2D* CaptureComponent)
466 if (!CaptureComponent)
472 CaptureComponent->ProjectionType = ECameraProjectionMode::Orthographic;
475 CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_SceneDepth;
479 CaptureComponent->bCaptureEveryFrame =
false;
480 CaptureComponent->bCaptureOnMovement =
false;
483 CaptureComponent->bAutoCalculateOrthoPlanes =
false;
486 CaptureComponent->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_RenderScenePrimitives;
489 CaptureComponent->bAlwaysPersistRenderingState =
true;
498 CaptureComponent->ShowFlags.SetLighting(
false);
499 CaptureComponent->ShowFlags.SetGlobalIllumination(
false);
500 CaptureComponent->ShowFlags.SetLumenGlobalIllumination(
false);
501 CaptureComponent->ShowFlags.SetLumenReflections(
false);
502 CaptureComponent->ShowFlags.SetReflectionEnvironment(
false);
503 CaptureComponent->ShowFlags.SetAmbientOcclusion(
false);
504 CaptureComponent->ShowFlags.SetScreenSpaceReflections(
false);
507 CaptureComponent->ShowFlags.SetPostProcessing(
false);
508 CaptureComponent->ShowFlags.SetBloom(
false);
509 CaptureComponent->ShowFlags.SetMotionBlur(
false);
510 CaptureComponent->ShowFlags.SetToneCurve(
false);
511 CaptureComponent->ShowFlags.SetEyeAdaptation(
false);
512 CaptureComponent->ShowFlags.SetColorGrading(
false);
513 CaptureComponent->ShowFlags.SetDepthOfField(
false);
514 CaptureComponent->ShowFlags.SetVignette(
false);
515 CaptureComponent->ShowFlags.SetGrain(
false);
518 CaptureComponent->ShowFlags.SetAtmosphere(
false);
519 CaptureComponent->ShowFlags.SetFog(
false);
520 CaptureComponent->ShowFlags.SetVolumetricFog(
false);
523 CaptureComponent->ShowFlags.SetDynamicShadows(
false);
524 CaptureComponent->ShowFlags.SetContactShadows(
false);
527 CaptureComponent->ShowFlags.SetTranslucency(
false);
528 CaptureComponent->ShowFlags.SetParticles(
false);
529 CaptureComponent->ShowFlags.SetDecals(
false);
535 CaptureComponent->ShowFlags.SetSkeletalMeshes(
false);
544 TArray<float> Splits;
545 Splits.Reserve(Cascades.Num());
549 Splits.Add(Cascade.FarPlane);
557 if (!Cascades.IsValidIndex(CascadeIndex) || !Cascades[CascadeIndex].VSMRT)
562 FTextureRenderTargetResource* Resource = Cascades[CascadeIndex].VSMRT->GameThread_GetRenderTargetResource();
563 return Resource ? Resource->TextureRHI :
nullptr;
568 if (!Cascades.IsValidIndex(CascadeIndex) || !Cascades[CascadeIndex].DepthRT)
573 FTextureRenderTargetResource* Resource = Cascades[CascadeIndex].DepthRT->GameThread_GetRenderTargetResource();
574 return Resource ? Resource->TextureRHI :
nullptr;
579 if (!bIsInitialized || Cascades.Num() == 0)
585 return Cascades[0].DepthRT !=
nullptr && Cascades[0].DepthRT->GetResource() !=
nullptr;
590 if (!Cascades.IsValidIndex(CascadeIndex))
592 return FVector::ZeroVector;
595 return Cascades[CascadeIndex].LightCameraPosition;
FVector GetLightCameraPosition(int32 CascadeIndex) const
FTextureRHIRef GetVSMTexture(int32 CascadeIndex) const
void Initialize(UWorld *World, int32 NumCascades, int32 Resolution, float MaxDistance)
void Update(const FVector &CameraPosition, const FVector &CameraForward, const FVector &LightDirection, uint32 FrameNumber)
bool HasValidShadowData() const
FTextureRHIRef GetDepthTexture(int32 CascadeIndex) const
TArray< float > GetSplitDistances() const
float CascadeLogLinearBlend
static const UIVSmokeSettings * Get()
bool bCaptureSkeletalMeshes
FVector LightCameraPosition
TObjectPtr< UTextureRenderTarget2D > VSMRT
FMatrix ViewProjectionMatrix
TObjectPtr< USceneCaptureComponent2D > CaptureComponent
TObjectPtr< UTextureRenderTarget2D > DepthRT
FVector LightCameraForward