Skip to main content
Autonomous AgentsMetaverse451 lines

Unreal Engine VR/XR Development

Quick Summary18 lines
This skill covers VR and XR development with Unreal Engine 5, leveraging its advanced rendering features (Nanite, Lumen, Virtual Shadow Maps) while meeting VR performance constraints. It addresses project configuration, interaction framework setup, optimization strategies specific to UE5's architecture, and deployment to major VR platforms.

## Key Points

1. Create Project:
2. Project Settings:
3. Quality Scalability:
- Motion Controller Component: Automatically tracks controller/hand
- Widget Interaction: Enables interaction with UMG UI in world space
- Enhanced Input: UE5's action-based input system
1. Line Trace Setup:
2. Valid Landing Check:
3. Execute Teleport:
4. Snap Turn:
1. □ Reduce overdraw (minimize transparent materials)
2. □ Enable foveated rendering
skilldb get metaverse-skills/unreal-engine-vr-developmentFull skill: 451 lines
Paste into your CLAUDE.md or agent config

Unreal Engine VR/XR Development

Purpose

This skill covers VR and XR development with Unreal Engine 5, leveraging its advanced rendering features (Nanite, Lumen, Virtual Shadow Maps) while meeting VR performance constraints. It addresses project configuration, interaction framework setup, optimization strategies specific to UE5's architecture, and deployment to major VR platforms.

Project Setup

Template and Configuration

UE5 VR Project Setup:

1. Create Project:
   ├── Template: VR Template (includes pre-built VR pawn and interactions)
   │   OR
   ├── Template: Blank → manually configure for VR
   └── Target platform: Mobile (Quest) or Desktop (PC VR)

2. Project Settings:
   ├── Platforms → XR:
   │   ├── Enable OpenXR Plugin
   │   ├── Enable OpenXR Hand Tracking
   │   └── Add Interaction Profiles (Meta Quest Touch, etc.)
   ├── Engine → Rendering:
   │   ├── Forward Shading: ON (mobile VR) or OFF (PC VR with deferred)
   │   ├── Mobile Multi-View: ON (stereo rendering)
   │   ├── Instanced Stereo: ON (PC VR)
   │   ├── Mobile HDR: OFF (Quest)
   │   ├── Anti-Aliasing: MSAA 4x (forward), TAA (deferred)
   │   └── VR Mode: Check "Start in VR"
   ├── Engine → General Settings:
   │   ├── Framerate: Uncapped (XR runtime handles vsync)
   │   └── Use Fixed Frame Rate: OFF
   └── Plugins:
       ├── OpenXR
       ├── OpenXR Hand Tracking
       ├── Meta XR (for Quest features)
       └── SteamVR (for PC VR via SteamVR)

3. Quality Scalability:
   ├── Create VR-specific scalability profiles
   ├── Disable expensive defaults (screen-space reflections, etc.)
   └── Set up platform-specific configs (Quest vs PC)

VR Pawn Architecture

VR Pawn Blueprint Structure:
BP_VRPawn (Character or Pawn)
├── VROrigin (Scene Component — tracking origin)
│   ├── Camera (Camera Component — auto-tracked to HMD)
│   ├── MotionController_Left (Motion Controller Component)
│   │   ├── HandMesh_Left (Skeletal Mesh — controller or hand model)
│   │   ├── GrabSphere_Left (Sphere Collision — grab detection)
│   │   ├── WidgetInteraction_Left (Widget Interaction — UI)
│   │   └── LaserBeam_Left (Static Mesh — interaction ray visual)
│   └── MotionController_Right (mirrored structure)
├── Capsule Component (collision, optional)
└── Character Movement Component (if using Character base)

Key Components:
- Motion Controller Component: Automatically tracks controller/hand
- Widget Interaction: Enables interaction with UMG UI in world space
- Enhanced Input: UE5's action-based input system

Enhanced Input for VR

Input Mapping

// VR Input Action definitions
// Create these as Data Assets:

// IA_Grab (Boolean)
// IA_Trigger (Axis1D)
// IA_Thumbstick (Axis2D)
// IA_PrimaryButton (Boolean)
// IA_Menu (Boolean)

// Input Mapping Context — binds actions to hardware
// IMC_VRDefault:
//   IA_Grab:
//     Left Hand:  OpenXR Left Grip
//     Right Hand: OpenXR Right Grip
//   IA_Trigger:
//     Left Hand:  OpenXR Left Trigger
//     Right Hand: OpenXR Right Trigger
//   IA_Thumbstick:
//     Left Hand:  OpenXR Left Thumbstick
//     Right Hand: OpenXR Right Thumbstick
// C++ VR Input handling
void AVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent);
    if (EIC)
    {
        // Grab actions
        EIC->BindAction(GrabAction_Left, ETriggerEvent::Started, this,
            &AVRPawn::OnGrabLeft);
        EIC->BindAction(GrabAction_Left, ETriggerEvent::Completed, this,
            &AVRPawn::OnReleaseLeft);

        // Trigger (analog)
        EIC->BindAction(TriggerAction_Right, ETriggerEvent::Triggered, this,
            &AVRPawn::OnTriggerRight);

        // Thumbstick locomotion
        EIC->BindAction(ThumbstickAction_Left, ETriggerEvent::Triggered, this,
            &AVRPawn::OnMoveInput);
    }
}

void AVRPawn::OnGrabLeft(const FInputActionValue& Value)
{
    TryGrabNearestObject(EControllerHand::Left);
}

void AVRPawn::OnMoveInput(const FInputActionValue& Value)
{
    FVector2D Input = Value.Get<FVector2D>();
    FVector Forward = Camera->GetForwardVector();
    Forward.Z = 0; Forward.Normalize();
    FVector Right = Camera->GetRightVector();
    Right.Z = 0; Right.Normalize();

    AddMovementInput(Forward * Input.Y + Right * Input.X);
}

Interaction System

Grab System

// VR Grab Component
UCLASS()
class UVRGrabComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    void TryGrab(UMotionControllerComponent* Controller)
    {
        // Overlap check
        TArray<FOverlapResult> Overlaps;
        FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);

        bool bHit = GetWorld()->OverlapMultiByChannel(
            Overlaps,
            Controller->GetComponentLocation(),
            FQuat::Identity,
            ECC_PhysicsBody,
            Sphere
        );

        if (bHit)
        {
            // Find closest grabbable
            AActor* Nearest = FindNearestGrabbable(Overlaps);
            if (Nearest)
            {
                // Attach to controller
                UPrimitiveComponent* GrabComp = Nearest->FindComponentByClass<UPrimitiveComponent>();
                GrabComp->SetSimulatePhysics(false);
                Nearest->AttachToComponent(Controller,
                    FAttachmentTransformRules::KeepWorldTransform);

                HeldActor = Nearest;

                // Haptic feedback
                Controller->GetPlayerController()->PlayHapticEffect(
                    GrabHaptic, Controller->GetTrackingSource());
            }
        }
    }

    void Release(UMotionControllerComponent* Controller)
    {
        if (HeldActor)
        {
            HeldActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
            UPrimitiveComponent* Comp = HeldActor->FindComponentByClass<UPrimitiveComponent>();
            Comp->SetSimulatePhysics(true);

            // Apply throw velocity
            Comp->SetPhysicsLinearVelocity(Controller->GetComponentVelocity());

            HeldActor = nullptr;
        }
    }

private:
    UPROPERTY() AActor* HeldActor = nullptr;
    float GrabRadius = 10.0f;
    UPROPERTY() UHapticFeedbackEffect_Base* GrabHaptic;
};

Teleportation

Teleportation System (Blueprint approach):

1. Line Trace Setup:
   ├── Start: Controller location
   ├── Direction: Controller forward
   ├── Type: Projectile path (parabolic arc)
   ├── Collision: NavMesh query at landing point
   └── Visual: Niagara particle beam or spline mesh

2. Valid Landing Check:
   ├── Project point to NavMesh
   ├── Check slope angle (< 30 degrees)
   ├── Check ceiling clearance (> player height)
   └── Show green/red indicator at landing

3. Execute Teleport:
   ├── Fade screen to black (0.1s)
   ├── Move VR Origin to target location
   ├── Maintain relative head position within play space
   └── Fade back in (0.1s)

4. Snap Turn:
   ├── Thumbstick right/left past threshold (0.7)
   ├── Rotate VR Origin by 30-45 degrees
   ├── Brief screen fade (optional, reduces discomfort)
   └── Cooldown to prevent rapid repeated turns

UE5 Rendering for VR

Nanite in VR

Nanite for VR — Considerations:
├── Supported: PC VR (requires modern GPU)
├── NOT supported: Quest standalone (no Nanite on mobile)
├── Benefits:
│   ├── Automatic LOD — no manual LOD chains needed
│   ├── Massive triangle counts without draw call cost
│   └── Virtual geometry streaming
├── VR Challenges:
│   ├── Higher base GPU cost than traditional meshes
│   ├── No MSAA support (requires TSR or FXAA)
│   ├── Stereo rendering doubles the work
│   └── Must profile actual VR frame times
└── Recommendation:
    ├── Use Nanite for environment geometry on PC VR
    ├── Use traditional meshes for mobile VR
    └── Test performance with both eyes rendering

Lumen in VR

Lumen for VR — Considerations:
├── Hardware Ray Tracing Lumen: Too expensive for VR currently
├── Software Lumen: Marginal for high-end PC VR
├── Alternative for VR:
│   ├── Baked lighting (Lightmass or GPU Lightmass)
│   ├── Light probes for dynamic objects
│   ├── Simple real-time lights (1-2 dynamic)
│   └── SSAO for contact shadows (cheap, effective)
└── For Quest:
    ├── Fully baked lighting only
    ├── Light probes for avatars
    ├── No real-time shadows (or 1 directional cascade)
    └── Lightmap resolution: 32-64 for most surfaces

Fixed Foveated Rendering

Foveated Rendering (Quest):
├── Center: Full resolution
├── Inner ring: 50% resolution
├── Outer ring: 25% resolution
├── Savings: 30-50% GPU fill rate

// Enable in C++:
#include "OculusXRFunctionLibrary.h"
UOculusXRFunctionLibrary::SetFoveatedRenderingLevel(
    EOculusXRFoveatedRenderingLevel::HighTop);

Eye-Tracked Foveated Rendering (Quest Pro, PSVR2):
├── Foveal region follows gaze direction
├── Higher quality savings (40-60%)
├── Requires eye tracking permission
└── Fallback to fixed foveation if tracking lost

Performance Optimization

Profiling Tools

UE5 VR Profiling Toolkit:
├── Unreal Insights: Detailed CPU/GPU trace
│   └── Launch with: -trace=default,gpu -statnamedevents
├── stat unit: Frame time breakdown (CPU game, CPU draw, GPU)
├── stat GPU: Per-pass GPU timing
├── RenderDoc: GPU capture for shader debugging
├── Meta Quest Developer Hub:
│   ├── OVR Metrics Tool overlay
│   ├── GPU profiler
│   └── Thermal monitoring
└── stat RHI: Draw calls, triangles, texture memory

Console Commands for VR:
  stat unit                    — Frame timing
  stat gpu                     — GPU pass breakdown
  r.ScreenPercentage 80        — Reduce render resolution
  r.MobileContentScaleFactor   — Quest render scale
  ProfileGPU                   — One-frame GPU profile
  stat scenerendering          — Scene render stats

Optimization Checklist

UE5 VR Optimization Priority List:

GPU-Bound (most common in VR):
1. □ Reduce overdraw (minimize transparent materials)
2. □ Enable foveated rendering
3. □ Lower shadow quality/resolution
4. □ Reduce post-processing (remove DOF, motion blur, SSR)
5. □ Use LODs aggressively (auto-generate with Nanite or manual)
6. □ Merge static meshes (fewer draw calls)
7. □ Use instanced static meshes for repeated geometry
8. □ Simplify materials (fewer texture samples, simpler math)
9. □ Reduce reflection capture density
10. □ Lower render resolution (r.ScreenPercentage)

CPU-Bound:
1. □ Reduce tick frequency on non-critical actors
2. □ Use async traces instead of sync
3. □ Limit physics bodies in scene
4. □ Use LOD for skeletal mesh bone count
5. □ Cull invisible actors (set cull distance)
6. □ Batch AI/logic updates across frames
7. □ Use HLOD for level streaming
8. □ Reduce Blueprint tick usage (use timers instead)

Memory (Quest-specific):
1. □ Texture streaming with aggressive pool size
2. □ ASTC compression on all textures
3. □ Audio compression (Vorbis for music, ADPCM for SFX)
4. □ Level streaming to limit loaded content
5. □ Skeletal mesh LOD with reduced bone counts

Quest Deployment

Android Build Setup

Quest Build Configuration:
├── Platform: Android
├── Build Target: Android (ASTC)
├── Architectures: arm64 only
├── Minimum SDK: 29
├── Target SDK: 32
├── Package Game: ON
├── Cook everything: Evaluate per-project

Required Plugins:
├── OpenXR
├── Meta XR (provides Quest-specific features)
├── OpenXR Hand Tracking
└── Android Runtime Settings configured for Quest

AndroidManifest additions:
- VR intent filter
- Hand tracking permission (if used)
- Scene API permission (if mixed reality)
- Passthrough permission (if mixed reality)

Build Process:
1. Package for Android (ASTC)
2. Output: .apk file
3. Install via ADB or Meta Quest Developer Hub:
   adb install -r YourGame.apk
4. Or upload to Meta Quest Developer Dashboard for distribution

Common Patterns

Distance Grab

Distance Grab (force-pull objects from afar):
1. Ray cast from controller
2. If ray hits grabbable object:
   a. Highlight object (outline post-process)
   b. Show "grab" icon on controller
3. On grip press:
   a. Object flies toward hand (lerp over 0.2-0.3s)
   b. Haptic ramp as object approaches
   c. Object snaps to hand, enters grabbed state
4. Physics: Disable on grabbed object during flight

Alternative: Force grip (point and object comes to you)
Alternative: Tractor beam (hold trigger, object moves with ray)

VR Menu System

Wrist Menu Pattern:
1. Detect "look at wrist" gesture:
   ├── Controller palm facing camera
   ├── AND camera looking at controller (dot product check)
   └── Threshold: palm-to-camera angle < 40 degrees
2. Spawn menu widget attached to wrist
3. Interact with opposite hand (ray or poke)
4. Menu follows wrist position
5. Dismiss when wrist turns away

World-Anchored Menu:
1. Spawn at controller position on menu button press
2. Menu stays in world space (doesn't follow controller)
3. Interact via ray or direct touch
4. Dismiss on button press or distance threshold

When to Apply This Skill

Use this skill when:

  • Starting a VR project in Unreal Engine 5
  • Configuring UE5 rendering features for VR performance
  • Implementing VR interaction systems in UE5
  • Optimizing an existing UE5 project for Quest deployment
  • Deciding between UE5 rendering features (Nanite/Lumen) for VR
  • Setting up the Enhanced Input system for VR controllers

Install this skill directly: skilldb add metaverse-skills

Get CLI access →