How to Use Joy-Cons as 3D Motion Controllers for PC VR Content in 2D with PortalVR Motion (2025)

Prerequisites & Hardware Checklist

Before you install anything, confirm you have the right hardware and software stack. PortalVR Motion's core promise is letting you play SteamVR and OpenXR-compatible content on a flat 2D display using Joy-Con IMU data as the motion input source — no VR headset required. That distinction matters: you're not streaming to a headset or using a compatibility shim; PortalVR Motion injects a virtual OpenXR device into the runtime and renders the scene in flat projection onto your monitor.

Required Hardware: Joy-Cons, PC Specs, and Bluetooth Requirements

| Component | Minimum | Recommended | |---|---|---| | Joy-Con Controllers | 1x Left + 1x Right (retail, unmodified) | 1x Left + 1x Right (fresh battery >80%) | | CPU | Intel Core i5-8400 / Ryzen 5 2600 | Core i7-10700K / Ryzen 7 5800X | | GPU | NVIDIA GTX 1060 6 GB / RX 580 | RTX 3070 / RX 6800 XT | | RAM | 8 GB DDR4 | 16 GB DDR4 | | Bluetooth Adapter | Bluetooth 4.0 LE (internal or USB dongle) | Bluetooth 5.0 LE (dedicated USB, CSR8510-based recommended) | | Display | 1080p 60 Hz monitor | 1440p 144 Hz monitor | | OS | Windows 10 21H2 (64-bit) | Windows 11 23H2 (64-bit) | | Storage | 500 MB free (app + runtime) | SSD strongly preferred |

Windows 11 vs Windows 10 note: Windows 11 23H2 ships with an updated Bluetooth LE stack that substantially reduces pairing friction with Joy-Cons. On Windows 10, you may need to manually install the Microsoft Bluetooth LE driver (bthle.inf) and disable the generic HID filter driver for the Joy-Con device nodes — covered in detail in the Common Issues section. Bluetooth 4.0 LE is the hard minimum; without Low Energy support the Joy-Cons will not enumerate correctly as HID over GATT devices.

Required Software: PortalVR Motion, Drivers, and Runtime Dependencies

  • [ ] PortalVR Motion v1.4.0 or later (Windows x64 installer from the official PortalVR site)
  • [ ] SteamVR 2.x installed and launched at least once (creates the OpenXR runtime registration)
  • [ ] Visual C++ Redistributable 2022 (x64) — PortalVR Motion's native layer depends on it
  • [ ] OpenXR Runtime set to SteamVR in the Windows OpenXR registry key (PortalVR Motion's installer can set this automatically)
  • [ ] hidapi Windows DLL (bundled with PortalVR Motion installer, but confirm hidapi.dll exists in the install directory)
  • [ ] Windows Bluetooth stack with LE support confirmed active in Device Manager

Supported VR Content Formats and Launchers

PortalVR Motion works with any title that uses SteamVR's OpenVR API or OpenXR. This covers the vast majority of the SteamVR library. Titles using proprietary SDKs (Oculus PC SDK pre-OpenXR migration) have partial support via the Meta XR Simulator bridge — more on that in the FAQ. Native OpenXR titles work best because PortalVR Motion registers itself as a conformant OpenXR runtime.

Estimated setup time: 25–35 minutes (including driver installation and first calibration run).


Step 1: Install PortalVR Motion and Pair Your Joy-Cons via Bluetooth

Getting the controllers detected cleanly before launching any VR content is critical. If you try to open PortalVR Motion with undetected Joy-Cons, the virtual device won't initialize and SteamVR will fall back to its null driver, which is a frustrating failure mode to debug after the fact.

Downloading and Installing PortalVR Motion on Windows

Download the PortalVR-Motion-Setup-x64.exe installer from the official PortalVR Motion website. Run it as Administrator — the installer registers a custom OpenXR layer manifest at HKLM\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Implicit and this requires elevated privileges. Accept the default install path (C:\Program Files\PortalVR Motion\) unless you have a specific reason to change it; the SteamVR plugin loader resolves paths relative to the registry entry, and non-standard paths occasionally cause manifest load failures.

Once installed, launch PortalVR Motion Dashboard from the Start Menu. Do not open SteamVR yet.

Pairing Left and Right Joy-Cons via Windows Bluetooth Settings

  1. Open Settings → Bluetooth & devices → Add device → Bluetooth.
  2. Hold the sync button (small circular button on the Joy-Con rail edge) until the controller LEDs begin cycling. Each Joy-Con must be paired individually — they appear as separate HID devices.
  3. Select Joy-Con (L) when it appears, click Connect, wait for "Connected" status. Repeat for Joy-Con (R).
  4. Both should appear under Settings → Bluetooth & devices as connected devices with battery level indicators (Windows 11) or simply as paired (Windows 10).

Verifying Controller Detection in the PortalVR Motion Dashboard

Use the following PowerShell script to confirm both Joy-Cons appear as recognized HID devices with Status: OK before trusting the PortalVR Motion UI:

# Enumerate all Bluetooth HID devices and filter for Joy-Con entries
# Run in an elevated PowerShell session for complete device visibility

$joycons = Get-PnpDevice -Class 'HIDClass' -Status 'OK' | Where-Object {
    $_.FriendlyName -match 'Joy-Con'
}

if ($joycons.Count -eq 0) {
    Write-Warning "No Joy-Con devices found with Status OK. Check Bluetooth pairing."
} elseif ($joycons.Count -lt 2) {
    Write-Warning "Only $($joycons.Count) Joy-Con detected. Ensure both Left and Right are paired."
    $joycons | Format-Table FriendlyName, InstanceId, Status -AutoSize
} else {
    Write-Host "Joy-Con detection: OK ($($joycons.Count) controllers found)" -ForegroundColor Green
    $joycons | Format-Table FriendlyName, InstanceId, Status -AutoSize
}

# Also verify the Bluetooth host adapter supports LE
$btAdapter = Get-PnpDevice -Class 'Bluetooth' | Where-Object { $_.Status -eq 'OK' }
$btAdapter | Format-Table FriendlyName, Status -AutoSize

In the PortalVR Motion Dashboard, navigate to Controllers tab. Both Joy-Con (L) and Joy-Con (R) should display a green Tracked badge. If you see Detected / Not Tracked, the IMU handshake hasn't completed — click Re-initialize IMU next to each controller.

Note: If a Joy-Con shows as "Connected" in Windows Bluetooth settings but doesn't appear in PortalVR Motion at all, the issue is almost always the BLE vs classic HID driver conflict. Jump to the Common Issues section for the registry fix before continuing.


Step 2: Configure 3D Tracking Space and Controller Mapping

This step translates raw Joy-Con IMU data into the virtual 3D controller poses that VR games expect. Getting the mapping right determines whether your in-game hands feel responsive or laggy and drift-prone.

Understanding PortalVR Motion's Tracking Algorithm for Joy-Con IMU Data

Each Joy-Con contains a 6-DOF IMU: a 3-axis accelerometer and a 3-axis gyroscope. PortalVR Motion uses a complementary filter (a weighted fusion of gyroscope integration and accelerometer gravity-vector correction) to derive orientation. Positional estimates (X, Y, Z displacement) are computed by double-integrating acceleration — which is inherently noisy and accumulates drift. PortalVR Motion partially compensates with periodic gravity subtraction and a recenter gesture, but this remains 3-DOF-with-positional-estimation rather than true 6-DOF like camera-tracked controllers.

The coordinate system PortalVR Motion uses: +Y is up (gravity axis), +Z points toward the player (forward-out of screen), +X is rightward. This matches the OpenXR right-hand coordinate convention. When you hold a Joy-Con vertically in your right hand with the joystick up, the controller's local frame aligns with this world frame at the calibration origin.

Setting Up Your Play Space Boundaries in 2D Mode

In the PortalVR Motion Dashboard → Play Space tab, select 2D Flat Mode. You'll be prompted to define a radius (in meters) for the virtual play boundary. For seated play, 0.5 m is appropriate. For standing rhythm games, use 1.0–1.2 m. These boundaries only affect the guardian overlay (if enabled); they don't restrict input.

Mapping Joy-Con Buttons to Virtual VR Controller Inputs

PortalVR Motion reads a JSON profile from %APPDATA%\PortalVR Motion\profiles\. Here's a complete custom mapping profile:

{
  "profile_name": "JoyCon_Default_2025",
  "version": "1.2",
  "left_controller": {
    "buttons": {
      "joy_zl": "trigger",
      "joy_l": "grip",
      "joy_minus": "menu",
      "joy_stick_click": "thumbstick_click",
      "joy_left": "thumbstick_left",
      "joy_right": "thumbstick_right",
      "joy_up": "thumbstick_up",
      "joy_down": "thumbstick_down",
      "joy_capture": "system"
    },
    "axes": {
      "thumbstick_x": { "source": "joy_stick_x", "sensitivity": 1.0, "deadzone": 0.12, "invert": false },
      "thumbstick_y": { "source": "joy_stick_y", "sensitivity": 1.0, "deadzone": 0.12, "invert": true },
      "trigger_analog": { "source": "joy_zl_analog", "sensitivity": 1.0, "deadzone": 0.05 }
    },
    "gyroscope": {
      "sensitivity_multiplier": 1.2,
      "deadzone_dps": 0.8
    }
  },
  "right_controller": {
    "buttons": {
      "joy_zr": "trigger",
      "joy_r": "grip",
      "joy_plus": "menu",
      "joy_stick_click": "thumbstick_click",
      "joy_a": "button_a",
      "joy_b": "button_b",
      "joy_x": "button_x",
      "joy_y": "button_y",
      "joy_home": "system"
    },
    "axes": {
      "thumbstick_x": { "source": "joy_stick_x", "sensitivity": 1.0, "deadzone": 0.12, "invert": false },
      "thumbstick_y": { "source": "joy_stick_y", "sensitivity": 1.0, "deadzone": 0.12, "invert": true },
      "trigger_analog": { "source": "joy_zr_analog", "sensitivity": 1.0, "deadzone": 0.05 }
    },
    "gyroscope": {
      "sensitivity_multiplier": 1.2,
      "deadzone_dps": 0.8
    }
  }
}

Save this as JoyCon_Default_2025.json in the profiles directory and select it from the Dashboard's Controller Profile dropdown. The deadzone_dps value (degrees-per-second) prevents micro-drift from registering as intentional rotation when your hands are still.

Note: The invert: true on thumbstick_y corrects for the Joy-Con's Y-axis being inverted relative to OpenXR's expected thumbstick convention. Without this, forward on the stick moves you backward in most locomotion implementations.


Step 3: Launch VR Content in 2D Mode via PortalVR Motion

With controllers mapped and tracking initialized, you're ready to actually run a game. The way PortalVR Motion renders VR content to a flat 2D screen is fundamentally different from SteamVR's built-in Desktop Theater mode — it's not just mirroring one eye's render; it renders a dedicated wide-FOV flat projection at the full resolution of your monitor without the lens distortion or chromatic aberration correction passes that headset rendering requires.

Supported VR Runtimes: SteamVR, OpenXR, and Standalone App Injection

PortalVR Motion supports three launch paths:

  • SteamVR (OpenVR API): Widest game compatibility. PortalVR Motion hooks into SteamVR as a virtual HMD + controller pair.
  • OpenXR native: For titles compiled against the OpenXR SDK directly. PortalVR Motion registers as the active OpenXR runtime.
  • Standalone injection: For executables not launched via Steam. Use PortalVR Motion's App Launcher and point it at the game's .exe directly.

Launching a SteamVR Game in 2D with Motion Input Active

In Steam, right-click the game → Properties → Launch Options and paste the following:

-vrmode none --portalvr-flat --portalvr-width 2560 --portalvr-height 1440

This string does two things: -vrmode none tells SteamVR's launcher not to initialize head-mounted display rendering (preventing it from complaining about no HMD detected), while --portalvr-flat is PortalVR Motion's intercepted flag that activates its flat 2D projection layer before the game's rendering loop starts. Replace 2560 and 1440 with your monitor's native resolution.

For a game like Beat Saber, the full launch options look like:

-vrmode none --portalvr-flat --portalvr-width 1920 --portalvr-height 1080 --portalvr-fov 100

The --portalvr-fov flag sets the horizontal field of view in degrees for the flat projection. 90–105 degrees works well for most action games; exploration games benefit from 110–120 degrees.

Adjusting FOV, Rendering Scale, and Display Output for 2D Screens

In PortalVR Motion Dashboard → Rendering tab:

  • Render Scale: Start at 1.0 (native). Increase to 1.5 for supersampling on powerful GPUs, or drop to 0.8 if you see frame drops.
  • Projection Mode: Use Rectilinear for most games. Cylindrical works well for seated cockpit games.
  • Eye: Since you're rendering to a 2D screen, select Center (synthesized center eye) rather than Left or Right.

Note: PortalVR Motion's 2D layer is distinct from SteamVR's native Mirror Window. SteamVR Mirror shows a distorted, headset-optimized render. PortalVR Motion generates a clean flat-projection render pass specifically for your monitor. This means games look noticeably better in PortalVR Motion's 2D mode than in SteamVR's mirror. Best genres: rhythm games (Beat Saber, Pistol Whip), wave shooters (Superhot VR), exploration (No Man's Sky VR).


Step 4: Calibrate and Tune Motion Tracking for Accuracy

IMU-only tracking is always a tradeoff between responsiveness and drift accumulation. This step locks in settings that keep drift manageable for typical 20–40 minute play sessions.

Running the Initial IMU Calibration Routine

In PortalVR Motion Dashboard → Calibration, click Start IMU Calibration. Place both Joy-Cons flat on a stationary surface (screen facing up) and click Capture Rest State. Leave them still for 10 seconds. This measures the bias offsets for each accelerometer and gyroscope axis at rest — these offsets are stored per-device by MAC address in %APPDATA%\PortalVR Motion\imu_calibration.json.

After calibrating, perform the Orientation Reset: hold both Joy-Cons in your natural play grip (vertically, joystick up) and press the Minus + Plus buttons simultaneously. This sets the current orientation as the world-space identity pose.

Adjusting Gyroscope Drift Compensation Settings

The complementary filter's alpha value controls how aggressively the accelerometer corrects gyroscope integration. High alpha = more stable but slightly laggier rotations. Low alpha = more responsive but drifts faster.

Fine-Tuning Positional Smoothing and Latency Tradeoffs

Edit the PortalVR Motion config file at %APPDATA%\PortalVR Motion\config.toml:

[imu]
# Complementary filter weight: 0.0 = pure gyro, 1.0 = pure accel
# Recommended range: 0.92-0.98 for gaming
imu_filter_alpha = 0.96

# Enable periodic drift correction using gravity vector realignment
drift_compensation_enabled = true

# How often (seconds) to apply a soft gravity correction nudge
drift_compensation_interval_s = 4.0

# Gesture to reset position/orientation origin
# Options: "shake", "double_tap", "button_combo"
recenter_gesture = "button_combo"

# Button combo for recenter (when recenter_gesture = "button_combo")
# Uses PortalVR Motion button names
recenter_buttons = ["left_minus", "right_plus"]

# IMU polling rate in Hz — Joy-Con supports up to 200 Hz via hidapi
# Higher = lower latency, higher CPU overhead
polling_rate_hz = 200

[smoothing]
# One-Euro filter min cutoff frequency (lower = smoother, higher = more responsive)
min_cutoff_hz = 1.5

# One-Euro filter beta (speed coefficient — higher reduces lag at fast motion)
beta = 0.007

# Derivative cutoff Hz
derivative_cutoff_hz = 1.0

| Smoothing Level | imu_filter_alpha | min_cutoff_hz | Perceived Latency | |---|---|---|---| | Maximum Smooth | 0.98 | 0.8 | ~22 ms | | Balanced (default) | 0.96 | 1.5 | ~14 ms | | Maximum Responsive | 0.92 | 3.0 | ~8 ms | | Raw (no smoothing) | 0.88 | 6.0 | ~5 ms |

For rhythm games, use Maximum Responsive or Raw. For exploration or slower-paced games, Balanced or Maximum Smooth prevents jitter from being distracting. The One-Euro filter (min_cutoff + beta parameters) is more sophisticated than a simple exponential moving average — it adapts cutoff frequency based on motion speed, so fast swings are responsive while stationary holds are stable.

Note: If you experience severe drift after 3–4 minutes of play regardless of filter settings, first re-run the IMU calibration routine. Drift is almost always worse when the Joy-Con battery is below 20% — the IMU's reference voltage affects measurement accuracy.


Common Issues & Fixes

Fix: Joy-Cons Not Detected After Pairing (Bluetooth HID vs BLE Conflict)

Cause: Windows loads the generic bthusb.sys driver for the Joy-Con instead of the HID over GATT profile, causing the device to enumerate without a usable HID interface.

Run the following PowerShell script as Administrator to force the correct driver priority:

# Force HID-over-GATT driver priority for Joy-Con Bluetooth devices
# Run as Administrator

$ErrorActionPreference = 'Stop'

# Registry path for Bluetooth device class upper filters
$btHidPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\HidBth\Parameters'

if (-not (Test-Path $btHidPath)) {
    New-Item -Path $btHidPath -Force | Out-Null
}

# Set Joy-Con HID service to load before generic BLE profile
Set-ItemProperty -Path $btHidPath -Name 'ConnectRetries' -Value 3 -Type DWord
Set-ItemProperty -Path $btHidPath -Name 'HidMode' -Value 1 -Type DWord

# Disable selective suspend for Bluetooth HID devices (prevents Joy-Con drops)
$usbSuspendPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\HidBth'
Set-ItemProperty -Path $usbSuspendPath -Name 'SelectiveSuspend' -Value 0 -Type DWord

Write-Host "Registry values set. Re-pair Joy-Cons via Bluetooth settings." -ForegroundColor Cyan

# Restart Bluetooth support service to apply
Restart-Service -Name bthserv -Force
Write-Host "Bluetooth service restarted." -ForegroundColor Green

After running this, remove both Joy-Cons from Windows Bluetooth settings and re-pair them from scratch. Verify with the Get-PnpDevice script from Step 1.

Fix: Motion Tracking Drifts Severely After 2-3 Minutes of Play

Cause: The IMU calibration rest-state offsets don't match the current temperature/session of the controller, causing gyroscope bias to accumulate rapidly.

Fix steps:

  1. Re-run IMU calibration (Dashboard → Calibration → Start IMU Calibration) with fresh batteries.
  2. In config.toml, set drift_compensation_interval_s = 2.0 (more frequent correction nudges).
  3. Use the recenter gesture mid-session: press Minus + Plus simultaneously to snap orientation back to identity. Do this naturally when transitioning between activities.
  4. Increase imu_filter_alpha to 0.97 to weight the gravity vector more heavily.

Fix: VR Game Launches in Headset Mode Instead of 2D Flat Mode

Cause: SteamVR's defaultRuntime or the game's own VR autodetect overrides the -vrmode none launch flag.

Edit SteamVR's settings file at C:\Program Files (x86)\Steam\config\steamvr.vrsettings and add the following key to the "steamvr" section:

{
  "steamvr": {
    "enableHomeApp": false,
    "mirrorViewGeometry": "0 0 1920 1080",
    "preferredRefreshRate": 90,
    "forcedDriver": "portalvr",
    "hmdDisplayDebugMode": false,
    "displayDebug": false
  }
}

The "forcedDriver": "portalvr" key tells SteamVR to load PortalVR Motion's virtual HMD driver as the exclusive display driver, bypassing any real HMD detection. Save the file, then restart SteamVR before launching your game.

Fix: High Input Latency or Stuttering Controller Response on Windows 10

Cause: Windows 10's Bluetooth stack throttles HID polling for Bluetooth LE devices to 7.5 ms minimum intervals by default, but with multiple devices active the scheduler can push effective polling to 15–30 ms intervals.

Fix: Set the Bluetooth connection interval registry override:

# Set minimum Bluetooth LE connection interval for lower-latency HID
# Units: 0.625 ms per unit. Value of 6 = 3.75 ms minimum interval
$btPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\BTHPORT\Parameters'

Set-ItemProperty -Path $btPath -Name 'MinSupportedConnectionInterval' -Value 6 -Type DWord
Set-ItemProperty -Path $btPath -Name 'MaxSupportedConnectionInterval' -Value 12 -Type DWord

Write-Host "BLE connection interval set to 3.75-7.5ms range. Reboot required." -ForegroundColor Yellow

Reboot after applying. Also ensure no other Bluetooth audio devices (headsets, speakers) are active on the same adapter — they compete for bandwidth and dramatically increase Joy-Con polling jitter.


FAQ

Q: Does PortalVR Motion Work with Nintendo Switch Joy-Cons Without Any Modification?

Yes. PortalVR Motion uses standard Bluetooth HID over GATT to communicate with retail, unmodified Nintendo Switch Joy-Cons. No firmware flashing, no third-party hardware, and no custom drivers are required beyond what the installer provides. The Joy-Cons pair via the standard Windows Bluetooth stack exactly as they do for other PC applications like gaming emulators. PortalVR Motion reads IMU data using the published HID report descriptor format for Joy-Con devices. The only version-specific caveat: Joy-Con controllers sold after mid-2022 include Nintendo's revised analog stick (addressing drift) and behave identically from a Bluetooth protocol perspective.

Q: Can I Use PortalVR Motion with Non-SteamVR Games Like Oculus/Meta PC Apps?

Partially. PortalVR Motion registers as an OpenXR runtime, and Meta's PC VR runtime (Meta Quest Link / Air Link software) does not use OpenXR by default for its PC-side rendering pipeline — it uses the proprietary Oculus PC SDK. However, titles on Meta's PC platform that explicitly support OpenXR (via the XR_META_* extensions or standard OpenXR 1.0) can be redirected to PortalVR Motion's runtime by setting the active OpenXR runtime in the registry. The experience is partial: some Meta-specific features (guardian, passthrough) won't function, but core controller input and rendering work for OpenXR-compliant titles. Purely Oculus-SDK-only titles are not compatible.

Q: Is PortalVR Motion Free or Does It Require a Paid License?

PortalVR Motion offers a free tier with core functionality — Joy-Con pairing, basic controller mapping, and 2D flat projection for SteamVR games. Advanced features including custom controller profile import/export, the One-Euro filter smoothing configuration, multi-app OpenXR runtime switching, and priority support require a paid Pro license. Pricing as of 2025 is a one-time purchase model (no subscription). Check the official PortalVR Motion website for current pricing, as it has been updated periodically. The free tier is sufficient to verify compatibility with your hardware and game library before committing to a purchase.

Recommended Tools