Files
UE5-Modular-Game-Framework/docs/developer/17-capture-systems.md

12 KiB

Developer Reference — Capture Systems (Systems 136-147)

Version: 1.0 | Systems: 136-147 (12 systems) | Phase: 17 | Layer: Rendering & Visual


Architecture Overview

The Planar Capture System provides a unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. All performance-critical work (camera math, render target management, scene capture lifecycle, quality budget enforcement) runs in C++, while designer-facing configuration, event scripting, and material authoring live in Blueprint.

System Layers

┌──────────────────────────────────────────────────────────────────────┐
│  BLUEPRINT CONTENT LAYER                                              │
│  BP_Mirror → BP_HorrorMirror     Designer places in level, configures │
│  BP_Portal → BP_Monitor → BP_FakeWindow                               │
│  DA_PlanarCaptureProfile → MPC_CaptureSurface → MI_* material instances│
├──────────────────────────────────────────────────────────────────────┤
│  C++ RUNTIME LAYER                                                    │
│  USS_PlanarCaptureManager     ← Global budget, RT pool, scoring (WorldSubsystem + FTickable) │
│  ABP_PlanarCaptureActor       ← Surface mesh, MDI, registration      │
│  UBPC_PlanarCapture           ← SceneCapture2D, camera math, horror  │
│  UPlanarCaptureCameraUtils    ← Static math: mirror, portal, oblique │
├──────────────────────────────────────────────────────────────────────┤
│  UE5 ENGINE LAYER                                                     │
│  USceneCaptureComponent2D  UTextureRenderTarget2D                    │
│  UMaterialParameterCollectionInstance  UWorldSubsystem               │
└──────────────────────────────────────────────────────────────────────┘

Data Flow: Mirror Reflection

Step-by-Step: Player Looks at a Mirror

  1. Registry: BP_Mirror.BeginPlay() calls SS_PlanarCaptureManager.RegisterSurface(this) — the mirror is now tracked globally.
  2. Evaluation (0.5s interval): SS_PlanarCaptureManager.Tick() calls EvaluateAllSurfaces().
  3. Scoring: For each registered surface, BPC_PlanarCapture.GetCurrentScore() computes:
    • Screen coverage (how much screen space does the surface occupy?)
    • Facing angle (is the player looking at the surface or edge-on?)
    • Distance to viewer
    • Scripted priority override
    • Visibility frustum check
  4. Tier Assignment: Based on composite score and budget limits, the manager calls BPC_PlanarCapture.ApplyQualityTier(Tier).
  5. Capture Tick (per frame): BPC_PlanarCapture.TickComponent() checks time since last capture vs the quality tier's CaptureInterval. If it's time to capture:
    • Gets the player camera transform
    • Calls ComputeCaptureCameraTransform() — for Mirror mode, this calls UPlanarCaptureCameraUtils::ComputeMirroredTransform() which reflects the player camera across the mirror plane
    • Sets the USceneCaptureComponent2D world transform
    • Calls SceneCapture->CaptureScene() — renders to the allocated UTextureRenderTarget2D
    • Calls PushMPCParameters() to update the MPC with current horror/visual params
  6. Material Display: M_CaptureSurface_Master samples the render target texture, applies UV flip (for mirror), and layers any active effects (dirt, steam, condensation). The output is displayed on the surface mesh.

Quality Tier Transitions

  Off ←──→ Low ←──→ Medium ←──→ High ←──→ Hero
  (score=0)  (>0)    (≥0.2)     (≥0.5)    (≥0.8)

  Demotion happens when:
  - Score drops below threshold (player walks away)
  - Budget limit exceeded (too many Hero surfaces)
  - GlobalQualityCap clamps tier
  - Force tier override is released

  Promotion happens when:
  - Score rises above threshold (player approaches)
  - Budget slots open up (higher-tier surface destroyed or goes Off)
  - Scripted priority boost kicks in

State Machine: Quality Tier Lifecycle

  [Surface Spawns]
       │
       ▼
  ┌─────────┐     score > 0     ┌─────────┐
  │   OFF   │ ────────────────► │   LOW   │
  └─────────┘                   └────┬────┘
       ▲                             │ score ≥ 0.2
       │ score = 0              ┌────▼────┐
       │ or !inFrustum          │ MEDIUM  │
       │                        └────┬────┘
       │                             │ score ≥ 0.5
       │                        ┌────▼────┐
       │                        │  HIGH   │
       │                        └────┬────┘
       │                             │ score ≥ 0.8
       │                        ┌────▼────┐
       │                        │  HERO   │
       │                        └─────────┘
       │
  ┌────┴────────┐
  │ SURFACE      │  Called on EndPlay, DisableSurface, or DestroySurface
  │ DESTROYED    │
  └─────────────┘

Budget Enforcement

The SS_PlanarCaptureManager enforces three levels of budget:

  1. Per-Tier Count Limits: MaxHeroSurfaces (default 1), MaxHighSurfaces (3), MaxMediumSurfaces (6). If 4 surfaces score ≥ 0.5, only the top 3 get High tier — the 4th gets Medium even if it scored for High.
  2. Total Memory Budget: MaxTotalRenderTargetMemoryMB (default 128MB). If exceeded, the manager logs a warning. Future: automatic demotion of lowest-priority surfaces until under budget.
  3. Global Quality Cap: GlobalQualityCap (default High). No surface exceeds this tier regardless of score. Set to Medium on Steam Deck / lower hardware.

Render Target Pool

The pool reduces memory allocation overhead by reusing render targets:

RequestRenderTarget(Size):
  1. Search pool for entry with matching Size AND !bInUse
  2. If found → mark bInUse=true, return RT
  3. If not → CreateRenderTarget(Size) → add to pool

ReleaseRenderTarget(RT):
  1. Find pool entry for this RT
  2. Mark bInUse=false (RT stays allocated, ready for next request)

This means the pool stabilizes after initial allocation — no new RTs are created unless a new size is requested.


Horror Features Deep Dive

Wrong Reflection (ActivateHorrorReflection)

1. UBPC_PlanarCapture saves current ShowOnlyActors → SavedShowOnlyActors
2. ShowOnlyActors is cleared
3. WrongReflectionActor is added to ShowOnlyActors
4. UpdateActorLists() pushes to USceneCaptureComponent2D::ShowOnlyActors
5. Next capture frame: SceneCapture2D now ONLY renders the WrongReflectionActor
6. Material: WrongReflectionBlend MPC param crossfades between normal RT and wrong reflection

Delayed Frame Ring Buffer

Hero tier with DelayedFrameCount = 5:
1. FrameRingBuffer array has 5 render targets
2. Each capture frame: RingBufferWriteIndex = (index + 1) % 5
3. Material: DelayedReflectionBlend MPC param blends between current frame and oldest ring buffer frame
4. Effect: Player's reflection lags 5 frames behind — unsettling

Steam Text Reveal

TriggerHorrorScare() → Timeline → TextRevealProgress MPC param ramps 0→1
Material Layer 6: TextRevealMask texture is wiped from 0 to 1
Appearance: "HELP ME" appears to write itself in the steam on the mirror

Integration Points

BPC_StateManager (130)

// Portal teleport must be gated
if (!StateManager->IsActionPermitted(FGameplayTag::RequestGameplayTag("Framework.Action.Teleport")))
    return; // Blocked — player is dead, in cutscene, etc.

BPC_ScareEventSystem (101)

// Horror mirror triggers coordinated scare
ScareEventSystem->TriggerScareEvent(ScareEventTag);
// This coordinates lights (96), audio (132), pacing (98), stress (10)

SS_AudioManager (132)

// All surface audio routes through audio subsystem
AudioManager->PlaySoundAtLocation(MirrorShatterCue, GetActorLocation());
AudioManager->PlaySoundAtLocation(PortalWhooshCue, GetActorLocation());

I_Persistable (36)

// Surface state persists across saves
// BP_PlanarCaptureActor implements I_Persistable:
CollectState()  { bIsDestroyed, CurrentDirtLevel, CurrentSteamLevel }
RestoreState()  { Apply saved state }

SS_EnhancedInputManager (128)

// Portal transition switches input context
InputManager->PushContext(PortalTransitionContext, EInputContextPriority::Inspection);
// Player exits portal → PopContext

Multiplayer Networking

What Replicates

  • bRepIsActive on BP_PlanarCaptureActor — server tells all clients whether a surface is active (e.g., a mirror was shattered by another player)

What Does NOT Replicate (Local-Only)

  • All capture rendering — each client renders their own view with their own camera perspective. There is zero reason to replicate render targets.
  • Quality tiers — each client evaluates surfaces independently (different camera positions mean different scores)
  • Horror effects — triggered server-side (scare event), executed locally on each client

Server-Authoritative Pattern

Server: BP_Mirror.DestroySurface() [HasAuthority]
  → Set bRepIsActive = false
  → OnRep_IsActive fires on all clients
  → Each client independently: DisableSurface() → ShutdownCapture()

Performance Characteristics

Tier GPU Cost (relative) VRAM per Surface CPU Cost
Hero 16x baseline 16 MB High (60 captures/sec)
High 4x baseline 4 MB Medium (30/sec)
Medium 1x baseline 1 MB Low (15/sec)
Low 0.25x baseline 256 KB Minimal (4/sec)
Off 0 0 Zero

Optimization Tips

  1. MaxCaptureDistance: Set lower (3000-5000) for indoor levels — mirrors far away go Off
  2. FullEvaluationInterval: Increase to 1.0s for large levels with many mirrors to reduce CPU scoring cost
  3. GlobalQualityCap: Set to Medium on Steam Deck. Set to High on consoles.
  4. ShowOnly lists: Use aggressively. Capturing only 5 actors is vastly cheaper than capturing 500.
  5. Monitor FPS: Monitors should use 5fps Low tier — they don't need real-time updates.
  6. Lumen on capture: Only enable on Hero tier. Each Lumen-enabled capture is ~3x more expensive.

Debugging

Console Commands

// Show all registered capture surfaces
SS_PlanarCaptureManager.GetSurfaceCount()

// Show pool memory usage
SS_PlanarCaptureManager.GetPoolMemoryUsageMB()

// Force all to max quality (for visual debugging)
SS_PlanarCaptureManager.ForceAllSurfacesToTier(Hero)
SS_PlanarCaptureManager.ReleaseForceTier()

// Show capture FPS (add to BPC_PlanarCapture debug mode)
stat SceneRendering

Visual Debug

  • Unlit view mode: See raw render target output without material effects
  • Wireframe: Verify camera frustum on SceneCaptureComponent2D
  • Stat GPU: SceneCapture passes appear under "SceneCapture" category

Build Order (Phase 17)

Sub-Phase Systems Dependencies
17a — C++ Core 136, 137, 138, PlanarCaptureCommon, PlanarCaptureCameraUtils Renderer module
17b — Materials 144, 145, 147 17a (for MPC parameter names)
17c — Blueprint Actors 139, 140, 141, 142, 143 17a + 17b
17d — Data Assets 146 17a
17e — Integration Wire to 101, 132, 130, 128, 36 17c

Developer Reference — Capture Systems v1.0. Companion to Blueprint Spec files in docs/blueprints/17-capture/.