IVSmoke 1.0
Loading...
Searching...
No Matches
IVSmokeCSMRenderer.cpp
1// Copyright (c) 2026, Team SDB. All rights reserved.
2
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"
9
10DEFINE_LOG_CATEGORY_STATIC(LogIVSmokeCSM, Log, All);
11
12//~==============================================================================
13// Constructor / Destructor
14
15FIVSmokeCSMRenderer::FIVSmokeCSMRenderer()
16{
17}
18
19FIVSmokeCSMRenderer::~FIVSmokeCSMRenderer()
20{
21 Shutdown();
22}
23
24//~==============================================================================
25// Lifecycle
26
27void FIVSmokeCSMRenderer::Initialize(UWorld* World, int32 NumCascades, int32 Resolution, float MaxDistance)
28{
29 if (!World)
30 {
31 UE_LOG(LogIVSmokeCSM, Error, TEXT("[FIVSmokeCSMRenderer::Initialize] World is null"));
32 return;
33 }
34
35 // Clean up existing resources
36 Shutdown();
37
38 // Clamp parameters
39 NumCascades = FMath::Clamp(NumCascades, 1, 8);
40 Resolution = FMath::Clamp(Resolution, 256, 2048);
41 MaxDistance = FMath::Max(MaxDistance, 1000.0f);
42
43 CurrentResolution = Resolution;
44 MaxShadowDistance = MaxDistance;
45
46 // Load settings
47 const UIVSmokeSettings* Settings = UIVSmokeSettings::Get();
48 if (Settings)
49 {
50 LogLinearBlend = Settings->CascadeLogLinearBlend;
51 bEnablePriorityUpdate = Settings->bEnablePriorityUpdate;
52 NearCascadeUpdateInterval = Settings->NearCascadeUpdateInterval;
53 FarCascadeUpdateInterval = Settings->FarCascadeUpdateInterval;
54 }
55
56 // Create owner actor for capture components
57 FActorSpawnParameters SpawnParams;
58 SpawnParams.ObjectFlags |= RF_Transient;
59 // Don't specify a fixed name - let the engine generate a unique one to avoid conflicts
60 // when transitioning between Editor Preview and PIE (both may have the level loaded)
61 AActor* Owner = World->SpawnActor<AActor>(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
62 if (!Owner)
63 {
64 UE_LOG(LogIVSmokeCSM, Error, TEXT("[FIVSmokeCSMRenderer::Initialize] Failed to create capture owner actor"));
65 return;
66 }
67 CaptureOwner = Owner;
68
69 // Initialize cascades
70 Cascades.SetNum(NumCascades);
71 for (int32 i = 0; i < NumCascades; i++)
72 {
73 FIVSmokeCascadeData& Cascade = Cascades[i];
74 Cascade.CascadeIndex = i;
75
76 // Create capture component
77 USceneCaptureComponent2D* CaptureComp = NewObject<USceneCaptureComponent2D>(Owner);
78 CaptureComp->RegisterComponent();
79 CaptureComp->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
80 ConfigureCaptureComponent(CaptureComp);
81 Cascade.CaptureComponent = CaptureComp;
82
83 // Create render targets
84 CreateCascadeRenderTargets(Cascade, Resolution);
85
86 // Assign depth RT to capture component
87 CaptureComp->TextureTarget = Cascade.DepthRT;
88 }
89
90 // Calculate initial cascade splits
91 CalculateCascadeSplits(NearPlaneDistance, MaxDistance, LogLinearBlend);
92
93 bIsInitialized = true;
94
95 UE_LOG(LogIVSmokeCSM, Log, TEXT("[FIVSmokeCSMRenderer::Initialize] Initialized with %d cascades, %dx%d resolution, %.0f max distance"),
96 NumCascades, Resolution, Resolution, MaxDistance);
97}
98
100{
101 for (FIVSmokeCascadeData& Cascade : Cascades)
102 {
103 // Clean up render targets
104 if (Cascade.DepthRT)
105 {
106 Cascade.DepthRT->RemoveFromRoot();
107 Cascade.DepthRT = nullptr;
108 }
109 if (Cascade.VSMRT)
110 {
111 Cascade.VSMRT->RemoveFromRoot();
112 Cascade.VSMRT = nullptr;
113 }
114
115 // Capture component will be destroyed with owner
116 Cascade.CaptureComponent = nullptr;
117 }
118
119 Cascades.Empty();
120
121 // Destroy owner actor
122 if (CaptureOwner.IsValid())
123 {
124 CaptureOwner->Destroy();
125 CaptureOwner = nullptr;
126 }
127
128 bIsInitialized = false;
129
130 UE_LOG(LogIVSmokeCSM, Log, TEXT("[FIVSmokeCSMRenderer::Shutdown] CSM renderer shut down"));
131}
132
133//~==============================================================================
134// Update
135
137 const FVector& CameraPosition,
138 const FVector& CameraForward,
139 const FVector& LightDirection,
140 uint32 FrameNumber)
141{
142 if (!bIsInitialized || Cascades.Num() == 0)
143 {
144 return;
145 }
146
147 // Store main camera position for camera-relative calculations
148 MainCameraPosition = CameraPosition;
149
150 //~==========================================================================
151 // Validity Check - PIE restart can invalidate capture components
152 // Check CaptureOwner first (all components are attached to it).
153 // If owner is destroyed, all components are invalid.
154 if (!CaptureOwner.IsValid())
155 {
156 UE_LOG(LogIVSmokeCSM, Warning, TEXT("[FIVSmokeCSMRenderer::Update] CaptureOwner invalidated (PIE restart?), shutting down for reinitialization"));
157 Shutdown();
158 return;
159 }
160
161 // Also verify at least one capture component is valid
162 bool bAnyValidComponent = false;
163 for (const FIVSmokeCascadeData& Cascade : Cascades)
164 {
165 if (IsValid(Cascade.CaptureComponent))
166 {
167 bAnyValidComponent = true;
168 break;
169 }
170 }
171 if (!bAnyValidComponent)
172 {
173 UE_LOG(LogIVSmokeCSM, Warning, TEXT("[FIVSmokeCSMRenderer::Update] All CaptureComponents invalidated, shutting down for reinitialization"));
174 Shutdown();
175 return;
176 }
177
178 // Determine which cascades need update
179 UpdateCascadePriorities(FrameNumber);
180
181 // Update each cascade that needs it
182 for (int32 i = 0; i < Cascades.Num(); i++)
183 {
184 if (Cascades[i].bNeedsCapture)
185 {
186 UpdateCascadeCapture(i, CameraPosition, LightDirection);
187 Cascades[i].LastCaptureFrame = FrameNumber;
188 }
189 }
190}
191
192void FIVSmokeCSMRenderer::UpdateCascadePriorities(uint32 FrameNumber)
193{
194 // TODO: Priority update system disabled due to texel snapping synchronization issues.
195 // When a cascade is not updated, its texel-snapped position becomes stale relative to
196 // the current camera position, causing shadow flickering.
197 // Re-implement with proper synchronization: either update VP matrix every frame
198 // (even when not capturing), or use per-cascade camera positions for cascade selection.
199 for (FIVSmokeCascadeData& Cascade : Cascades)
200 {
201 Cascade.bNeedsCapture = true;
202 }
203}
204
205void FIVSmokeCSMRenderer::UpdateCascadeCapture(
206 int32 CascadeIndex,
207 const FVector& CameraPosition,
208 const FVector& LightDirection)
209{
210 if (!Cascades.IsValidIndex(CascadeIndex))
211 {
212 return;
213 }
214
215 FIVSmokeCascadeData& Cascade = Cascades[CascadeIndex];
216 if (!IsValid(Cascade.CaptureComponent))
217 {
218 UE_LOG(LogIVSmokeCSM, Warning, TEXT("[FIVSmokeCSMRenderer::UpdateCascadeCapture] CaptureComponent for cascade %d is invalid"), CascadeIndex);
219 return;
220 }
221
222 // Normalize light direction
223 FVector NormalizedLightDir = LightDirection.GetSafeNormal();
224 if (NormalizedLightDir.IsNearlyZero())
225 {
226 NormalizedLightDir = FVector(0.0f, 0.0f, 1.0f);
227 }
228
229 //~==========================================================================
230 // CSM Camera Positioning
231 //
232 // OrthoWidth covers the cascade's view frustum at its far distance.
233 // For a reasonable FOV (~90°), width at distance D is roughly 2*D.
234 float NewOrthoWidth = Cascade.FarPlane * 2.0f;
235
236 // Calculate light view axes
237 FVector LightForward = -NormalizedLightDir; // Camera looks opposite to light direction
238 FVector LightRight = FVector::CrossProduct(NormalizedLightDir, FVector::UpVector);
239 if (LightRight.IsNearlyZero())
240 {
241 LightRight = FVector::CrossProduct(NormalizedLightDir, FVector::ForwardVector);
242 }
243 LightRight.Normalize();
244 FVector LightUp = FVector::CrossProduct(LightRight, LightForward).GetSafeNormal();
245
246 // Position shadow camera far enough to see all shadow casters
247 float CaptureDistance = MaxShadowDistance * 1.5f;
248 FVector BaseCapturePosition = CameraPosition + NormalizedLightDir * CaptureDistance;
249
250 //~==========================================================================
251 // Texel Snapping - Use SMALLEST cascade's texel size for ALL cascades
252 //
253 // CRITICAL: All cascades must snap to the SAME grid to ensure:
254 // 1. Same WorldPos maps to same relative UV across cascades
255 // 2. Minimal shadow shimmer during camera movement
256 // 3. Smooth cascade transitions without edge artifacts
257 //
258 // Uses cascade 0's texel size (smallest = finest grid) for consistency.
259 double SmallestOrthoWidth = (double)Cascades[0].FarPlane * 2.0;
260 double TexelSize = SmallestOrthoWidth / (double)CurrentResolution;
261
262 // Project PLAYER CAMERA position onto light view axes
263 double PlayerRightOffset = FVector::DotProduct(CameraPosition, LightRight);
264 double PlayerUpOffset = FVector::DotProduct(CameraPosition, LightUp);
265
266 // Snap to unified grid (same for all cascades)
267 double SnappedRight = FMath::FloorToDouble(PlayerRightOffset / TexelSize) * TexelSize;
268 double SnappedUp = FMath::FloorToDouble(PlayerUpOffset / TexelSize) * TexelSize;
269
270 // Calculate adjustment
271 FVector SnapAdjustment = LightRight * (float)(SnappedRight - PlayerRightOffset)
272 + LightUp * (float)(SnappedUp - PlayerUpOffset);
273
274 FVector SnappedPosition = BaseCapturePosition + SnapAdjustment;
275
276 //~==========================================================================
277 // Store current frame values (used by both capture and shader)
278 Cascade.OrthoWidth = NewOrthoWidth;
279 Cascade.LightCameraPosition = SnappedPosition;
280 Cascade.LightCameraForward = LightForward;
281
282 // Apply to capture component
283 FRotator CaptureRotation = LightForward.Rotation();
284 Cascade.CaptureComponent->SetWorldLocationAndRotation(SnappedPosition, CaptureRotation);
285 Cascade.CaptureComponent->OrthoWidth = NewOrthoWidth;
286
287 // Calculate view-projection matrix
288 CalculateViewProjectionMatrix(Cascade);
289
290 //~==========================================================================
291 // Synchronous Capture - ensures VP matrix and depth texture match
292 Cascade.CaptureComponent->CaptureScene();
293
294 UE_LOG(LogIVSmokeCSM, Verbose, TEXT("[FIVSmokeCSMRenderer::UpdateCascadeCapture] Cascade %d: Near=%.0f, Far=%.0f, OrthoWidth=%.0f"),
295 CascadeIndex, Cascade.NearPlane, Cascade.FarPlane, NewOrthoWidth);
296}
297
298//~==============================================================================
299// Cascade Split Calculation
300
301void FIVSmokeCSMRenderer::CalculateCascadeSplits(float NearPlane, float FarPlane, float LogLinearBlendFactor)
302{
303 const int32 NumCascades = Cascades.Num();
304 if (NumCascades == 0)
305 {
306 return;
307 }
308
309 // Ensure minimum near plane
310 NearPlane = FMath::Max(NearPlane, 1.0f);
311
312 for (int32 i = 0; i < NumCascades; i++)
313 {
314 float t = (float)(i + 1) / (float)NumCascades;
315
316 // Linear distribution
317 float Linear = NearPlane + (FarPlane - NearPlane) * t;
318
319 // Logarithmic distribution (better near/far balance)
320 float Log = NearPlane * FMath::Pow(FarPlane / NearPlane, t);
321
322 // Blend between linear and logarithmic
323 float SplitDistance = FMath::Lerp(Linear, Log, LogLinearBlendFactor);
324
325 Cascades[i].FarPlane = SplitDistance;
326 Cascades[i].NearPlane = (i == 0) ? NearPlane : Cascades[i - 1].FarPlane;
327 }
328
329 UE_LOG(LogIVSmokeCSM, Log, TEXT("[FIVSmokeCSMRenderer::CalculateCascadeSplits] Splits calculated (LogLinear=%.2f):"), LogLinearBlendFactor);
330 for (int32 i = 0; i < NumCascades; i++)
331 {
332 UE_LOG(LogIVSmokeCSM, Log, TEXT(" Cascade %d: %.0f - %.0f cm"), i, Cascades[i].NearPlane, Cascades[i].FarPlane);
333 }
334}
335
336//~==============================================================================
337// Texel Snapping
338
339FVector FIVSmokeCSMRenderer::ApplyTexelSnapping(
340 const FVector& LightViewOrigin,
341 float OrthoWidth,
342 int32 Resolution,
343 const FVector& LightRight,
344 const FVector& LightUp) const
345{
346 // Calculate world-space texel size
347 // Use double precision for large coordinates to avoid float precision issues
348 double TexelSize = (double)OrthoWidth / (double)Resolution;
349
350 // Project position onto light view axes using double precision
351 // This is critical when coordinates are large (e.g., 100,000 cm)
352 double RightOffset = FVector::DotProduct(LightViewOrigin, LightRight);
353 double UpOffset = FVector::DotProduct(LightViewOrigin, LightUp);
354
355 // Snap to texel grid using Floor (not Round) for stability
356 // Round can cause jittering when value oscillates around X.5
357 double SnappedRight = FMath::FloorToDouble(RightOffset / TexelSize) * TexelSize;
358 double SnappedUp = FMath::FloorToDouble(UpOffset / TexelSize) * TexelSize;
359
360 // Calculate adjustment delta
361 double DeltaRight = SnappedRight - RightOffset;
362 double DeltaUp = SnappedUp - UpOffset;
363
364 // Apply snapped offset
365 FVector Snapped = LightViewOrigin;
366 Snapped += LightRight * (float)DeltaRight;
367 Snapped += LightUp * (float)DeltaUp;
368
369 return Snapped;
370}
371
372//~==============================================================================
373// View-Projection Matrix
374
375void FIVSmokeCSMRenderer::CalculateViewProjectionMatrix(FIVSmokeCascadeData& Cascade)
376{
377 //~==========================================================================
378 // Calculate VP Matrix to MATCH SceneCaptureComponent2D's actual rendering
379 //
380 // We must use the EXACT same method that UE uses for SceneCaptureComponent2D
381 // to ensure our VP matrix matches the captured texture.
382 //
383 // This is read from CaptureComponent to ensure we use the exact same values
384 // that the capture will use (after any engine-side adjustments).
385
386 if (!IsValid(Cascade.CaptureComponent))
387 {
388 return;
389 }
390
391 // Get the actual transform that will be used for capture
392 FVector CameraLocation = Cascade.CaptureComponent->GetComponentLocation();
393 FRotator CameraRotation = Cascade.CaptureComponent->GetComponentRotation();
394
395 //~==========================================================================
396 // View Matrix - Match UE's FSceneView calculation
397 //
398 // UE calculates view matrix from component transform using:
399 // 1. Translation matrix (negative location)
400 // 2. Rotation matrix (inverse rotation + axis swap for UE coordinate system)
401 FMatrix ViewRotationMatrix = FInverseRotationMatrix(CameraRotation) * FMatrix(
402 FPlane(0, 0, 1, 0),
403 FPlane(1, 0, 0, 0),
404 FPlane(0, 1, 0, 0),
405 FPlane(0, 0, 0, 1)
406 );
407 FMatrix ViewMatrix = FTranslationMatrix(-CameraLocation) * ViewRotationMatrix;
408
409 //~==========================================================================
410 // Projection Matrix - Orthographic
411 float OrthoWidth = Cascade.CaptureComponent->OrthoWidth;
412 float HalfWidth = OrthoWidth * 0.5f;
413 float HalfHeight = HalfWidth; // Square projection
414
415 // Near/far planes - use large range to capture all shadow casters
416 float NearZ = 1.0f;
417 float FarZ = MaxShadowDistance * 3.0f;
418
419 // Build orthographic projection matrix (UE uses reversed-Z)
420 FMatrix ProjectionMatrix = FReversedZOrthoMatrix(HalfWidth, HalfHeight, 1.0f / (FarZ - NearZ), 0.0f);
421
422 // Combined view-projection matrix
423 Cascade.ViewProjectionMatrix = ViewMatrix * ProjectionMatrix;
424
425 // Update camera position/forward from component (ensure consistency)
426 Cascade.LightCameraPosition = CameraLocation;
427 Cascade.LightCameraForward = CameraRotation.Vector();
428}
429
430//~==============================================================================
431// Render Target Creation
432
433void FIVSmokeCSMRenderer::CreateCascadeRenderTargets(FIVSmokeCascadeData& Cascade, int32 Resolution)
434{
435 // Create depth render target (R32F)
436 Cascade.DepthRT = NewObject<UTextureRenderTarget2D>();
437 Cascade.DepthRT->AddToRoot(); // Prevent GC
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);
444
445 // Create VSM render target (RG32F) with UAV support for compute shader processing
446 const UIVSmokeSettings* Settings = UIVSmokeSettings::Get();
447 if (Settings && Settings->bEnableVSM)
448 {
449 Cascade.VSMRT = NewObject<UTextureRenderTarget2D>();
450 Cascade.VSMRT->AddToRoot();
451 Cascade.VSMRT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RG32f;
452 Cascade.VSMRT->bCanCreateUAV = true; // Required for VSM compute shader processing
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);
458 }
459
460 UE_LOG(LogIVSmokeCSM, Verbose, TEXT("[FIVSmokeCSMRenderer::CreateCascadeRenderTargets] Created RTs for cascade %d: %dx%d"),
461 Cascade.CascadeIndex, Resolution, Resolution);
462}
463
464void FIVSmokeCSMRenderer::ConfigureCaptureComponent(USceneCaptureComponent2D* CaptureComponent)
465{
466 if (!CaptureComponent)
467 {
468 return;
469 }
470
471 // Orthographic projection for directional light
472 CaptureComponent->ProjectionType = ECameraProjectionMode::Orthographic;
473
474 // Capture scene depth
475 CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_SceneDepth;
476
477 // Disable auto-capture - we call CaptureScene() manually in UpdateCascadeCapture()
478 // for synchronous VP matrix and depth texture synchronization
479 CaptureComponent->bCaptureEveryFrame = false;
480 CaptureComponent->bCaptureOnMovement = false;
481
482 // Disable auto-calculate for consistency
483 CaptureComponent->bAutoCalculateOrthoPlanes = false;
484
485 // Use scene primitives
486 CaptureComponent->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_RenderScenePrimitives;
487
488 // Persist rendering state for quality
489 CaptureComponent->bAlwaysPersistRenderingState = true;
490
491 //~==========================================================================
492 // ShowFlags Optimization for Depth-Only Shadow Capture
493 //
494 // NOTE: Nanite must stay ENABLED - fallback meshes don't write depth properly.
495 // Disable only rendering features that don't affect depth output.
496
497 // --- Disable lighting/shading (not needed for depth) ---
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);
505
506 // --- Disable post-processing (not needed for depth) ---
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);
516
517 // --- Disable atmosphere/fog (not needed for depth) ---
518 CaptureComponent->ShowFlags.SetAtmosphere(false);
519 CaptureComponent->ShowFlags.SetFog(false);
520 CaptureComponent->ShowFlags.SetVolumetricFog(false);
521
522 // --- Disable shadows (we're creating shadows, not receiving) ---
523 CaptureComponent->ShowFlags.SetDynamicShadows(false);
524 CaptureComponent->ShowFlags.SetContactShadows(false);
525
526 // --- Disable non-shadow-casting elements ---
527 CaptureComponent->ShowFlags.SetTranslucency(false);
528 CaptureComponent->ShowFlags.SetParticles(false);
529 CaptureComponent->ShowFlags.SetDecals(false);
530
531 // --- Optionally disable skeletal meshes (characters) ---
532 const UIVSmokeSettings* Settings = UIVSmokeSettings::Get();
533 if (Settings && !Settings->bCaptureSkeletalMeshes)
534 {
535 CaptureComponent->ShowFlags.SetSkeletalMeshes(false);
536 }
537}
538
539//~==============================================================================
540// Accessors
541
543{
544 TArray<float> Splits;
545 Splits.Reserve(Cascades.Num());
546
547 for (const FIVSmokeCascadeData& Cascade : Cascades)
548 {
549 Splits.Add(Cascade.FarPlane);
550 }
551
552 return Splits;
553}
554
555FTextureRHIRef FIVSmokeCSMRenderer::GetVSMTexture(int32 CascadeIndex) const
556{
557 if (!Cascades.IsValidIndex(CascadeIndex) || !Cascades[CascadeIndex].VSMRT)
558 {
559 return nullptr;
560 }
561
562 FTextureRenderTargetResource* Resource = Cascades[CascadeIndex].VSMRT->GameThread_GetRenderTargetResource();
563 return Resource ? Resource->TextureRHI : nullptr;
564}
565
566FTextureRHIRef FIVSmokeCSMRenderer::GetDepthTexture(int32 CascadeIndex) const
567{
568 if (!Cascades.IsValidIndex(CascadeIndex) || !Cascades[CascadeIndex].DepthRT)
569 {
570 return nullptr;
571 }
572
573 FTextureRenderTargetResource* Resource = Cascades[CascadeIndex].DepthRT->GameThread_GetRenderTargetResource();
574 return Resource ? Resource->TextureRHI : nullptr;
575}
576
578{
579 if (!bIsInitialized || Cascades.Num() == 0)
580 {
581 return false;
582 }
583
584 // Check if at least cascade 0 has a valid render target
585 return Cascades[0].DepthRT != nullptr && Cascades[0].DepthRT->GetResource() != nullptr;
586}
587
588FVector FIVSmokeCSMRenderer::GetLightCameraPosition(int32 CascadeIndex) const
589{
590 if (!Cascades.IsValidIndex(CascadeIndex))
591 {
592 return FVector::ZeroVector;
593 }
594
595 return Cascades[CascadeIndex].LightCameraPosition;
596}
597
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)
FTextureRHIRef GetDepthTexture(int32 CascadeIndex) const
TArray< float > GetSplitDistances() const
static const UIVSmokeSettings * Get()
TObjectPtr< UTextureRenderTarget2D > VSMRT
TObjectPtr< USceneCaptureComponent2D > CaptureComponent
TObjectPtr< UTextureRenderTarget2D > DepthRT