282 lines
12 KiB
Markdown
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/`.*
|