Research: native / RN gaussian splat rendering without a WebView #6
Notifications
Due Date
No due date set.
Blocks
#14 Write the Session 1 spec (pose to splat rendering)
lars/splat-indoor-navigation
Reference: lars/splat-indoor-navigation#6
Reference in New Issue
Block a user
Question
Is there a 2026 option for rendering a gaussian splat in React Native without a WebView, and would it change the architecture?
Answer specifically:
react-native-filament, Babylon RN, Unity/Unreal as a view, Three.js onreact-native-wgpu) — maturity, licence, whether they actually read splat formats.Part of the wayfinder map #1.
Answer
Short version: yes, a WebView-free native path exists on iOS and it is genuinely good — but only on iOS, and only if you write the React Native bridge yourself. There is no cross-platform, off-the-shelf RN gaussian-splat component in 2026. The WebView route stands for Session 1.
All dates below are as of 2026-08-11. Every version/date claim is from npm registry metadata, the GitHub API, or the project's own docs/source.
1. The "dedicated RN gaussian splat package" question — settled
react-native-gsplatis the only npm package that claims to be this. It is empty.0.0.1, published 2026-04-19, MIT, authorjoncodeofficial, no README, norepositoryfield.unpackedSize= 301 bytes, and the archive contains exactly one file,package/package.json.mainpoints atindex.js, which does not exist. No dependencies, no native code.https://registry.npmjs.org/react-native-gsplatand its dist tarball.A GitHub repository search for
react-native gaussian splatreturns no library — only one unrelated hackathon app (Simon-xP/CxC-2026, 0 stars). Conclusion: no dedicated RN splat package exists. Treat any search hit forreact-native-gsplatas a name-squat.2. Package-by-package
react-native-filament (Margelo) — healthy, but cannot render splats
1.11.0, published 2026-05-27. MIT. GitHubmargelo/react-native-filament: 1,387 stars, last push 2026-05-27, not archived, 49 open issues. Actively maintained..glbonly. Its own Getting Started guide: "Currently only loading.glbfiles is supported. You can convert any other 3D models (gltf, obj, FBX, etc) to .glb..." No splat, no point cloud.google/filamentdiscussion #8033 (opened 2024-08-06), maintainer pixelflinger answered (2024-08-11): "It can be implemented using our material system. It's not supported natively." Later comments in the same thread — the most recent dated 2025-10-29 — identify the real blocker: "Filament doesn't support compute shaders so there's no (easy) way to depth-sort the splats based on camera orientation on the GPU." Two years on, still unresolved. Open3D works around exactly this by running a separate GPU compute pipeline alongside Filament and compositing via a shared depth texture — i.e. the splat renderer is not Filament, it just sits next to it.<Camera position={...} target={...} near far />— declarative props you can drive per frame from JS, plus a worklet render callback (react-native-worklets-core). The gesture path (CameraManipulator.grabBegin/grabUpdate/grabEnd) is optional, not mandatory. Limitations stated: one camera per scene, perspective only.react-native-worklets-core— note a likely conflict with our currentreact-native-worklets@0.5.1(the Reanimated-4 package, a different library). Needs a dev build, not Expo Go.Babylon React Native — maintained, but structurally blocked
@babylonjs/react-native2.0.5, published 2026-05-19. MIT. GitHubBabylonJS/BabylonReactNative: 433 stars, last push 2026-06-02, 32 open issues. Alive.SPLATFileLoaderhandles.splat,.ply, and.spz; there is a documentedGaussianSplattingMaterial/GaussianSplattingMesh. On paper this is the most attractive option.FreeCamera/TargetCameraacceptposition+rotationQuaternionset imperatively — this part is clean.react-native-webgpu (William Candillon, Dawn-backed) — the most interesting native option, but you still write the renderer
react-native-webgpu0.8.2(2026-08-06) is the live one;react-native-wgpu0.5.17(2026-07-08) is now explicitly "Shim that re-exports react-native-webgpu under its previous package name." Both MIT.wcandillon/react-native-webgpu: 1,204 stars, created 2024-06-17, last push 2026-08-06, not archived. Very actively developed — but still pre-1.0 at 0.8.x.react-native-worklets >= 0.7.2for off-thread rendering (we have 0.5.1, would need a bump)."react-native-webgpu"inplugins), works with Continuous Native Generation, requiresnpx expo prebuild --clean→ development build, not Expo Go. Their template "works with React Three Fiber on iOS, Android, and web."three/webgpusince r171.@sparkjsdev/sparkis explicitly WebGL2 (see §4), so it does not run on this. You would port or write a WGSL splat pipeline yourself.expo-gl / expo-three — expo-gl fine, expo-three dead, and the combination can't host a splat viewer
expo-glis healthy and in our SDK:16.0.10(SDK 54 line) published 2026-01-27; SDK 57 line at57.0.2(2026-07-15). MIT. Included in Expo Go.gl instanceof WebGL2RenderingContext." So instanced arrays / float textures / integer textures are available in principle.fenceSync,clientWaitSync,waitSync,getSyncParameter,isSync,deleteSync),getBufferSubData,renderbufferStorageMultisample,getInternalformatParameter,getActiveUniformBlockParameter, and allcompressedTexImage*. Also: "Thepixelsargument oftexImage2D()must benull, anArrayBufferwith pixel data, or an object of the form{ localUri }" — noImageBitmap, no DOM sources.'worklet'added at the start." So a 1–5M-splat CPU sort would run on the RN JS thread, alongside our positioning logic and UI. That is not viable.expo-threelast published 2024-07-28 (8.0.0). Two years stale — treat as abandoned.react-native-gpu — does not exist
No such package on npm. Anything referring to it means
react-native-webgpu/react-native-wgpu. Nothing to evaluate.Unity / Unreal as a hosted view — possible, wildly disproportionate
@azesmway/react-native-unity1.1.1, published 2026-08-07, MIT — active, and currently the only maintained option. (The oldreact-native-unity-viewlast shipped1.3.3on 2019-01-07 — dead for 7½ years.)aras-p/UnityGaussianSplatting: MIT, 3,379 stars, but last push 2025-10-17 (~10 months stale) and self-described as "Toy Gaussian Splatting visualization in Unity."3. MetalSplatter and bridging a native Metal/Vulkan renderer
MetalSplatter is real, current, well-shaped, and it is the strongest native option — on iOS only. I read its source directly.
github.com/scier/MetalSplatter— "Render Gaussian Splats using Metal on Apple platforms (iOS/iPhone/iPad, macOS, and visionOS)". MIT. 708 stars, created 2023-12-31, last push 2026-05-28, repo updated 2026-08-08. Not archived. Actively maintained.Package.swiftdeclares iOS 18, macOS 15, visionOS 2, Swift 6 language mode. Note the iOS 18 floor.MetalSplatter(core renderer),SplatIO,PLYIO,SampleBoxRenderer, plus aSplatConverterCLI.SplatIO/Sources/SplatFileFormat.swift:.ply,.splat,.spz. (There is aSPZSceneReader/SPZSceneWriterand an SPZ compression dependency.)MetalSplatter/Sources/SplatSorter.swift, withonSortStart/onSortComplete(TimeInterval)/afterNextSorthooks and asortTimeout. You do not write the sort.SplatRenderer.swift:You hand it a view matrix, a projection matrix, and a target texture. There are no built-in gesture/orbit controls to fight. For "we drive the camera from a positioning system every frame," this is exactly the shape we want. It also renders into a caller-supplied
MTLTexture, so hosting it in anMTKViewinside a native RN view is straightforward.Android has no equivalent. This is the decisive asymmetry. What exists is desktop-oriented:
nvpro-samples/vk_gaussian_splatting— Apache-2.0, 481 stars, last push 2026-06-04. NVIDIA desktop Vulkan sample, not an Android library.jaesung-cs/vkgs— MIT, 159 stars, last push 2026-07-17. Vulkan viewer + Python binding; desktop.MouseChannel/MCGS— Vulkan compute-shader splat renderer claiming to run anywhere, but 37 stars, last push 2025-02-05, no licence file. Unusable.Cross-platform escape hatches, so you don't write it twice:
ArthurBrussee/brush— Rust + wgpu, Apache-2.0, 4,907 stars, last push 2026-08-09. Genuinely cross-platform and genuinely current. But it is a reconstruction app/trainer, not an embeddable render library, and embedding Rust+wgpu into an RN native module is its own multi-week project.Effort estimate for the native bridge (my estimate, not sourced — treat as an order of magnitude):
MTKViewhost, render loop,CAMetalLayerlifecyclereact-native-webgpufor both platformsVersus the WebView route: ~2–4 days to a working, pose-driven splat view on both platforms, with zero native code. That is a 5–15× difference for Session 1, and the native route buys nothing we need yet.
4. The WebView route, re-checked (so the comparison is fair)
react-native-webview— MIT, very active:latest=14.0.1(2026-06-20), with15.0.0(2026-07-05) and16.0.0(2026-07-11) on thenexttag. 347 versions. It is bundled in Expo Go, so Session 1 needs no dev build at all.@sparkjsdev/sparkis the standout viewer choice.2.1.0published 2026-05-18, MIT, peer depthree >= 0.180.0, only runtime depfflate. Its own README: "Portable: Works across almost all devices, targeting 98%+ WebGL2 support", "Renders fast even on low-powered mobile devices", "Render multiple splat objects together with correct sorting", and format support for.PLY(including compressed PLY),.SPZ,.SPLAT,.KSPLAT,.SOG. Being WebGL2 (not WebGPU) is a feature here — it works in today's webviews.playcanvas2.21.3(2026-07-29, MIT, active, has gsplat + SOG);@mkkellogg/gaussian-splats-3d0.4.7(2025-01-25 — 19 months stale);gsplat1.2.9(2025-07-12 — ~13 months stale);@zappar/three-gaussian-splat0.1.0(2023-12-19 — abandoned).threeitself is0.185.1(2026-07-01).postMessageis implemented on top ofinjectJavaScripton all platforms, and the RN-WebView community measures a round trip (web → RN → state → back) at "tens of milliseconds at minimum". But we do not need a round trip: we need a one-way, fire-and-forgetinjectJavaScriptof ~7 floats at 30–60 Hz, with the WebView's ownrequestAnimationFramedoing the interpolation. Mitigations to design in from day one: send pose at 20–30 Hz and interpolate/extrapolate in-page rather than pushing every frame; keep the payload a fixed-width numeric string, not JSON; and read the gyro/orientation directly inside the WebView via DeviceOrientation/DeviceMotion so only the low-rate position fix has to cross the bridge. This should be measured in Session 1 — it is the single number that would justify revisiting the native route.5. Expo dev client vs bare RN — summary
expo prebuild; New Arch only, RN ≥ 0.81)Nothing here forces bare React Native except Unity and (in practice) Babylon RN. A MetalSplatter bridge can absolutely live as an Expo Module in a dev client. So "we'd have to abandon Expo" is not a valid argument against the native route — the valid argument is Android, and cost.
Verdict
The WebView route stands for Session 1. Do not change the plan. Ship
react-native-webview+ a WebGL2 splat viewer in a local HTML bundle, with@sparkjsdev/spark(MIT, 2026-05-18, WebGL2, reads .ply/.spz/.splat/.ksplat/.sog, explicitly mobile-tuned) as the first choice and PlayCanvas as the fallback. It works in Expo Go today with zero native code, on both platforms, in ~2–4 days.Three reasons the native option loses right now, despite being real:
render(viewports:)API is a near-perfect fit for externally-driven camera pose — but Android has no counterpart to bridge. You would be building one platform now and a from-scratch Vulkan/GLES renderer later, or rewriting both onreact-native-webgpu.Confirmed as a later optimization, not a Session-1 change. The native path to keep warm is react-native-webgpu + a WGSL splat pipeline (one renderer, both platforms, real compute shaders, Expo config plugin, RN ≥ 0.81 ✔) — not MetalSplatter, precisely because MetalSplatter can never cover Android.
The trigger that would change this: if Session 1 measurement shows the RN→WebView pose channel cannot sustain smooth 30 Hz camera updates, or WebView splat rendering thermally throttles on target hardware within a typical walk. Measure both in Session 1.
Open / unresolved
SPLATFileLoader+GaussianSplattingMesh, but the RN binding is WebGL-only and the sort needs a Web Worker RN doesn't have. No working report found either way. A 1-day spike would close this — and if it works, it is the cheapest native option by far.injectJavaScriptat 30–60 Hz on target devices. I found only a qualitative "tens of milliseconds" figure for a full round trip; the one-way cost is unmeasured. This is the load-bearing unknown for the whole plan.react-native-worklets-core(needed by react-native-filament) conflicts with ourreact-native-worklets@0.5.1. Moot if Filament is ruled out, but it affects any future worklet-based renderer.react-native-webgpuwantsreact-native-worklets >= 0.7.2for off-thread rendering; we are on 0.5.1. Unknown whether the bump is disruptive alongside Reanimated 4.Sources: npm registry metadata for each package; GitHub API repo metadata;
scier/MetalSplatterPackage.swift,Sources/MetalSplatter/SplatRenderer.swift,Sources/SplatIO/SplatFileFormat.swift(main branch, fetched 2026-08-11);google/filamentdiscussion #8033; Babylon.js Gaussian Splatting docs,SPLATFileLoadertypedoc, and forum thread #37568; expo-gl SDK 57 docs + npm package description; react-native-filament Getting Started and camera guides; react-native-webgpu docs (installation, expo);@sparkjsdev/sparkREADME v2.1.0. No repo files were modified in producing this, per instruction.Addendum — Apple ships first-party splat rendering on iOS 27
A late-reporting sub-agent pass landed after the answer above was posted. It does not change the verdict (WebView + Spark for Session 1), but it changes the long-term native picture materially and adds sourcing the original pass lacked.
Apple now has first-party Gaussian splat rendering in RealityKit —
GaussianSplatComponentandGaussianSplatResource, availability iOS 27 / iPadOS 27 / macOS 27 / visionOS 27, all marked beta as of 2026-08-11 (WWDC26 session 279). The API takes raw buffers for position/scale/rotation/opacity/spherical-harmonics and assumes no file format, so a parser is still needed — MetalSplatter's MIT-licensedPLYIO+SplatIOtargets fill exactly that gap. So the pragmatic future iOS stack is SplatIO for parsing + RealityKit for rendering, at roughly 5–8 days for an RN wrapper versus 10–14 for the MetalSplatter/MTKViewroute.Also relevant:
KHR_gaussian_splattingglTF 2.0 extension release candidate (Feb 2026), backed by NVIDIA, Google, Adobe, Cesium. Splats are becoming a standard asset type, not a bespoke format zoo.react-native-webgpuis the corrected package name (react-native-wgpuis deprecated) — MIT, v0.8.2, 2026-08-06, Dawn-backed, iOS + Android + visionOS. Still the one write-once native path.UnityGaussianSplatting's own README says mobile support is inconsistent and iOS "do[es] not function properly", the author stopped significant development, and the INRIA training licence is non-commercial.newArchEnabled=falseis ignored.deploymentTarget(iOS 18 for MetalSplatter, iOS 27 for RealityKit).Revisit trigger for the native decision: iOS 27 going GA (~Sept 2026). At that point the iOS native path costs days rather than weeks — but Android still has nothing, so a single WebGPU renderer stays the only write-once answer.
Addendum 2 — correction: Babylon React Native IS a turnkey native splat path, cross-platform
The answer above says nothing cross-platform reads splats natively and that Babylon RN's binding is WebGL-only. That is wrong. A later pass verified the opposite in source.
splatFileLoaderregisters.splat,.ply, compressed.ply,.spz,.sog;gaussianSplattingMeshBase.pure.tsis 175KB, plus a streaming/paging stack.sortSplats()inPlugins/NativeOptimizations/Source/NativeOptimizations.cpp, added by commit "Native GS" (#1576) on 2026-01-14. The source branches onIsNativeand skips worker instantiation entirely.@babylonjs/react-native2.0.5 (2026-05-19): the package pins BabylonNative commitce2edf0dated 2026-04-14, after the Native GS commit, and all three platformCMakeLists.txtlinkNativeOptimizations.FreeCamerawithattachControlnever called, settingposition/rotationQuaternioninonBeforeRenderObservable.The real costs, which are why the WebView verdict still stands for Session 1:
react-native-webview+ Spark running in a dev client in days.requireNativeComponentwith nocodegenConfig, running through new-arch interop. Tracking issue #595 has been open since 2023-08-03, and Babylon's own Playground test app hasnewArchEnabledcommented out.DecompressionStream, so SPZ v4/NGSP is rejected by the native fallback loader and SOG crashes on Native. Use.ply/ compressed.ply/.splat..ksplatunsupported (convert with@playcanvas/splat-transform).Also corrected: Spark cannot run in RN at all —
import BundledWorker from "./worker?worker&inline"is Vite-only andspark_rs_bg.wasmis mandatory; Metro can't resolve either and Hermes has no WebAssembly. Spark's own RN request (#381, opened 2026-06-24) is unanswered. So Spark requires the WebView — it is not a candidate for a native path, which strengthens the WebView choice rather than weakening it.Attribution fix:
react-native-webgpuis wcandillon's (William Candillon, 347 commits), not Software Mansion's — SWM contributes and separately owns TypeGPU. Current 0.8.2 (2026-08-06), 28 commits in 30 days, ships its own Expo config plugin and an official template (create-expo-app -e with-webgpu). Still zero splat code — choosing it means writing the loader and GPU radix sort.Two non-existent packages to not plan around:
react-native-gpu(npm 404) andthree-gaussian-splat(npm 404).react-native-filamentis a dead end for splats — no VertexBuffer API from JS and materials must be offline-compiled.filamat.expo-threeis abandoned (last release 2024-07-28).