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

282 lines
12 KiB
Markdown

# 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)
```cpp
// Portal teleport must be gated
if (!StateManager->IsActionPermitted(FGameplayTag::RequestGameplayTag("Framework.Action.Teleport")))
return; // Blocked — player is dead, in cutscene, etc.
```
### BPC_ScareEventSystem (101)
```cpp
// Horror mirror triggers coordinated scare
ScareEventSystem->TriggerScareEvent(ScareEventTag);
// This coordinates lights (96), audio (132), pacing (98), stress (10)
```
### SS_AudioManager (132)
```cpp
// All surface audio routes through audio subsystem
AudioManager->PlaySoundAtLocation(MirrorShatterCue, GetActorLocation());
AudioManager->PlaySoundAtLocation(PortalWhooshCue, GetActorLocation());
```
### I_Persistable (36)
```cpp
// Surface state persists across saves
// BP_PlanarCaptureActor implements I_Persistable:
CollectState() { bIsDestroyed, CurrentDirtLevel, CurrentSteamLevel }
RestoreState() { Apply saved state }
```
### SS_EnhancedInputManager (128)
```cpp
// 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/`.*