Releases: ReactVision/viro
Release list
v2.57.2
v2.57.2 — 16 KB page-size alignment (completion)
This release completes the 16 KB memory-page alignment work started in previous releases,
unblocking Google Play / Meta Quest Store submission for Android 15+ devices.
Highlights
- All bundled native libraries are now 16 KB-page aligned. v2.57.0 aligned
libopenxr_loader.so, but the prebuiltlibc++_shared.soinside the renderer
AAR was still 4 KB-aligned (2**12) and kept failing the requirement. Because
it's a prebuilt copied verbatim from the NDK sysroot, the
-Wl,-z,max-page-size=16384linker flag (which already aligns everything Viro
compiles) can't touch it — and NDK r21–r26 ship it at 4 KB. The renderer's
Android NDK was bumped from r25 → r27 (27.1.12297006), whose
libc++_shared.sois 16 KB-aligned, and the prebuilt AARs
(viro_renderer-release.aar,react_viro-release.aar) were regenerated.
Notes
- Every 64-bit (
arm64-v8a) library now reports ≥ 16 KB segment alignment —
most at 16 KB (2**14), withlibvrapi.so/libgvr*.soat 64 KB (2**16).
Verified with Google'scheck_elf_alignment.sh(0 unaligned). - ELF segment alignment is one half of the requirement; ensure the consuming app
also packages each.souncompressed and 16 KB-aligned in the APK/AAB
(AGP 8.2+ handles this automatically). - Pairs with
@reactvision/virocore2.57.2.
See CHANGELOG.md for full detail.
v2.57.1
v2.57.1 — Mixed Reality on Meta Quest
This release brings Viro's AR component API to Meta Quest 3 / 3S through the
OpenXR renderer — no new API to learn. The same ViroARScene you ship on phones
now runs in passthrough mixed reality on Quest.
Highlights
-
Mixed Reality scenes on Quest. Pass a
ViroARScenetoViroXRSceneNavigator
and it runs on phones (ARCore) and Quest (OpenXR).onAnchorFound,
ViroARPlane, andViroARPlaneSelectorfire from the room's floors, walls,
ceilings, and tables, with passthrough enabled automatically. -
Object detection on Quest.
ViroObjectDetectornow runs on Quest 3 / 3S,
driven by the Meta Passthrough Camera API. v1 emits labels + normalized boxes. -
Passthrough styling. New
setPassthroughStyle(viewTag, { opacity, edgeColor })
tunes passthrough opacity and edge-highlight colour at runtime. -
Reliability fixes. Passthrough no longer renders a black background, and an
initialpassthroughEnabledprop is honored even when set before the renderer
initializes.
Upgrade notes
- Permissions (Quest). The Expo plugin now declares
horizonos.permission.USE_ANCHOR_API(room planes) and
horizonos.permission.HEADSET_CAMERA(object-detection camera). Both are
runtime-granted — request them in-app oradb shell pm grant <pkg> <perm>. - Room planes require Space Setup. Plane data comes from the Quest room model;
run Space Setup on the headset first, or no plane anchors are produced. - MR scenes: set
hdrEnabled={false}onViroXRSceneNavigatorfor now — the
HDR post-process path forces an opaque composite that hides passthrough. (A fix
to lift this restriction is planned.) - Object detection on Quest is Horizon OS v74+ and emits labels + 2D boxes
only (noworldPosition/screenBoundingBoxyet).
See CHANGELOG.md for the full list of changes, and
docs/QUEST_SETUP.md for setup details.
v2.57.0
ReactVision ViroReact v2.57.0
This release adds on-device object detection, improves AR depth accuracy on iOS, fixes layered glTF/GLB animation playback, aligns the OpenXR loader to the Android 16 KB page-size requirement, and adds Expo SDK 56 support.
ViroObjectDetector
A new component that runs YOLOE object detection through ONNX Runtime on the device. It works only in AR: it shares the camera feed of the enclosing ViroARSceneNavigator rather than opening a camera of its own. Detections are reported through onDetection with a label, confidence, and normalized bounding box.
Each detection also includes a screenBoundingBox in density-independent points, aligned to the on-screen camera preview, so it maps directly onto an absolutely-positioned overlay without extra math. Detection and the 2D overlay work the same on iOS and Android. The 3D worldPosition raycast is currently iOS only.
Inference runs in the companion package @reactvision/react-viro-onnx, which uses the NNAPI execution provider with FP16 on Android. The detector has no built-in inference path: if the provider is not installed, it produces no detections and calls onError.
npm install @reactvision/react-viro @reactvision/react-viro-onnx{ "expo": { "plugins": ["@reactvision/react-viro", "@reactvision/react-viro-onnx"] } }Setup, model bundling, and the coordinate system are documented in docs/ViroObjectDetector.md.
AR depth on iOS
Depth points are now read directly from the AR depth map instead of being approximated, and the monocular depth model is warmed up before its first use. This removes the inaccurate and late depth values that previously showed up on the first frames and improves occlusion and hit-testing on devices without LiDAR.
ViroARScene gains an onDepthReady event on iOS and Android. It fires once, when AR depth first becomes available, so you can wait for it before enabling features that depend on depth.
Layered GLB animations (VIRO-5741)
glTF/GLB skeletal clips that mixed STEP and LINEAR interpolation, or whose channels used several independent time-grids, were being dropped or flattened and froze instead of playing. This is common in Blender exports with layered animations. The loader now resamples all of a skin's channels onto one common time-grid and merges each joint's channels into a single index-aligned keyframe animation, with a per-frame density cap so skinning cost stays bounded on large clips. These animations now play to the end.
OpenXR loader 16 KB page-size alignment
The bundled OpenXR loader library, libopenxr_loader.so, is now aligned to a 16 KB page size (the loader was updated from 1.1.38 to 1.1.49). This native .so previously used 4 KB alignment, which fails the 16 KB memory-page requirement for Android 15 and later and blocks Google Play and Meta Quest Store submission. Apps that do not use XR are not affected.
Expo SDK 56
The config plugin and prebuilt artifacts build against Expo 56 and React Native's new architecture.
Other fixes
- VR controller input works again after moving the VR event listener to the new architecture.
onDraginStudioSceneNavigatornow fires.- visionOS-only sources are excluded from the iOS CocoaPods build, which fixes compile errors in iOS-only targets.
Experimental
- Initial visionOS renderer: a Metal-based renderer and driver, the React Native bindings, and the renderer bridge. This is still in progress and not ready for production.
Upgrading
npm install @reactvision/react-viro@2.57.0
# For ViroObjectDetector:
npm install @reactvision/react-viro-onnx
npx expo prebuildThere are no breaking API changes. If you use ViroObjectDetector, bundle your .onnx model into the native project as described in the component docs. Note that expo prebuild --clean removes manually placed assets.
v2.56.0
Release Notes — v2.56.0
What's new
Dynamic mesh node
VRODynamicMeshNode lets you update geometry every frame without recreating the node or triggering GPU reallocation. This unlocks a class of apps that wasn't practical before: procedural terrain, marching cubes in AR, CPU skinning of custom formats, and output from external simulation engines. The vertex buffers (positions, normals, UVs, colors) are updated in place using an orphan + sub-data path on OpenGL, keeping frame time flat even at 60 fps.
Virtual game controller
Two new native components — ViroVirtualJoystick and ViroVirtualButton — bring on-screen game controls to AR apps with sub-2 ms input latency. Touch events are handled entirely at the native layer and written to a shared state registry that your C++ game loop reads every frame, with no JS bridge round-trips in the hot path. Both components also fire JS callbacks (onStickChange, onPressIn, onPressOut) so you can drive UI reactions from the same input.
<ViroVirtualJoystick
controllerId="p1"
stickSide="left"
radius={60}
onStickChange={(e) => move(e.nativeEvent.x, e.nativeEvent.y)}
/>
<ViroVirtualButton controllerId="p1" button="A" size={52} />PCM audio streaming
StreamingAudioManager opens a real-time audio path where you push raw PCM samples as they are produced, rather than loading a complete file. The API is the same on iOS and Android:
StreamingAudioManager.create('voice');
StreamingAudioManager.beginStreaming('voice', 24000, 1);
StreamingAudioManager.play('voice');
// from your audio producer loop:
StreamingAudioManager.pushSamples('voice', base64Float32PCM);Useful for TTS output, procedural sound synthesis, physics-driven audio, and any embedded engine that generates audio samples at runtime.
AR World Mesh — public subscriber API
The AR world mesh is now a general-purpose multi-consumer provider. Any part of your app can subscribe to receive the full mesh geometry (vertices, indices, confidence) along with a source tag that tells you whether the data came from LiDAR, the monocular depth model, or ARKit plane anchors.
This release also adds:
- Plane fallback — devices without LiDAR or the Depth API now get a mesh built from detected AR planes, so the feature degrades gracefully instead of producing nothing.
- Per-consumer decimation — each subscriber can cap its triangle budget independently, so a physics engine and a nav-mesh consumer can coexist without fighting over resolution.
- Async physics — Bullet BVH construction runs on a background thread, preventing the ARKit frame drops that appeared when processing large meshes on the render thread.
- Full Android support — ARCore depth mode is now activated automatically when world mesh is enabled, and plane mesh generation is implemented end-to-end on Android.
Game loop
ViroGameLoop and the useGameLoop / useFixedUpdate hooks give you a proper per-frame callback with both variable-dt and deterministic fixed-step modes. Fixed-step mode is useful for physics engines and networked simulations that require ticks at an exact frequency.
<ViroGameLoop
onUpdate={({ dt, elapsed }) => { /* variable, every frame */ }}
fixedHz={30}
onFixedUpdate={({ dt }) => { /* deterministic, 30 times/s */ }}
/>ViroGameLoopUtils provides setPosition, setRotation, and setScale that write directly to the native node, bypassing React's reconciler for zero-overhead transforms from inside the loop.
Improvements
Monocular depth — new metric model, better occlusion
The monocular depth estimator has been upgraded to Depth Anything V2 (metric, indoor), a model trained on indoor scenes that outputs depth in meters. Compared to the previous model, occlusion triggers more reliably and at the correct distances for typical indoor AR use.
The pipeline also received several quality improvements: temporal confidence synthesis to avoid upgrading hit-tests on unstable depth pixels, in-place GPU texture updates that eliminate a per-frame allocation, correct handling of all four device orientations, and adaptive thermal throttling to prevent overheating during extended sessions.
Two new props on ViroARSceneNavigator let you tune the estimator at runtime:
| Prop | Default | Description |
|---|---|---|
monocularDepthScale |
1.0 |
Multiplies all depth values before occlusion. Lower values make occlusion trigger sooner. |
monocularDepthTargetFPS |
5 |
Inference rate. Raise for smoother occlusion; lower to reduce thermal load. |
Bug fixes
-
SIGABRT on Android 14+ (API 34) — a hard crash in the ARCore anchor handling path has been resolved. The crash occurred when
NewStringUTFreceived a non-Modified-UTF-8 anchor ID, which became common on Android 14. -
Quest Store submission rejected due to forced landscape orientation — the Viro Android plugin was unconditionally setting landscape orientation on
MainActivity, which caused Quest Store validation failures for non-Quest apps and overrode the orientation set inapp.json. The override now applies only to Quest apps.
Upgrade notes
No breaking changes. All new components and hooks are additive. StreamingAudioManager, ViroVirtualJoystick, ViroVirtualButton, ViroGameLoop, and the useGameLoop family are available as named exports from @reactvision/react-viro.
The setUpdateCallback on VROARWorldMesh is retained but deprecated in favor of the new subscribe / unsubscribe API.
v2.55.0
This release adds first-class Meta Horizon OS support, a new ReactVision Studio integration layer, and a cross-reality JS routing component that lets a single app target phones, tablets and Meta Quest from one React surface. It also ships three Android / iOS stability fixes that unblock the latest Android ABI and resolve two regressions reported by the community.
Highlights
Meta Horizon OS support
VR scenes run natively on Meta Quest 3 / Quest Pro / Quest 2 / Quest 1 via a new OpenXR backend in virocore. The integration is end-to-end and has been device-validated at 90 Hz on Quest 3:
-
Dual-Activity architecture. The same APK ships an Android phone / tablet panel (
MainActivity) and an exclusive VR Activity (VRActivity, emitted automatically by the Expo plugin whenxRMode: ["QUEST"]is set). Consumers don't have to manage the switch — mount<ViroXRSceneNavigator>and entering/exiting VR is handled for you (see Cross-reality JS layer below). -
Two simultaneous pointers. Right and left controllers (or tracked hands) each get an independent cyan laser line, hover state, and click resolution. Either side can be controller-tracked or hand-tracked; right is the single-pointer fallback when only one is available.
-
Full input set. Touch controllers (triggers, grips, A/B/X/Y, menu, thumbsticks, haptics),
XR_EXT_hand_trackingjoints, andXR_FB_hand_tracking_aimfor fingertip-aimed pointing. Pinch-to-click and grip-to-grab are detected per hand. B and Menu buttons exit VR; returning from the Quest system menu relaunches automatically, explicitexitVRScene()calls don't. -
Stable hover and click on small UI targets. Pose hysteresis and a 75 ms hover grace window absorb natural OpenXR aim jitter, so hover state doesn't oscillate and trigger pulls land on first try.
-
Passthrough + recenter.
VRModuleOpenXR.setPassthroughEnabled(viewTag, …)andrecenterTracking(viewTag)for in-app control. -
Full lighting pipeline. HDR, PBR, bloom, shadows — all validated at 90 Hz with App=5–6 ms frame time on Quest 3.
-
Fast Refresh works inside VR. Metro Fast Refresh, timers, and
requestAnimationFramekeep working when the headset is on, just like in the 2D panel. -
questAppIdplugin option. Sideloaded builds no longer show the system "App Name Unavailable" overlay.
Cross-reality JS layer
Three additions to @reactvision/react-viro collapse the multi-platform story into a one-liner for downstream apps.
-
ViroXRSceneNavigator— auto-detects the platform and mounts the right navigator:ViroVRSceneNavigatoron Meta Quest,ViroARSceneNavigatoron iOS / non-Quest Android. Pass a single shared scene, or per-platform scenes:// Shared scene <ViroXRSceneNavigator initialScene={{ scene: MyScene }} /> // Per-platform scenes <ViroXRSceneNavigator arInitialScene={{ scene: MyARScene }} vrInitialScene={{ scene: MyVRScene }} />
-
Platform guards on the existing navigators.
ViroARSceneNavigatorshort-circuits with a clean fallback when mounted on Quest instead of trying to start an AR session;ViroVRSceneNavigatordoes the same on non-Quest Android instead of falling through to the deprecated Google Cardboard split-screen renderer. Both expose an override prop (questFallback/nonQuestFallback) for custom UIs (e.g. a "Launch VR" launcher button). -
isQuestandhasOpenXRSupport— runtime-detected booleans exported from the package root. Detection is hardware-ID based (Build.MANUFACTURER,BRAND,MODELviaPlatform.constants), not module-presence based, so a single APK that bundles Quest support does not misidentify regular phones as Quest.
Multi-pointer hooks
Two helpers for apps that want simple aggregated state without writing the per-source bookkeeping themselves.
import {
useAnySourceHover,
useAnySourcePressed,
} from "@reactvision/react-viro";
function MyButton() {
const [hovered, onHover] = useAnySourceHover();
const [pressed, onClickState] = useAnySourcePressed();
return (
<ViroNode onHover={onHover} onClickState={onClickState} onClick={fire}>
<ViroQuad
scale={pressed ? [0.95, 0.95, 0.95] : [1, 1, 1]}
materials={[hovered ? "btnHover" : "btnIdle"]}
/>
</ViroNode>
);
}Both hooks return [bool, handler] and deduplicate per source ID so a second pointer crossing an already-hovered node does not produce spurious enter/exit toggles in JS state. Apps that do care which specific pointer fired the event (drag-to-controller, single-handed gestures) read source directly from the raw callback.
StudioSceneNavigator
Drop-in component for ReactVision Studio scenes. Fetches a Studio-authored scene by UUID via rvGetScene(sceneId) — auth is the project's API key, wired through the Expo plugin (rvProjectId in app.json):
import { StudioSceneNavigator } from "@reactvision/react-viro";
<StudioSceneNavigator sceneId="abc-123-uuid" style={StyleSheet.absoluteFill} />;What it renders, end to end:
- 3D models (GLB / VRX), images, video, and text from the scene's asset registry
- Per-asset placement (position / rotation / scale) and physics bodies
- Scene functions:
NAVIGATION(push to another scene),ALERT,ANIMATION - Collision bindings (asset-pair triggered functions)
- Animation registry (Studio-authored keyframes pushed into
ViroAnimations) - Material configs with optional shader modifiers — animated
timeuniform for animated presets,_rf_vpw/_rf_vphviewport uniforms for screen-space effects, and auto-flaggedrequiresCameraTexturewhen the shader samples the camera feed - Image-tracking targets and the
Viro360Image/Viro360Videobackground - Physics world configs
StudioSceneNavigator is fully cross-reality on Quest — the same sceneId mounts the scene in AR on phones / tablets and in VR on headset, with no consumer-side branching.
Underlying API surface
StudioSceneNavigator is powered by two new endpoints in the ReactVision Cloud Anchor SDK (libreactvisioncca, shipped inside virocore):
getScene(sceneId, callback)— returns the full scene response (metadata, assets, animations, collision bindings, scene functions, project info).getSceneAssets(sceneId, callback)— asset-list-only variant for clients that already have scene metadata cached.
Both are exposed to JS via ViroARSceneNavigator.rvGetScene(sceneId) and rvGetSceneAssets(sceneId), and authenticate with the project API key wired through the Expo plugin. Existing Cloud Anchor / Geospatial endpoints are unchanged.
Fixes
-
16 KB
.sopage alignment forlibvrapi.so(Issue A). The Android 2025 ABI requires all shipped.sofiles to align to 16 KB pages.libvrapi.sois now repackaged with-Wl,-z,max-page-size=16384, resolving load failures on devices with the new page size. -
ViroARImageMarkerchildren fixed-on-screen after re-detection (Android, GitHub #465). Models parented to an image marker no longer pin to screen coordinates after the target was lost and re-acquired in v2.54.0. Markers re-anchor cleanly to the detected world pose every time, including subsequent re-detection. -
iOS
ViroPortalSceneportal-tree stability (GitHub #452). Continued portal-render-pass hardening on top of the v2.54.0 fix:- Portal stencil silhouette no longer drops transparent entry fragments before alpha discard runs.
- 360° background inside a portal is no longer overwritten by the AR camera background drawn afterwards.
- The interior of a portal hole no longer reveals the portal interior when the user is outside a nested exit-frame portal.
- AR occlusion is disabled inside the portal interior so virtual content is no longer discarded by depth-based occlusion when nested.
Compatibility
- React Native 0.83, Expo 54 + Expo 55 for AR / non-Quest paths (unchanged from 2.54.0).
- Android Gradle Plugin 8.7+ is required for 16 KB-aligned APKs. Minimum SDK unchanged (24).
- Meta Quest support requires React Native ≥ 0.83 / Expo ≥ 55.
ViroXRSceneNavigatorthrows an actionable JS error if the runtime is below the threshold. AR continues to work on the Expo 54 + RN 0.79 baseline. - Meta Quest support also requires the Quest variant of the Viro Android package —
ReactViroPackage(ReactViroPackage.ViroPlatform.QUEST)inMainApplication.kt, plusxRMode: ["QUEST"]in the Expo plugin config. Both are emitted automatically byexpo prebuildwhen the plugin is configured. - Recommended install path: Expo Dev Client. Bare React Native is not tested for this release. It should work but will need a substantial amount of manual wiring —
MainApplication.ktpackage registration, theVRActivityAndroid Activity declaration with the correct intent filter and Quest hardware features, thexRModeQuest manifest features, and iOS Podfile configuration. The Expo plugin generates all of these automatically. Bare RN support will be revisited in a follow-up release. - iOS and non-Quest Android code paths are unchanged. Existing apps upgrade with no code changes.
Migration
No breaking JS API changes. Existing ViroARSceneNavigator and ViroVRSceneNavigator usage continues to work. New cross-reality apps should mount <ViroXRSceneNavigator> and wire its onExitViro prop to take the panel back to wherever VR was launched from (e.g. navigation.goBack()). B / Menu buttons exit VR automatically; exitVRScene() from @reactvision/react-viro is only needed for programmatic in-scene exits.
v2.54.0
React Viro — v2.54.0 Release Notes
Release date: March 31, 2026
What's New
Semantic Masking
Virtual objects can now react to what the camera sees in the real world. A material can be configured to appear only on specific surfaces — for example, render a cloud effect exclusively on pixels classified as sky, or hide a character wherever a person is detected. Eleven real-world categories are supported: sky, building, tree, road, sidewalk, terrain, structure, object, vehicle, person, and water. Available on both Android and iOS.
iOS setup: Semantic masking on iOS requires the ARCore Semantics pod. Add includeSemantics: true to your Expo plugin config in app.json to have it included automatically. If you are already using provider: "arcore" or includeARCore: true, the pod is already included and no extra config is needed.
["@reactvision/react-viro", {
"ios": {
"includeSemantics": true
}
}]Animated GLB Models
3D models in GLB/glTF format with embedded animations now play correctly. This covers the three main animation systems used by artists — skeletal rigs (character movement), morph targets (facial expressions, blend shapes), and skinning (soft-body deformation). No extra configuration is needed; animations embedded in the file are automatically available.
Permission Helpers
Two new utility functions make it easier to handle camera and location permissions:
requestRequiredPermissions(permissions?)— prompts the user for the specified permissions. Pass a list to request only what your feature needs ("camera","microphone","storage","location"), or call with no arguments to request all four at once.checkPermissions(permissions?)— reads the current permission status without prompting the user. Useful for checking what has already been granted before deciding whether to ask.
Expo 54 & 55 Support
The package is now compatible with both Expo SDK 54 and Expo SDK 55.
Bug Fixes
Android — App crash on launch (Android 15 / 16)
Apps were crashing immediately on launch on devices running Android 15 or 16 due to a new 16 KB memory page-size requirement introduced by Google. All native libraries have been rebuilt to comply with the new standard.
Android — App crash after returning from background
A combination of three separate issues caused a guaranteed crash whenever the operating system suspended the app and reclaimed GPU memory. On resume, the app attempted to use GPU resources that no longer existed. All three root causes have been fixed; the app now recovers cleanly from background suspension.
Android — ViroARImageMarker detection callback not firing
The onAnchorFound callback on ViroARImageMarker was never called on Android, making it impossible to respond to image detection events in JavaScript. The native event bridge has been corrected.
Android — Model texture overlaying the screen during video recording
When recording AR scenes, the 3D model's texture was incorrectly rendered over the entire screen instead of just the model. This was caused by a stale GPU state cache when switching between the display and recording graphics contexts. The cache is now correctly reset on each context switch.
iOS — Portal scene interior not rendering (#452)
On iOS, looking through a ViroPortalScene showed only the camera feed — none of the 3D content inside the portal was visible. This was caused by leftover GPU stencil state from the previous frame that prevented interior content from passing the render test. The stencil state is now correctly reset each frame.
iOS — EAS cloud build failure (Podfile syntax error) (#441)
iOS builds through Expo Application Services (EAS) were failing with a CocoaPods syntax error in the generated Podfile. The Expo plugin now generates a valid Podfile in all build environments.
Summary
| Area | Change |
|---|---|
| New feature | Semantic masking for materials (Android + iOS) |
| New feature | Animated GLB — skeletal, morph targets, skinning |
| New feature | requestRequiredPermissions utility |
| New feature | checkPermissions utility |
| Platform | Expo 54 & 55 support |
| Fix — Android | Crash on launch on Android 15 / 16 (16 KB page size) |
| Fix — Android | Crash after returning from background |
| Fix — Android | ViroARImageMarker onAnchorFound never fired |
| Fix — Android | Model texture overlay during video recording |
| Fix — iOS | Portal scene interior content not rendering (#452) |
| Fix — iOS | EAS cloud build Podfile syntax error (#441) |
v2.53.1
Hotfix: In MALI GPU devices, the camera texture may get frozen while switching AR scenes.
v2.53.0
@reactvision/react-viro v2.53.0 — Release Notes
⚠️ Breaking Changes
ViroARSceneNavigator — provider replaces cloudAnchorProvider and geospatialAnchorProvider
The two separate props are merged into a single provider prop that controls both backends simultaneously. provider defaults to "reactvision" so the prop can be omitted entirely in most cases.
Before:
<ViroARSceneNavigator
cloudAnchorProvider="reactvision"
geospatialAnchorProvider="reactvision"
initialScene={{ scene: MyARScene }}
/>After:
// defaults to "reactvision" — prop can be omitted
<ViroARSceneNavigator initialScene={{ scene: MyARScene }} />
// Or to override:
<ViroARSceneNavigator provider="arcore" initialScene={{ scene: MyARScene }} />ViroCloudAnchorProvider and ViroGeospatialAnchorProvider are now deprecated aliases for the new ViroProvider type. They still compile with a deprecation warning.
Expo plugin (withViro) — provider replaces cloudAnchorProvider and geospatialAnchorProvider
Before:
["@reactvision/react-viro", {
"cloudAnchorProvider": "reactvision",
"geospatialAnchorProvider": "reactvision",
"rvApiKey": "...",
"rvProjectId": "..."
}]After:
["@reactvision/react-viro", {
"provider": "reactvision",
"rvApiKey": "...",
"rvProjectId": "..."
}]ViroARPlaneSelector requires manual anchor wiring
The component no longer self-discovers planes. You must now forward ViroARScene anchor events to it via a ref:
const selectorRef = useRef<ViroARPlaneSelector>(null);
<ViroARScene
anchorDetectionTypes={["PlanesHorizontal", "PlanesVertical"]}
onAnchorFound={(a) => selectorRef.current?.handleAnchorFound(a)}
onAnchorUpdated={(a) => selectorRef.current?.handleAnchorUpdated(a)}
onAnchorRemoved={(a) => a && selectorRef.current?.handleAnchorRemoved(a)}
>
<ViroARPlaneSelector ref={selectorRef} alignment="Both" onPlaneSelected={...}>
<MyContent />
</ViroARPlaneSelector>
</ViroARScene>What's new
ReactVision Cloud Anchors (RVCA)
Place persistent AR content that other users can find — no Google Cloud account required. RVCA is a proprietary closed-source SDK; its implementation is not part of the open-source ViroCore or react-viro distribution.
The "reactvision" provider routes hostCloudAnchor / resolveCloudAnchor through the ReactVision platform. The existing hostCloudAnchor, resolveCloudAnchor, and onCloudAnchorStateChange API is unchanged.
- Hosting: scan your environment briefly, then call
hostCloudAnchor. Returns an error immediately if the environment has not been sufficiently observed — no silent fallback. - Resolving: the SDK matches the live camera feed against stored anchor data and returns a pose once a confident localisation is established. Cross-platform (iOS host → Android resolve and vice versa) is fully supported.
- TTL:
hostCloudAnchoraccepts attlDaysparameter (1–365) to control anchor expiry on the backend. - GPS tagging: set the device location before hosting to embed GPS coordinates as anchor metadata. Enables proximity search via
rvFindNearbyCloudAnchors.
8 new methods on arSceneNavigator for full CRUD and analytics on cloud anchors:
| Method | Description |
|---|---|
rvGetCloudAnchor(anchorId) |
Fetch a single anchor record |
rvListCloudAnchors(limit, offset) |
Paginated list of all project anchors |
rvUpdateCloudAnchor(id, name, desc, isPublic) |
Rename / re-describe an anchor |
rvDeleteCloudAnchor(anchorId) |
Permanently delete an anchor and its assets |
rvFindNearbyCloudAnchors(lat, lng, radius, limit) |
GPS proximity search |
rvAttachAssetToCloudAnchor(id, url, size, name, type, userId) |
Attach a hosted file |
rvRemoveAssetFromCloudAnchor(anchorId, assetId) |
Remove an attached asset |
rvTrackCloudAnchorResolution(...) |
Record resolve analytics manually |
ReactVision Geospatial Anchor Management
5 new management methods for GPS-tagged anchors (backed by the proprietary RVCA SDK):
| Method | Description |
|---|---|
rvListGeospatialAnchors(limit, offset) |
Paginated list |
rvGetGeospatialAnchor(anchorId) |
Fetch a single geospatial anchor |
rvFindNearbyGeospatialAnchors(lat, lng, radius, limit) |
GPS proximity search |
rvUpdateGeospatialAnchor(id, sceneAssetId, sceneId, name) |
Update metadata |
rvDeleteGeospatialAnchor(anchorId) |
Permanently delete |
createGeospatialAnchor, createTerrainAnchor, and createRooftopAnchor are now supported with provider="reactvision". No VPS, no ARCore Geospatial API, and no ARCore pods are required.
Asset upload:
rvUploadAsset(assetType, filename, data, appUserId) — uploads a file to ReactVision storage and returns a URL and asset ID. Supported assetType values: "3d-model", "image", "video", "audio". The returned asset ID can be linked to a geospatial anchor via rvUpdateGeospatialAnchor or to a cloud anchor via rvAttachAssetToCloudAnchor.
New geospatial utilities
gpsToArWorld(devicePose, lat, lng, alt)— converts a GPS coordinate to an AR world-space[x, y, z]offset from the device's current geospatial pose.latLngToMercator(lat, lng)— converts a GPS coordinate to a metric 2D position. Building block forgpsToArWorldand custom geo math.
Both are exported from @reactvision/react-viro.
New ViroProvider type
Canonical union type "none" | "arcore" | "reactvision" exported from the package. Replaces the deprecated ViroCloudAnchorProvider and ViroGeospatialAnchorProvider types.
Shader modifier system — major expansion
Shader modifiers now support the full range of custom GPU effects:
- Custom textures — declare
uniform sampler2Din modifier code and pass textures viamaterialUniforms. Swap them at runtime withupdateShaderUniform. - Vertex → fragment data — use the new
varyingsfield to pass typed values from a Geometry modifier to a Surface or Fragment modifier (e.g. displacement amount driving roughness). - Scene depth access — set
requiresSceneDepth: trueon a fragment modifier to receivescene_depth_textureautomatically. Enables soft particles, contact glow, and intersection effects. - Live camera feed on geometry — set
requiresCameraTexture: trueto sample the AR camera on any surface. Platform differences (samplerExternalOESvssampler2D) handled invisibly. Enables magnifying glass, portal, refraction, and warp effects. - Deterministic ordering — modifiers now have a
priorityfield so engine internals never override your effects regardless of attachment order.
AR plane detection improvements
- Plane detection now defaults to both horizontal and vertical — no need to set
anchorDetectionTypesexplicitly. - Objects placed via
ViroARPlaneSelectornow appear at the exact tap point, not the plane centre. onPlaneSelectedreceives the tap position as a third argument:(plane, tapPosition?) => void.- New props:
onPlaneRemoved,hideOverlayOnSelection,material. useActualShapenow defaults totrue— polygon boundary used instead of bounding rect.- New public method
handleAnchorRemovedonViroARPlaneSelectorfor use in theonAnchorRemovedcallback.
Depth sensor access
ViroARSceneNavigator has three new props for apps that need depth data without full occlusion:
depthEnabled— activates LiDAR / monocular depth / ARCore Depth forDepthPointhit tests.depthDebugEnabled— overlays the depth map on the camera feed.preferMonocularDepth(iOS only) — forces monocular depth even on LiDAR devices.
Bug fixes
- Models with washed-out / overexposed colours — GLB and other 3D assets appeared too bright due to an emissive colour value being incorrectly added to all materials. Fixed.
- iOS video recording silent failure —
startVideoRecording/stopVideoRecordingwere broken on iOS 17+ with the New Architecture. MultipleAVAssetWriterandAVAudioSessionissues fixed; recording now falls back to video-only if audio fails. - Android crash when closing a scene with physics — null pointer dereference on scene close for physics-enabled nodes. Fixed.
- Android New Architecture dev menu error — "You should not use ReactNativeHost directly" error on startup. Fixed.
- Ghost planes in
ViroARPlaneSelector— pre-allocated slot index mapping was non-deterministic causing planes to appear in wrong positions or persist after removal. Fully rewritten. - Selected plane disappeared immediately on tap — opacity logic inversion fixed.
- Children rendered on every plane slot — children now rendered once, only on the selected plane.
onPlaneDetectedreturn value ignored — returningfalsefromonPlaneDetectedpreviously had no effect. Now correctly prevents the plane from being added to the visible set.- Removed planes not cleaned up — disappeared planes were never removed from internal state, accumulating over time.
handleAnchorRemovednow deletes the entry and resets selection if needed. - Plane updates silently dropped for large surfaces — ARKit update threshold logic was AND instead of OR; large planes (floors, walls) rarely updated. Fixed.
- Rapid plane updates dropped on initial detection — fixed 100 ms throttle replaced with adaptive rate (33 ms for first 20 updates, 66 ms thereafter).
v2.52.1
What's Changed
- Fix ARCORE crashes and depth issues. by @doranteseduardo in #439
Full Changelog: v2.52.0...v2.52.1
v2.52.0 - Shader System & Memory Improvements
Summary
This release adds shader customization support for iOS and Android, fixes critical memory leaks, and improves AR depth functionality.
Key Features
Shader System (New)
- Cross-platform shader support
- Real-time shader customization for iOS and Android
- Shader propagation down node trees
- Standardized fragment output
- Texture and animation support in shaders
- Optimized material sharing for better performance
AR & Depth Improvements
- Depth-based AR hit testing
- Monocular depth fallback for non-LiDAR devices
- Fixed depth frame alignment issues
Critical Bug Fixes
- iOS Memory Leaks (Critical): Fixed all memory leaks on iOS platform
- Material Overflow: Fixed cloned materials array overflow crashes
- Portal Crashes: Fixed crashes when unmounting portals on iOS
- Gravity Type Crash: Fixed Android crash (gravity now uses 3D vector
[x, y, z]instead of number) - VRX Asset Loading: Fixed VRX asset loading issues on iOS
- hitResultId: Fixed availability in AR hit tests
Other Improvements
- Performance throttle to prevent system overload
- Refactored thread lock implementation
- Removed debug logs from production code
- Improved TypeScript type definitions