Executive assessment
Correctness comes from coordination, not brute force.
The system treats a panorama as six synchronized perspective cameras, each with explicit orientation, memory, temporal, and initialization rules. Those faces become a 2:1 master, receive finishing in the panorama’s topology, and are accepted only after spatial and temporal QA.
The decisive choices are camera-local quaternion composition, bounded face concurrency, Movie Render Queue high-resolution tiling, deterministic exposure and warm-up, and longitude-wrap-aware post-assembly bloom. The validated presets are conservative on purpose: they exchange some throughput for reproducibility, seam stability, and usable VRAM headroom in foliage-heavy scenes.
Capture + projection
Six ordinary views become one continuous frame.
A cubemap covers the sphere with six 90° rectilinear cameras aligned to the positive and negative axes of the source camera’s local frame. Unreal can render each face through its normal perspective pipeline, including MRQ, Lumen, virtual shadow maps, foliage culling, and post-processing, without shading one extreme equirectangular render target directly.
Direction mapping
For normalized output coordinates u and v, the assembler constructs a longitude, latitude, and unit direction. The dominant component selects a cube face; the other components become face-space coordinates. The renderer and assembler must share one tested convention for axes, handedness, texture flips, and pole roll.
λ = 2π(u − 0.5) φ = π(0.5 − v) d = (cosφ cosλ, cosφ sinλ, sinφ)| Face | Local look | Local up | Role |
|---|---|---|---|
| +X | (1, 0, 0) | (0, 0, 1) | Camera-forward |
| +Y | (0, 1, 0) | (0, 0, 1) | Camera-right |
| −X | (−1, 0, 0) | (0, 0, 1) | Camera-back |
| −Y | (0, −1, 0) | (0, 0, 1) | Camera-left |
| +Z | (0, 0, 1) | (−1, 0, 0) | Zenith; intentional roll |
| −Z | (0, 0, −1) | (1, 0, 0) | Nadir; intentional roll |
Pole-facing cameras have no naturally unique screen-up direction. Their up vectors intentionally choose the roll that makes the zenith and nadir meet the lateral faces in the order expected by the assembler.
Orientation correctness
Face offsets belong in camera-local quaternion space.
Let qcamera be the animated camera orientation and qface the fixed local rotation for one cube face. Under the active-rotation convention used here, the world orientation is:
qworld-face = normalize(qcamera × qface-local)In Unreal, the implementation should be verified by vectors rather than by memorized multiplication-order wording: rotating the canonical forward and up vectors by the final quaternion must produce the same vectors as first choosing the face-local look/up pair and then rotating those vectors by the animated source camera. Once that identity holds for all six faces, source pitch, yaw, and roll propagate uniformly to the cube.
Why Euler addition fails
Adding 90° offsets to a Rotator is not composition. Three-dimensional rotations do not commute, and the axes represented by Euler components change as earlier rotations are applied. The error becomes obvious at the zenith and nadir: ±90° pitch sits at the Euler singularity, where yaw and roll become coupled. If the source camera is already pitched or rolled, the pole offset is applied about a world or intermediate axis instead of the camera-local right axis. The cap can twist, flip, or detach from the four lateral faces while level-camera tests still look correct.
Validated raster structure
Master resolution determines the retained face core.
For a 2:1 master of width W and height W/2, the validated face core is N = W/4. Six source squares jointly sample solid angle; they are not packed directly into the output raster, so their total source pixel count is intentionally larger than the equirectangular frame.
| Master | Output raster | Core face | 5% guarded source | Guard / side |
|---|---|---|---|---|
| 4K | 4,096 × 2,048 | 1,024² | ≈ 1,076² | ≈ 26 px |
| 8K | 8,192 × 4,096 | 2,048² | ≈ 2,152² | ≈ 52 px |
The guarded sizes are rounded to practical even dimensions. At constant angular pixel density, a 1.05 linear guard implies a field of view near 92.8°. Assembly retains the central 90° core. Overscan lets view-space filters converge before the crop edge, but it cannot make opposite cube edges or the 0°/360° longitude seam visible to one per-face filter.
Work partitioning
Face batching and MRQ tiling solve different problems.
| Mechanism | What it divides | Primary effect | Invariant |
|---|---|---|---|
| Cube-face batching | Six camera views into jobs | Controls live view contexts, retry scope, and concurrency | Every face stays a full 90° view |
| MRQ high-resolution tiling | One face raster into 2×2 or 3×3 tiles | Reduces raster-scaled render targets and post buffers | Face count and delivery resolution do not change |
A 2×2 MRQ setting renders four sub-frusta of one face and stitches them back into that face. It does not mean two cube faces. Tiling reduces scene color, depth, velocity, G-buffer, history, and post-process allocations that scale with raster area. Textures, foliage instance data, shadow resources, Lumen scene data, and ray-tracing acceleration structures can remain resident, so memory does not fall by the square of the tile count.
Validated hardware presets
Concurrency is deliberately asymmetric by GPU and resolution.
| GPU | Master | Faces / job | MRQ tiles | Texture pool |
|---|---|---|---|---|
| RTX 5090 | 4K | 2 opposing | 2 × 2 | 6,000 MB |
| RTX 5090 | 8K | 1 | 2 × 2 | 7,000 MB |
| RTX 4090 | 4K | 1 | 2 × 2 | 4,500 MB |
| RTX 4090 | 8K | 1 | 3 × 3 | 5,000 MB |
The 5090 recovers throughput at 4K by scheduling two opposing faces per job. Every 8K preset returns to one face per job. The 4090 remains at one face per job at both resolutions and uses a finer 3×3 split at 8K. The texture-pool values constrain streaming residency; they are not total VRAM limits.
VRAM findings
The final 8K profile roughly halved practical pressure.
The initial non-tiled, more-concurrent 8K approach nearly exhausted a 32 GB RTX 5090. The final foliage-heavy 8K configuration generally used about 17 GB, leaving roughly 15 GB of headroom for scene variation, driver allocations, shader transitions, encoding, and transient peaks.
Each live face can own full render targets, visibility state, histories, and post-process intermediates.
Screen probes, radiance state, denoising, and temporal state are view- and resolution-sensitive.
Dense transforms, culling data, wind deformation, material variety, and alpha overdraw remain expensive.
BLAS/TLAS storage and updates can dominate when millions of animated leaf and branch instances are admitted.
High-resolution mips compete with virtual shadow map pages and per-light resources outside the tile-scaled buffers.
Scene color, depth, velocity, bloom, denoisers, AA, and tile overlap create multiple large surfaces.
Foliage-heavy lighting policy
| Profile | Lumen | Standalone RT | Foliage in RTAS | Intent |
|---|---|---|---|---|
| Foliage | Software | Disabled | Excluded | Maximum stability and headroom |
| Balanced | Software; optional hardware | Explicit | Explicit | Higher fidelity when scene and memory permit |
| Custom | Explicit | Explicit | Explicit | Expert control with no hidden inheritance |
Hardware Lumen and standalone ray-traced reflections, shadows, or ambient occlusion are separate choices. Balanced may retain optional hardware Lumen without silently enabling every standalone effect or rebuilding a massive foliage acceleration structure.
Temporal strategy
Initialize histories; do not spread one output instant across time.
| Control | Value | Reason |
|---|---|---|
| Spatial samples | 4 | Improves per-frame sampling without shifting the delivery time |
| Temporal samples | 1 | Avoids face/tile motion divergence and accumulation mismatch |
| History | Per tile enabled | Keeps state aligned with each sub-frustum and raster extent |
| Motion blur | Restrained | Limits boundary disagreement and does not hide foliage defects |
| Engine warm-up | 64 frames | Settles streaming, Lumen, shadows, particles, and simulation |
| Render warm-up | 48 frames | Builds view-specific render histories before capture |
| Handle | 1 frame / face | Absorbs the residual first-capture transient and is trimmed |
The six faces and MRQ tiles are independent projections. A screen-space sample on one face has no valid pixel correspondence on its neighbor, and history from one tile can represent another sub-frustum or overlap region. Reuse produces ghosts, unstable Lumen convergence, exposure drift, and seams that change with motion. High-frequency foliage amplifies the risk: thin alpha edges, subpixel branches, wind deformation, and changing occlusion generate legitimate temporal energy that multi-sample accumulation can smear or resolve differently on adjacent faces.
Handle-frame arithmetic
For a 20-second clip at 24 fps, the manifest and assembler must agree on all three counts:
20 s × 24 fps = 480 delivery frames1 handle + 480 delivery = 481 source frames per face6 faces × 481 = 2,886 source PNGsDelivery frame 0 uses source frame 1 from every face; source frame 0 is discarded independently. Counts and timestamps must be recorded per face so a retry cannot introduce a one-frame phase error on one side of the cube.
Image formation + delivery
Panorama topology governs bloom and exposure.
Two-stage bloom
The validated forest profile uses restrained Unreal bloom at intensity 0.10 and threshold 2.0 while the faces retain their 5% guards. After equirectangular assembly, a second bloom pass uses strength 0.22 and a 64 px radius at 4K or 128 px at 8K. Doubling the radius preserves roughly the same angular footprint.
Per-face bloom cannot guarantee continuity. Its convolution sees only one perspective image; adjacent energy is absent or oriented differently, and cropping removes the outer guard. The equirectangular left and right edges are neighbors on the sphere but distant in ordinary image memory. The finishing kernel therefore samples longitude modulo image width so light crossing 0°/360° contributes on both sides. Latitude uses an intentional pole policy rather than wrapping north to south.
Fixed exposure
The forest profile locks exposure at 5.0 with exposure bias −5.5. Both values are identical for every face, tile, warm-up, and frame. Independent auto-exposure would meter a sun-facing view, shaded forest view, zenith, and nadir from very different luminance distributions. Those differences become stable face seams or temporal pumping that geometric reprojection cannot repair.
HEVC Main10 delivery
Final video is HEVC Main10 through NVENC at 24 fps, approximately CQ17 with the P7 preset. Main10 improves gradient precision and reduces banding risk; it does not itself define an HDR deliverable. HDR also requires an HDR output transform, appropriate primaries and transfer function, correct matrix/range signaling, and any necessary mastering metadata. Main10 can carry excellent 10-bit SDR.
Automated QA
Every frame is analyzed; seams and motion are normalized locally.
Full assembled frames are reduced to a practical analysis resolution so every frame can be checked rather than sampled. Downscaling preserves the broad seams, pole defects, clipping, exposure shifts, and temporal discontinuities that determine admission.
| Metric | Computation concept | Defect class |
|---|---|---|
| Mean luminance | Full-frame luma mean | Exposure drift or gross failure |
| Highlight / near-white | Coverage above configured thresholds | Bloom growth, clipping, range errors |
| Crushed black | Coverage below the near-black threshold | Lost shadow detail |
| Left/right wrap ratio | 0°/360° difference ÷ local gradient baseline | Longitude seam |
| Cube-boundary ratio | Projected boundary energy ÷ parallel local baseline | Face crop, orientation, light, or history seam |
| Zenith/nadir spread | Robust statistics in high-latitude bands | Pole pinching, rotation, or unstable shading |
| Pole-wrap delta | Circular horizontal differences near the poles | Longitude discontinuity amplified by projection |
| Frame delta | Mean absolute first-order difference | Large one-frame visual change |
| Temporal acceleration | First-difference change, full frame and blocks | Pop, intersection, missing frame, or state reset |
A seam ratio near 1.0 means the tested boundary is no stronger than a comparable nearby gradient. A tree trunk crossing a face boundary should not fail merely because it contains contrast; it should fail when the boundary is disproportionately strong relative to the same local content.
Independent temporal analyzer
D1t = Yt − Yt−1 D2t = D1t − D1t−1First-order differences measure motion and appearance change. Second-order differences measure acceleration of that change. The analyzer evaluates a low-frequency signal and a detail residual, then aggregates second-order energy over spatial blocks. Block-level p95 and maximum values keep a localized intersection or missing fragment from disappearing inside a calm frame mean.
Each value is divided by a robust rolling temporal baseline. Local ratios are more useful than absolute deltas in fast foliage motion: wind, camera speed, leaf density, shutter, and contrast can make a healthy shot produce large raw differences. A local baseline rises during sustained motion but still exposes an isolated acceleration several times stronger than its neighbors.
Preliminary plugin boundary
The plugin is the declarative control plane — today.
The UE 5.8 C++ editor plugin uses UDeveloperSettings. It exposes RTX 5090, RTX 4090, and Custom hardware choices; 4K, 8K, and Custom resolutions; and Foliage, Balanced, and Custom scene profiles. Advanced settings remain transparent rather than hiding the interactions that determine stability.
MRQ tile grid, face batching, job structure, VRAM and texture-pool budget
Spatial/temporal samples, per-tile history, warm-ups, handle frames, motion blur
Software/hardware Lumen, standalone ray tracing, foliage participation in RTAS
Overscan, Unreal bloom, wrap bloom, fixed exposure, exposure bias
Frame rate, HEVC Main10, NVENC quality, paths, naming, and cleanup policy
A resolved JSON recipe containing explicit effective values after preset inheritance
The resolved recipe is the architectural center. Preset names are user input; workers should consume explicit, versioned effective values. A complete recipe includes frame range and rate, face convention, quaternion policy, core/guard dimensions, tiling and batches, warm-ups and handles, lighting switches, exposure and bloom, encoder signaling, expected artifact counts, and cleanup scope.
Recommended next milestone
Make the resolved recipe drive a resumable worker pipeline.
- 01Resolve + validate
Validate schema, freeze paths and frame math, and compute the expected job graph.
- 02Isolated capture worker
Launch UE/MRQ outside the editor so crashes, resets, and memory leaks cannot corrupt orchestration state.
- 03Resumable manifests
Track every clip, face, frame, dimension, timestamp, checksum, and state: pending, running, rendered, verified, or failed.
- 04Assembly + encode
Trim handles, crop cores, convert projection, apply wrap bloom, and encode Main10 with explicit color signaling.
- 05Automated QA report
Emit machine-readable metrics and concise human evidence; block publication on rejection.
- 06Publish
Publish only QA-approved masters and reports, then verify the destination is readable.
- 07Transactional cleanup
Plan deletion only for recipe-owned intermediates and commit it after durable publication verification.
RESOLVED → CAPTURING → SOURCE_VERIFIED → ASSEMBLED → QA_PASSED → PUBLISHED → CLEANUP_COMMITTEDFailure at capture returns only the affected face/job to pending. Assembly failure preserves verified sources. QA failure preserves both sources and the candidate master. Cleanup is a separate commit, never an unconditional epilogue. That state model makes a 2,886-file clip recoverable without rerendering successful faces and makes deletion auditable.