Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

266 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bracer XR

Unity Platform Release License DOI

The forearm as a touchscreen. A directly touchable interface, reconstructed in real time from the Meta Quest 3's environment depth. No controllers, no markers, no external hardware.

This is an on-body interaction system for extended reality: the forearm acts as the display surface and the opposite hand as the input, bringing phone-style direct touch to bare skin.

Demo: a touchable UI projected onto the reconstructed forearm surface in passthrough on Quest 3.


How It Works

The forearm surface is reconstructed each depth frame from the Quest 3's environment depth, which the headset computes by stereo-matching its passthrough cameras. The pipeline spans the GPU and Burst-compiled worker threads and is frame-pipelined: reconstruction jobs are scheduled asynchronously, and the main thread uploads each finished mesh only once its jobs complete.

In brief: for each new depth frame, take the headset's depth image, discard everything that is not forearm, turn what remains into a mesh, and project a touchable UI onto it.

Per-depth-frame pipeline: GPU depth processing (grown hand mask, hand-masked depth, temporal median), an async readback to CPU Burst jobs (unproject, seed, BFS flood, boundary smooth, mesh), then a GPU upload to composite the on-arm UI in passthrough.
The per-depth-frame pipeline. On the GPU, the interacting hand is grown into a two-zone mask, carved out of the depth image, and stabilized with a 3-frame temporal median. An async readback hands the stabilized depth to CPU Burst jobs that unproject it into world space, isolate the forearm patch (seed, BFS flood, boundary smooth), and build the mesh. The mesh is uploaded back to the GPU to composite the touchable UI in passthrough.

How each stage works
  1. Resolve the wrist and elbow bones from the body skeleton to construct the arm coordinate frame: axis, lateral, and normal vectors, plus the pronation angle and a portrait/landscape orientation.
  2. Render the interacting hand as a full-frame GPU silhouette and grow it into a two-zone mask with a separable dilation: an inner no-trust cushion and a bleed-reconstruct ring. Then stabilize the depth with a 3-frame, motion-reprojected per-texel median (the median rejects stereo "flying pixels" so the arm boundary stops flickering; reprojecting the history into the current head pose keeps it stable under head motion). During stabilization the hand silhouette is cut out of the depth history, the cushion around it is rebuilt at arm depth borrowed from just outside the mask (its own readings are bleed or hand, never trusted), and the stereo "bleed" the finger lifts in the ring beyond that is reconstructed from the same clean arm, so the canvas continues right up to the hand, and the fixes enter history and stay temporally stable.
  3. Read back the stabilized forearm crop from the GPU asynchronously, at the crop's native depth-texel resolution and a single float per texel, so only the arm region ever crosses the bus. A Burst job then unprojects each depth texel into a world-space hit grid on the CPU, using the depth frame's own capture pose; all hand handling already happened during stabilization, so the carved finger arrives invalid and drops out as a hole.
  4. A seed region plus BFS flood isolates the patch from background geometry, gated to two cylinders: the forearm and the palm (wrist to the middle-finger knuckle, so the hand is captured when waved or turned but the fingers are excluded).
  5. A parallel Burst boundary smoother de-steps the extracted edge cells (the temporal median in step 2 is the depth denoise).
  6. Generate the mesh: vertices and triangles are emitted in parallel through atomic counters, normals are computed in parallel across the grid, and UVs follow a linear, camera-fixed projection (with a pronation scroll offset).

Touch detection

Each frame, the fingertip joints reported by hand tracking (currently the index fingertip) are tested against the reconstructed surface: the nearest surface cell within range is found, the signed distance above the surface is computed, and a UV coordinate is derived at sub-cell precision from the finger position. Touch comes from the tracked skeleton rather than from sensing the fingertip in the noisy depth map, which keeps it stereo-stable. The surface keeps updating throughout interaction (pronation included), with no freeze step.

UV design

UV is a linear projection (not cylindrical). The camera-fixed lateral axis keeps the viewport upright regardless of wrist rotation, and pronation adds a U scroll offset so rotating the wrist reveals new content rather than spinning the image. Two panels: U=[0,0.5] dorsal, U=[0.5,1] palmar.

Performance

Everything runs on the headset and the pipeline is built to disappear into the frame budget.

  • Holds the Quest 3's 72 fps with the full pipeline running (~4 ms of app GPU time per frame against the ~13.9 ms budget).
  • Reconstruction is gated to the depth sensor's cadence (~25 Hz). A bit-exact check on the depth reprojection matrix skips redundant frames, so the same depth data is never reconstructed twice; rendering and touch stay decoupled at the headset's refresh rate.
  • No main-thread stalls. The CPU stages are Burst-compiled jobs spread across worker threads and frame-pipelined: the readback, segmentation, and meshing for one depth frame are harvested a frame later, so the main thread never blocks on them.
  • Only the arm crosses the bus. The async GPU readback is cropped to the forearm at the depth texture's native resolution (one float per texel) so the GPU->CPU transfer scales with the arm, not the screen.

Figures are measured on-device from a clean boot and vary with build, scene, and device state.


Limitations & Scope

Bracer XR is a research prototype with a deliberately narrow scope:

  • Quest 3 only. It depends on Meta's environment depth API and Movement SDK body tracking; it does not run on other headsets.
  • The depth source is low-resolution. Quest's environment depth is a single ~320×320 image spread across the entire field of view, so the forearm occupies only a fraction of it. The surface captures the arm and a finger fine, but thin gaps and small, fine-grained features are below what it can resolve.
  • A hovering finger lifts the depth around itself. Quest's stereo depth pulls arm texels toward a nearby finger (a mixed-pixel "bleed"), which would raise the reconstructed surface at the contact point. The pipeline reconstructs that ring from the clean arm just outside it, but a slight residual raise remains at the resolution limit.
  • The arm patch is found from depth, not trusted from the skeleton. Wrist and elbow bone tracking is only a rough starting point and can be inaccurate, so the patch is seeded from a tight, confident region near the bone axis and grown by a BFS flood through depth-connected cells. The flood is walled to forearm and palm cylinders because, left unbounded, BFS can leak across depth-continuous neighbors into surfaces that are not the arm.
  • One forearm, one interacting hand. The system reconstructs a single arm and tracks a single touching hand at a time.
  • The touching hand is masked out, not reconstructed under. To keep it from corrupting the mesh, the hand is cut from the depth, leaving a hole exactly where its silhouette sits; the surface around it is rebuilt from neighboring arm, so the canvas reaches the hand. Touch is detected from hand tracking rather than by sensing the fingertip in the depth map, so this does not break interaction.

Known issues

  • Hand tracking can occasionally fail to initialize on launch (OVRHand never reports valid data), so the hands don't lock on and touch never registers. A mitigation is in place but is not fully reliable; the issue is being investigated. Restarting the app usually recovers.

Requirements

Meta Quest 3 v69+ firmware, Developer Mode enabled
Unity Editor 2022.3.62f3 (must match exactly) with Android Build Support (SDK, NDK, OpenJDK)
Git LFS git lfs install before cloning
SDKs (already in repo) Meta XR All-in-One SDK (hand tracking, passthrough), Meta Movement SDK (body tracking)

Build & Run

git lfs install
git clone https://github.com/treytuscai/bracer-xr.git
  1. Open the project in Unity Hub with editor 2022.3.62f3.
  2. Open Assets/_Project/Scenes/MainScene.unity.
  3. Connect the Quest 3 over USB (adb devices to confirm).
  4. File -> Build Settings -> confirm Android is the active platform and the scene is listed -> Build and Run.

All project, SDK, and OVRManager settings are committed. No manual configuration is required.


Usage

Once the app is running on the headset:

  1. Hold out your left arm in view of the headset. The UI appears on your forearm once body and hand tracking lock on.
  2. Touch with your other hand. Bring a fingertip to the surface. Contact registers from a slight hover down through a shallow press (touchHoverHeight / touchDepth).
  3. Rotate your wrist to switch panels. The display has two: dorsal (U=[0,0.5]) and palmar (U=[0.5,1]).
  4. Turn your arm upright or sideways to swap between the portrait and landscape images (portraitTexture / landscapeTexture). Set orientationMode to lock to one. The image is swapped, not rotated, so author each one in its own orientation.

The surface updates continuously. Keep your hand on it while moving or rotating the arm. There is no need to pull away or recalibrate.

Just want to see the reconstruction? Leave surfaceMaterial unset on the ForearmDepthSurface component and the mesh renders as a translucent cyan debug surface, no material or textures to author. To project a UI onto it, assign a material whose shader exposes _MainTex and set portraitTexture / landscapeTexture.

The on-arm UI is static art. portraitTexture and landscapeTexture are fixed images stretched onto the display window; they do not adapt to arm size or shape, so a layout authored for one forearm may not line up on another. Treat displayWidth / displayHeight / displayOffset (and homeTwistDeg) as physical fits to the wearer, then author the textures to the window you set.


Example Experiments

master is the surface system itself. The experiments branch holds example experiment apps built on top of the surface together with Tyler (@tydevlieger). That branch is experimental: quick study code, unoptimized and unpolished. Read it as a demo of what the surface supports, not as part of the system itself.


Project Structure

Application code and assets live in Assets/_Project/; everything else under Assets/ is SDK- or Unity-managed.

Assets/_Project/
├── Scripts/
│   └── Surface/
│       ├── Core/                  depth pipeline stages
│       │   ├── ArmFrame.cs            wrist/elbow -> arm coordinate frame
│       │   ├── DepthReadback.cs       dispatch, async GPU readback + CPU unprojection
│       │   ├── DepthStabilizer.cs     GPU hand mask + 3-frame temporal median
│       │   ├── HandMask.cs            baked hand mesh + fingertip positions
│       │   ├── SurfaceExtractor.cs    seed + BFS flood isolation
│       │   ├── BoundarySmoother.cs    parallel Burst boundary smoothing
│       │   ├── MeshGenerator.cs       parallel mesh + UV + normal emission
│       │   └── SurfaceUV.cs           shared display UV mapping (mesh + touch)
│       ├── Buffer/                shared NativeArray data buses
│       │   ├── SurfaceBuffer.cs
│       │   └── MeshBuffer.cs
│       ├── ForearmDepthSurface.cs     root MonoBehaviour; orchestrates the pipeline
│       └── ForearmInteraction.cs      touch detection against the surface
├── Shaders/
│   ├── ForearmProjection.shader     URP transparent shader for the surface mesh
│   ├── DepthTemporalMedian.shader   3-frame reprojected median that stabilizes the depth
│   └── HandMaskRender.shader        hand silhouette grown into the two-zone mask
├── Materials/   Scenes/   Textures/

Key Parameters

Primary tuning surface, on the ForearmDepthSurface component:

Parameter Default Description
handMarginTexels 8 Depth texels the hand silhouette is grown by, covering the stereo bleed/lift around the hand. The finger inside it is carved from depth history; the bleed ring is reconstructed from clean arm. Raise until the lift clears.
occlusionMarginTexels 1 Cushion (depth texels) around the hand where measured depth is never trusted: the strongest bleed plus the real hand peeking past the rendered mask. Rebuilt at borrowed arm depth, so the canvas reaches the hand silhouette instead of a wider hole. Stays below handMarginTexels
borrowDepthBand 0.03 m When reconstructing the bleed ring, the depth window behind the nearest borrowed sample that still counts as the same surface (rejects a farther background, e.g. a table behind the arm)
enablePalm true Include the palm (wrist -> middle-finger MCP) in the reconstruction; off = forearm only
seedRadialDist 0.05 m Inner radius for confident forearm seed cells
maxFloodDist 0.1 m Outer wall that caps BFS flood growth away from the arm
maxFromElbow 0.02 m How far past the elbow the forearm cylinder extends (the wrist-side cap is flat; the palm cylinder takes over)
connectivityThreshold 0.01 m Max 3D step between adjacent flood cells to count as connected
edgeSmoothPasses / edgeWindowRadius 3 / 2 Boundary smoothing iterations and per-pass neighborhood half-width (cells)
depthStepRatio 0.15 Triangle discontinuity cut: drops a face whose cells differ in true depth by more than this fraction. Grazing-tolerant (fills steep surface, no holes) but cuts self-occluded folds (no webbing)
displayHeight / displayWidth 0.4 / 0.4 m Physical size of the UV display window (equal = square pixels)
displayOffset 0.08 m Center of the display window along the arm from the wrist
homeTwistDeg 67° Home-pose wrist-roll offset that anchors the pronation scroll so the home pose lands on the palmar panel. Measured on the reference rig; may differ by rig or arm, so adjust it if the panels sit off.
orientationMode Auto Auto swaps the portrait/landscape image with the arm's pitch; Portrait/Landscape lock to one
portraitTexture none Image shown upright; drives the material's UI Texture
landscapeTexture none Image shown sideways (author it already sideways)

Touch tuning, on the ForearmInteraction component:

Parameter Default Description
touchHoverHeight 0.008 m How far above the surface a finger still registers
touchDepth 0.04 m How far through the surface a press can go before being ignored
maxCellSearchDist 0.04 m Max arm-frame distance to the nearest surface cell for a touch to register

License

The original work in this repository (primarily the code under Assets/_Project/) is licensed under the Apache License 2.0, © 2026 Trey Tuscai. You are free to use, modify, and build upon it, provided you retain the copyright and attribution notices (see NOTICE).

Third-party SDKs vendored elsewhere under Assets/ and Packages/ (Meta XR SDK, Meta Movement SDK, etc.) are governed by their own license terms, not the Apache License above.

About

Real-time on-skin touch UI for Meta Quest 3. Reconstructs your forearm from environment depth and makes it a touchscreen.

Topics

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages