skillissue.sh

house / Quest Passthrough Camera Capture

✓ hosted skill bundlexrv1.0.0MIT5.0 KB
skills/quest-passthrough-camera-capture/SKILL.md

Practical lifecycle rules for safely copying Meta Quest passthrough camera frames into Unity-owned textures.

InstallCLI + MCP
CLIRun from your local shell.
$autovault add jack-arturo/skillissue@775e1e1620d2730c8a67e8b7958448e3e888dfc2:skills/quest-passthrough-camera-capture/SKILL.md --sync-profiles
MCPPaste into an agent MCP tool call.
>add_skill({ source: "github", identifier: "jack-arturo/skillissue@775e1e1620d2730c8a67e8b7958448e3e888dfc2:skills/quest-passthrough-camera-capture/SKILL.md" })
Choose CLI for a shell install or MCP for an agent tool call.
Source
Example type
bundle
hosted skill bundle
Bundle files
1
SKILL.md + resources
Resources
0
inspectable source files
Declared agents
2
from frontmatter
Source
house
pinned GitHub bundle
SKILL.mdview raw →
---
name: quest-passthrough-camera-capture
description: Use when reading Meta Quest Passthrough Camera Access (PCA) frames into a Unity texture — freezing a room snapshot, feeding a shader, or piping camera pixels to generation/inference — especially when the feed's lifetime, permission gating, color space, or a blocking readback is in play.
license: MIT
tags: [unity, quest, passthrough, pca, mixed-reality, meta-xr, rendertexture, shader]
agents: [claude-code, codex]
category: xr
metadata:
  version: "1.0.0"
capabilities:
  network: false
  filesystem: readwrite
  tools: [Read, Edit, Write]
requires-secrets: []
resources:
  - path: story.md
    type: file
---

Reading passthrough camera frames on Quest

PassthroughCameraAccess (Meta XR Core / MRUK) hands you the headset's real

camera feed. It is a native-written texture owned by a component with its own

lifecycle — treat it as a borrowed handle, never as an asset.

Never bind the feed's texture to a renderer

GetTexture() returns the texture the feed writes into from the render thread

and stops writing the moment the feed is disabled. Anything that disables the

feed — a controller's OnDisable, a withdrawn permission, a torn-down scene —

leaves a bound quad showing a live room that freezes, goes black, or references

a destroyed native handle, depending on timing.

Blit into a RenderTexture you allocate and release:

_target = new RenderTexture(source.width, source.height, 0, source.graphicsFormat);
Graphics.Blit(source, _target);

Sizing and format both matter:

  • Size from the source texture each time, not once at startup. CurrentResolution is what the device granted, not what you requested, and it can differ from the request or change mid-session.
  • Match source.graphicsFormat. The feed is R8G8B8A8_SRGB; blitting into a linear target darkens the whole image subtly enough to read as "the capture is murky" rather than as a color-space bug.

Guard every access on `enabled && IsPlaying`

GetTexture() internally validates state and logs an error when the

component is disabled or unpermitted. An unguarded per-frame call produces a

per-frame error line and returns null. Check feed.enabled && feed.IsPlaying

first.

Never call `GetColors()` in a frame path

It is AsyncGPUReadback.RequestIntoNativeArray(...).WaitForCompletion() — a

blocking stall, and its own docs say so. If pixels must reach the CPU, issue a

non-blocking AsyncGPUReadback and consume it a frame or more later. For

display or shader input, the pixels never need to leave the GPU at all.

Capture continuously, freeze by stopping

When you want a frame at a specific moment (an entry, a trigger, a gesture),

do not capture at that moment — allocation plus copy on the one frame the

user is watching is the worst possible placement. Instead blit on a slow

cadence beforehand into a texture nothing samples, and on the trigger simply

stop blitting. The last frame taken becomes the frozen one.

The freeze then has no separate flag to fall out of sync with: not capturing

is frozen.

Do not create a second feed

If anything else in the project already runs PassthroughCameraAccess

components (a stereo lens, a CV pipeline), find and read the existing one

rather than adding your own — they contend for the same camera. Search the

scene with a bounded retry; the feed may not exist on the frame your component

lands, because composition is often permission-gated.

Design for the feed being absent

PCA is device-only and permission-gated. In the Editor, in tests, on an

unsupported headset, or after a denied camera permission there is simply no

feed. That path must be a designed outcome with a bundled fallback asset — not

an error state — because it is the path every non-device run takes.

Keep "has a target" and "has a frame" as separate questions. An

allocated-but-never-blitted RenderTexture is a perfectly valid Texture full

of nothing, and preferring it over a fallback turns every permission-denied

session into a black rectangle.

What testing can and cannot reach

Extract the copy into a source-agnostic method (LatchFrom(Texture)) so an

ordinary Texture2D can stand in for the feed. That gives real coverage of

sizing, creation, release, and whether the blit moves pixels — assert a known

color reads back from the target, because a latch that sizes perfectly and

copies nothing looks identical to a working one from every angle except the

user's.

What no desktop test can answer: whether the live frame arrives upright.

The feed is native-written and Graphics.Blit follows the platform's

texture-origin convention, which a Texture2D stand-in gets right for reasons

a native handle may not. Put "is it flipped or mirrored, and on which axis?" on

the device checklist explicitly.

Related

OVRPassthroughLayer.textureOpacity dims what the wearer sees through the

compositor. It does not affect the raw PCA feed at all — a frame captured while

the passthrough layer is fully dimmed still shows the lit room.

story.md

Why

A borrowed camera texture looks simple right up until permissions change or the native feed disappears mid-frame.

How

Treat the feed as borrowed, copy it into a texture you own, keep CPU readback off the frame path, and build a credible no-camera fallback. The boring bits are the feature.

Related

Follow Unity Quest Build for device evidence and Unity AI Collaboration when more than one tool can touch the Editor.

Bundle contents

Every file declared by this skill is inspectable here. Static resources are previewed from same-origin hosted files; script-like files are shown as text only.

1files
markdownSKILL.md
view raw →

SKILL.md

Primary agent instructions, frontmatter, workflow, and declared resource manifest.

Select a package file to inspect it.

Declared capabilities

network
false
filesystem
readwrite
tools
[Read, Edit, Write]

No secrets declared by this package.

Public, pinned, and inspectable

This house package is installed from the pinned Git commit shown here. Inspect the source and every bundled file before you run it.

package pin775e1e1sourceGitHub packagecompatibilityclaude-code, codex
VersionBundleSourcePinRaw
v1.0.0latest1 files · 5.0 KBhouse775e1e1SKILL.md