Skip to main content
valtik
Participant
December 23, 2025
Question

Issues with Gaussian Splatting integration in Meta Spatial SDK (v0.9.2) – Standalone Quest 3

Hi everyone,

I am an architect and urban planner, developing VR/MR projects in my spare time. I’m currently working on a native Quest 3 application to view Gaussian Splats for highly detailed virtual project tours.

My Goal:

Render native Gaussian Splatting (3DGS) on Quest 3 (Standalone).

Synchronize the splat position with the real world for a seamless transition from Mixed Reality (MR) to Virtual Reality (VR) with a skybox.

Targeting a minimum of 150k splats with stable performance.

Previous Attempts: I initially used Unity with the Aras/Ninja implementations. While it works perfectly via Oculus Link (PCVR), it’s not viable for my needs because:

PC Builds don't support the specific MR features I need for this project.

The Android build (standalone) performance is extremely poor, even with optimized settings and a small 50k splat PLY file (unusable frame rates).

Current Issue with Meta Spatial SDK: I’ve switched to the Meta Spatial SDK (Packages v0.9.2) to leverage the new native splat support mentioned here: Spatial SDK Splats Documentation.

Despite following the documentation step-by-step:

The splats do not appear in the scene.

I am getting several reference errors (MissingReference/NullReference) that I can't seem to resolve.

My Question: Has anyone successfully created a native Quest 3 APK using the Spatial SDK to run Gaussian Splats (~150k splats)? If so, could you share the correct workflow or point out common pitfalls with this specific SDK version?

Any help or documentation beyond the official guide would be greatly appreciated!

13 replies

Degly
Inspiring
December 27, 2025

Have you tested other Gaussian Splatting implementations? There's a few open source projects on Github that have been trying to achieve a similar result

valtik
valtikAuthor
Participant
January 2, 2026

Thank's for the reply. Yes, as mentioned, I tried Aras's [plugin] and the Ninja implementation on Unity, but it lags too much. SuperSplat works in native VR, but ine HTPM, and I want a native solution via a custom app. That is why I want to make it work with the Meta Spatial SDK or Unity.

valtik
valtikAuthor
Participant
January 3, 2026

I'm sharing the specific code from my ImmersiveActivity.kt for a more detailed analysis. Here is the relevant code structure:

package com.example.apptest

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.webkit.WebView
import android.widget.TextView
import androidx.compose.ui.platform.ComposeView
import androidx.core.net.toUri
import com.meta.spatial.castinputforward.CastInputForwardFeature
import com.meta.spatial.compose.ComposeFeature
import com.meta.spatial.compose.ComposeViewPanelRegistration
import com.meta.spatial.core.BuildConfig
import com.meta.spatial.core.Entity
import com.meta.spatial.core.Pose
import com.meta.spatial.core.SpatialFeature
import com.meta.spatial.core.SpatialSDKExperimentalAPI
import com.meta.spatial.core.Vector3
import com.meta.spatial.datamodelinspector.DataModelInspectorFeature
import com.meta.spatial.debugtools.HotReloadFeature
import com.meta.spatial.isdk.IsdkFeature
import com.meta.spatial.okhttp3.OkHttpAssetFetcher
import com.meta.spatial.ovrmetrics.OVRMetricsDataModel
import com.meta.spatial.ovrmetrics.OVRMetricsFeature
import com.meta.spatial.runtime.NetworkedAssetLoader
import com.meta.spatial.runtime.SceneMaterial
import com.meta.spatial.toolkit.AppSystemActivity
import com.meta.spatial.toolkit.DpPerMeterDisplayOptions
import com.meta.spatial.toolkit.LayoutXMLPanelRegistration
import com.meta.spatial.toolkit.Material
import com.meta.spatial.toolkit.Mesh
import com.meta.spatial.toolkit.MeshCollision
import com.meta.spatial.toolkit.PanelRegistration
import com.meta.spatial.toolkit.PanelStyleOptions
import com.meta.spatial.toolkit.QuadShapeOptions
import com.meta.spatial.toolkit.Transform
import com.meta.spatial.toolkit.UIPanelSettings
import com.meta.spatial.vr.VRFeature
import java.io.File
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import com.meta.spatial.splat.Splat
import com.meta.spatial.splat.SplatFeature
import android.net.Uri
import com.meta.spatial.splat.SpatialSDKExperimentalSplatAPI


class ImmersiveActivity : AppSystemActivity() {
  private val activityScope = CoroutineScope(Dispatchers.Main)

  lateinit var textView: TextView
  lateinit var webView: WebView

  @OptIn(SpatialSDKExperimentalSplatAPI::class)
  override fun registerFeatures(): List<SpatialFeature> {
    val features =
        mutableListOf<SpatialFeature>(
            VRFeature(this),
            SplatFeature(),
            ComposeFeature(),
            IsdkFeature(this, spatial, systemManager),
        )
    if (BuildConfig.DEBUG) {
      features.add(CastInputForwardFeature(this))
      features.add(HotReloadFeature(this))
      features.add(OVRMetricsFeature(this, OVRMetricsDataModel() { numberOfMeshes() }))
      features.add(DataModelInspectorFeature(spatial, this.componentManager))
    }
    return features
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    NetworkedAssetLoader.init(
        File(applicationContext.getCacheDir().canonicalPath),
        OkHttpAssetFetcher(),
    )

    loadGLXF()
  }

  override fun onSceneReady() {
    super.onSceneReady()

    scene.setLightingEnvironment(
        ambientColor = Vector3(0f),
        sunColor = Vector3(7.0f, 7.0f, 7.0f),
        sunDirection = -Vector3(1.0f, 3.0f, -2.0f),
        environmentIntensity = 0.3f,
    )
    scene.updateIBLEnvironment("environment.env")

    scene.setViewOrigin(0.0f, 0.0f, 2.0f, 180.0f)

    // Creating Skybox
    Entity.create(
        listOf(
            Mesh("mesh://skybox".toUri(), hittable = MeshCollision.NoCollision),
            Material().apply {
              baseTextureAndroidResourceId = R.drawable.skydome
              unlit = true // Prevent scene lighting from affecting the skybox
            },
            Transform(Pose(Vector3(x = 0f, y = 0f, z = 0f))),
        )
    )
      Entity.create(
          listOf(
              // Assurez-vous qu'un fichier comme "room.splat" existe bien dans app/src/main/assets/
              Splat("apk:///assets/room.splat".toUri()),

              // Positionne et met à l'échelle le splat dans la scène
              Transform(
                  Pose(
                      position = Vector3(x = 0f, y = 1.5f, z = -2f), // Position: 1.5m de haut, 2m devant la caméra
                      scale = Vector3(x = 1f, y = 1f, z = 1f)
                  )
              )
          )
      )
  }


  fun playVideo(webviewURI: String) {
    textView.visibility = View.GONE
    webView.visibility = View.VISIBLE
    val additionalHttpHeaders = mapOf("Referer" to "https://${packageName}")
    webView.loadUrl(webviewURI, additionalHttpHeaders)
  }

  @OptIn(SpatialSDKExperimentalAPI::class)
  override fun registerPanels(): List<PanelRegistration> {
    return listOf(
        // Registering light-weight Views panel
        LayoutXMLPanelRegistration(
            R.id.ui_example,
            layoutIdCreator = { _ -> R.layout.ui_example },
            settingsCreator = { _ -> UIPanelSettings() },
            panelSetupWithRootView = { rootView, _, _ ->
              webView =
                  rootView.findViewById<WebView>(R.id.web_view) ?: return@LayoutXMLPanelRegistration
              textView =
                  rootView.findViewById<TextView>(R.id.text_view)
                      ?: return@LayoutXMLPanelRegistration
              val webSettings = webView.settings
              @SuppressLint("SetJavaScriptEnabled")
              webSettings.javaScriptEnabled = true
              webSettings.mediaPlaybackRequiresUserGesture = false
            },
        ),
        // Registering a Compose panel
        ComposeViewPanelRegistration(
            R.id.options_panel,
            composeViewCreator = { _, context ->
              ComposeView(context).apply { setContent { OptionsPanel(::playVideo) } }
            },
            settingsCreator = {
              UIPanelSettings(
                  shape =
                      QuadShapeOptions(width = OPTIONS_PANEL_WIDTH, height = OPTIONS_PANEL_HEIGHT),
                  style = PanelStyleOptions(themeResourceId = R.style.PanelAppThemeTransparent),
                  display = DpPerMeterDisplayOptions(),
              )
            },
        ),
    )
  }

  override fun onSpatialShutdown() {
    super.onSpatialShutdown()
  }

  private fun loadGLXF(): Job {
    return activityScope.launch {
      glXFManager.inflateGLXF(
          "apk:///scenes/Composition.glxf".toUri(),
          keyName = "example_key_name",
          onLoaded = { glxfInfo ->
            // get the environment mesh and set it to use an unlit shader.
            val environmentEntity: Entity = glxfInfo.getNodeByName("Environment").entity
            val environmentMesh = environmentEntity.getComponent<Mesh>()
            environmentMesh.defaultShaderOverride = SceneMaterial.UNLIT_SHADER
            environmentEntity.setComponent(environmentMesh)
          },
      )
    }
  }
}

Here the red problems :
No value passed for parameter 'context'.
No value passed for parameter 'systemManager'.
No parameter with name 'position' found.
No parameter with name 'scale' found.

Thank you for your guidance.

Meta Employee
January 7, 2026

You'll need to pass the appropriate values to the SplatFeature constructor: 

SplatFeature(this.spatialContext, systemManager)

This code snippet from the Splat Sample code should show your how to create your entity and attach the Splat component: 

https://github.com/meta-quest/Meta-Spatial-SDK-Samples/blob/1cf1d7850a7b51513c683c539f4a2dc182a7f567/SplatSample/app/src/main/java/com/meta/spatial/samples/splatsample/SplatSampleActivity.kt#L170


Do note that the only supported splat file types are ".spz" and ".ply".  




 

Meta Employee
January 6, 2026

Hi valtik! 

Could you share any more detail on the reference errors or the code snippets you are using to add the splats to the scene? 

You can also take a look at the Splat Sample for an example of how to use it: https://github.com/meta-quest/Meta-Spatial-SDK-Samples/tree/main/SplatSample


valtik
valtikAuthor
Participant
January 8, 2026

Thank you so much! Your link to Splat Sample was a lifesaver! I was able to check out the two demo environments, and they work perfectly.

I’m now trying to add my own .spz file. I managed to get it in once, but it was positioned incorrectly, so I changed my approach from android studio to view it in the spatial editor to adjust its scale and position. I added the 'splat' component and entered my filename 'chambre130ksplat.spz' in the 'path' field, but nothing is appearing. Any idea why?

Meta Employee
January 8, 2026

Glad that helped! 

Could you share the code snippet you are using to load your splat?

Some common pitfalls could be not setting it's position to the origin (0,0,0), setting the scale to something other than (1,1,1) or not including the appropriate prefix to the file path. The comments in the SplatSample provide some examples.

Just to rule out any issues with the splat, I like to verify it in another viewer. This one has been helpful: https://gsplat.org/ and you can even share a link to view your splat with others. 


valtik
valtikAuthor
Participant
January 25, 2026

Hi everyone,

I've been working on improving the rendering quality of the Gaussian Splatting sample in the Meta Spatial SDK, and I’m looking for ways to bridge the gap between the current sample fidelity and the results seen in Meta Horizon Hyperscape.

I have a few technical questions regarding the roadmap and current capabilities:

1. Cloud Rendering for Custom Apps: Hyperscape’s quality is impressive, likely due to cloud-based processing or streaming. Is there a plan to make a similar cloud-rendering backend available for developers using the Spatial SDK for their own standalone Android apps?

2. Spherical Harmonics (SH) Control: Currently, it appears that SH is locked at Degree 0 in the SDK, resulting in a "flat" look without the view-dependent reflections seen on PC viewers. Is there a way to unlock or adjust the SH Degree (e.g., to Degree 1 or 2) to better suit specific high-fidelity needs?

3. Splat Stretching & Scaling: I’ve noticed that splats often appear more "stretched" and blurry in the headset compared to the exact same .spz file viewed on a PC. This suggests a difference in how the rasterizer handles splat scaling or alpha blending on the mobile chipset.

Could we get access to more granular quality settings (such as Splat Scale factor, SH Degree, or Sorting depth) to find the "sweet spot" for specific datasets?

Are these features currently on the roadmap for a future update of the Meta Spatial SDK?Thanks for your help!

New Participant
February 16, 2026

I am running into the exact same usage case, and I was using Aras' implementation until I found out its performance on VR.

How is your experience with the Spatial SDK's imported Gaussians so far? Are they visually compelling? I think Meta did not open-source their implementation, so I cannot tell what optimizations and new techniques they use, which is a pity since I want to reproduce the SOTA work based on Aras, but Meta seems to already done that.

Participant
April 18, 2026

Based on Meta's Hyperscape Capture app, assuming they use the same SDK, I'd say pretty compelling