Forum Discussion

valtik's avatar
valtik
Start Member
25 days ago

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!

9 Replies

  • Degly's avatar
    Degly
    Start Partner

    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's avatar
      valtik
      Start Member

      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's avatar
    valtik
    Start Member

    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.

  • 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's avatar
      valtik
      Start Member

      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?

      • duncanwycliffeSN's avatar
        duncanwycliffeSN
        Meta Employee

        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. 


→ Find helpful resources to begin your development journey in Getting Started

→ Get the latest information about HorizonOS development in News & Announcements.

→ Access Start program mentor videos and share knowledge, tutorials, and videos in Community Resources.

→ Get support or provide help in Questions & Discussions.

→ Show off your work in What I’m Building to get feedback and find playtesters.

→ Looking for documentation?  Developer Docs

→ Looking for account support?  Support Center

→ Looking for the previous forum?  Forum Archive

→ Looking to join the Start program? Apply here.

 

Recent Discussions