- Created a comprehensive implementation checklist for the Planar Capture System (Systems 136-147) detailing tasks across multiple phases including C++ core, material foundation, Blueprint actors, data assets, integration, and performance testing. - Added a developer reference document outlining the architecture, data flow, state machine, budget enforcement, render target pooling, horror features, integration points, multiplayer networking, performance characteristics, debugging methods, and build order for the capture systems. - Introduced examples of capture surface usage in the Project Void horror game, including specific implementations for mirrors, monitors, portals, and fake windows, along with a checklist for integration tasks.
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 │
│ ASS_PlanarCaptureManager ← Global budget, RT pool, scoring │
│ 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
- Registry:
BP_Mirror.BeginPlay()callsSS_PlanarCaptureManager.RegisterSurface(this)— the mirror is now tracked globally. - Evaluation (0.5s interval):
SS_PlanarCaptureManager.Tick()callsEvaluateAllSurfaces(). - 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
- Tier Assignment: Based on composite score and budget limits, the manager calls
BPC_PlanarCapture.ApplyQualityTier(Tier). - Capture Tick (per frame):
BPC_PlanarCapture.TickComponent()checks time since last capture vs the quality tier'sCaptureInterval. If it's time to capture:- Gets the player camera transform
- Calls
ComputeCaptureCameraTransform()— for Mirror mode, this callsUPlanarCaptureCameraUtils::ComputeMirroredTransform()which reflects the player camera across the mirror plane - Sets the
USceneCaptureComponent2Dworld transform - Calls
SceneCapture->CaptureScene()— renders to the allocatedUTextureRenderTarget2D - Calls
PushMPCParameters()to update the MPC with current horror/visual params
- Material Display:
M_CaptureSurface_Mastersamples 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:
- 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. - Total Memory Budget:
MaxTotalRenderTargetMemoryMB(default 128MB). If exceeded, the manager logs a warning. Future: automatic demotion of lowest-priority surfaces until under budget. - 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
bRepIsActiveonBP_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
- MaxCaptureDistance: Set lower (3000-5000) for indoor levels — mirrors far away go Off
- FullEvaluationInterval: Increase to 1.0s for large levels with many mirrors to reduce CPU scoring cost
- GlobalQualityCap: Set to Medium on Steam Deck. Set to High on consoles.
- ShowOnly lists: Use aggressively. Capturing only 5 actors is vastly cheaper than capturing 500.
- Monitor FPS: Monitors should use 5fps Low tier — they don't need real-time updates.
- 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/.