cancel
Showing results for 
Search instead for 
Did you mean: 

How to handle scene loading in Unity like when using SteamVR (compositor)

jashan
Protege
I'm currently evaluating how feasible adding a native Oculus SDK version of Holodance is and the greatest challenge I see so far is porting our approach to loading scenes over.

The way we currently do this is based on Valve's SteamVR_LoadLevel example. In a nutshell: Set the skybox for the compositor, set a load texture in front of the player in the compositor, fade the game to the compositor, once that is done, stop rendering to VR (because the player now only sees what's in the compositor), start loading the scene and wait until it's done, do other things that could cause hiccups like prewarming shaders and a garbage collection round, start rendering to VR, fade back in. Play.

In addition to what the standard SteamVR_LoadLevel does, we render a preview of the level that is being loaded, with some text and a progress bar using Unity UI, into a RenderTexture that we also show and update on changes in the compositor. Also, to make things a little more fancy, we have several textures on quads to have our logo in 3D on several planes. Theoretically we could even animate those planes but Unity does have some hiccups while loading levels (even when using async and additive), so we don't do this.

Reviewing the API provided by Oculus Utilities for Unity 5.x, it seems that one class we could use is OVROverlay. Putting the texture to the right position is simply by using the Transform location and mesh primitive, right?

Then, there's 
OVRScreenFade, which lets us fade ... but only to a color.

So ... it seems there are two things missing: How can we fade full to the compositor and stop rendering to VR while we are doing things what would cause framedrops?

When using SteamVR / OpenVR, this is done by calling 
CVRCompositor.SuspendRendering(true).
18 REPLIES 18

neelb-oculus
Explorer
Hey @jashan,
Good question. When you're trying to perform any type of fade, SetColorScaleAndOffset() is the way to go. It's a simple formula, where in the last step of the compositor, the color value is multiplied by colorScale and added to colorOffset, and these values can be changed per-frame.
Currently, we've exposed 2 mechanisms for color scale and offset. One which sets a given color scale and offset for all layers, and one which sets these just for the eyeFov layer. The API was designed like that mainly because colorScale is often a global property that developers want to apply to all layers, and an obvious use case is a fade-to-black, for which the colorScale starts at (1, 1, 1, 1) and ends up at (0, 0, 0, 0). Sometimes for splash screens, you might also only want to change values for the eyeFov layer.
As regards per-layer color scale and offset, we are adding it very soon, hopefully in our next release. Right now you can only apply global or only eyeFov color scale as outlined above, but soon, you'll be able to set it per-layer, so for your case, if you have the cubemap that you want to fade in/out, and affect nothing else in the scene, you'll be able to do that soon.

jashan
Protege
Hi @theevader - sounds great, thank you for elaborating on this!

So just to be sure: Currently, when I pass "false" for applyToAllLayers, this would only fade eyeFov, i.e. what the engine renders (Unity). When I pass "true", it should have an effect on all layers and eyeFov, so the compositor-Cubemap (i.e. Cubemap from OVROverlay) not being effected would be considered a bug, right?

But either way, in one of the next releases, I could pass a specific layer and with that, I could also pass the Cubemap layer, correct?

neelb-oculus
Explorer
@jashan Exactly right. If you pass true for applyToAllLayers, then everything in your scene will fade, because your scene consists of the eyeFov + any overlays/underlays that are specified by OVROverlay. So your Cubemap OVROverlay should be fading in and out when true is passed in. In one of the next 2 releases, you'll be able to "override" colorScale and colorOffset on a per-layer basis, whereby by default, there is no override, and if you choose to override, you can set the per-layer values.

jashan
Protege
@theevader Do you have any idea why the Cubemap might not fade? Everything else does, just not the Cubemap.

jashan
Protege
... also, does anyone know why sometimes, the compositor does no head-tracking (i.e. all layers from the compositor "move with the head", or stuck in front of your face)? So far, this seems very random to me: Worked in the editor, made a build, build had the compositor stuck. Did another build (but also did change some things in-between), still worked fine in the Unity editor, and now also worked in the build. Now I have a build where again, it all worked fine in the editor, but the headset renders from origin. This only occurs in the compositor, otherwise, the game renders just fine.

jashan
Protege

jashan said:

... also, does anyone know why sometimes, the compositor does no head-tracking (i.e. all layers from the compositor "move with the head", or stuck in front of your face)? 


Apparently, no one knew. I don't know for sure - but here's what I found out so far. This issue put me near giving up, so I'm leaving this here in case someone else runs into the same issues; this might save you some headaches (and Oculus engineers, please at least read the last paragraph):

It had something to do with the scene: I could grab the file "level0" from one build where it worked correctly, and use that to overwrite that same file in a build where it didn't work, and that way I could "fix" such a broken build (and also, break a build where it worked by doing it in the opposite direction). Unfortunately, I'm not aware of a decent way to look for differences in two such files (they were also the exact same file size). Mind you: Those are not the scene assets in the Unity projects, but the binary levelN files in the build.

I did have sessions where I could reproduce it in the Unity editor for a while, but even when Unity said the current scene had no changes compared to what's stored on the disk, making a copy of that scene, and switching over to that copy would make it work in the editor again. Then, switching back to the scene where it didn't work just a minute ago would keep it working. It was not easy to get the Unity editor into the state of this not working, and I don't know for sure what exactly caused it - all I know is that it usually happened after doing a build that produced a faulty build.

In one of the sessions where it didn't work in the editor, I spent quite some time trying to debug OVROverlay to figure out what might be causing this. The most likely candidate obviously was the headLocked flag, which gets set when the OVROverlay is attached to a descendant of the main camera. I could not validate this to be the cause of the issue - headLocked was never set to true in any of my test-sessions but they did show the faulty behavior. One possible explanation could be that this somehow got cached in the runtime handling the compositor from a previous session, but that's just random guessing.

Still, not knowing of a better way to solve this, I removed this whole headLocked stuff (no responsible VR developer should use headlocked stuff, anyways, so it's not like I lost anything of value). Also, as I saw that OVROverlay uses Camera.main, and you probably use that more often, I made sure to only have the VR camera tagged with MainCamera.

From the looks of it, this did fix it; potentially due to Camera.main behaving more as you expect it in some other places of your integration. The thing is: Camera.main is not a reliable way to check for VR cameras. If you check for Camera-components where Camera.stereoTargetEye is not StereoTargetEyeMask.None, you actually reliably do what you probably thought you were doing by using Camera.main.

Anonymous
Not applicable

@jashan did you find a good way to fade in/out the OVROverlay? Are you using OVRManager.SetColorScaleAndOffset to fade in/out from black, apart from using OVROVerlay cubemap to display something around the user?

Hello JianZ,

 

Thank you for your solution.

I have used the OVROVerlaySample and I have replaced SimulateLevelLoad(); with scene loading, but the loading message is not displayed and the scene is frozen until the next scene is loaded.

 

here is the code:

        IEnumerator WaitforOVROverlay()
        {
            Transform camTransform = mainCamera.transform;
            Transform uiTextOverlayTrasnform = loadingTextQuadOverlay.transform;
            Vector3 newPos = camTransform.position + camTransform.forward * distanceFromCamToLoadText;
            newPos.y = camTransform.position.y;
            uiTextOverlayTrasnform.position = newPos;
            cubemapOverlay.enabled = true;
            loadingTextQuadOverlay.enabled = true;
            noneRadioButton.isOn = true;
            yield return new WaitForSeconds(0.1f);
            //Here is my scene loading
            SceneManagerWithParameters.Load(sceneMaison, "", "");
            cubemapOverlay.enabled = false;
            loadingTextQuadOverlay.enabled = false;
            yield return null;
        }

Is there something wrong ?

I got it working using Async Loading for the scene:

void TriggerLoad()        {
     StartCoroutine(WaitforOVROverlay());
     StartCoroutine(LoadYourAsyncScene());
}

 

        IEnumerator WaitforOVROverlay2()
        {
            Transform camTransform = mainCamera.transform;
            Transform uiTextOverlayTrasnform = loadingTextQuadOverlay.transform;
            Vector3 newPos = camTransform.position + camTransform.forward * distanceFromCamToLoadText;
            newPos.y = camTransform.position.y;
            uiTextOverlayTrasnform.position = newPos;
            cubemapOverlay.enabled = true;
            loadingTextQuadOverlay.enabled = true;
            noneRadioButton.isOn = true;            
            yield return new WaitForSeconds(1.0f);
            yield return null;
        }
        IEnumerator LoadYourAsyncScene()
        {
            // The Application loads the Scene in the background as the current Scene runs.
            // This is particularly good for creating loading screens.
            // You could also load the Scene by using sceneBuildIndex. In this case Scene2 has
            // a sceneBuildIndex of 1 as shown in Build Settings.
            AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(myScene);
            // Wait until the asynchronous scene fully loads
            while (!asyncLoad.isDone)
            {
                yield return null;
            }
        }