22 April 2018

Downloading holograms from Azure and using them in your Mixed Reality application

Intro

In my previous post I explained two ways of preparing holograms and uploading them to Azure: using a whole scene, and using just a prefab. In this post I am going to explain how you can download those holograms in a running Mixed Reality or HoloLens app and an show them.

image

Setting the stage

We uploaded two things: a single prefab of an airplane, with a behavior attached, and a scene containing a prefab – a house, also with a behavior attached. The house rotates, the airplane follows quite an erratic path. To access both we created a Shared Access Signature using the Azure Storage Explorer.

In the demo code there’s a Unity project called RemoteAssets. We have used that before in earlier posts. The third scene (Assets/App/Demo3/3RemoteScenes) is the scene that actually tries to load the holograms

If you open that scene, you will see two buttons: “Load House” and “Load Plane”

image

nicked from the Mixed Reality Toolkit Examples I nicked these buttons. The left button, “Load House”, actually loads the house. It does so because it’s Interactive script’s OnDownEvent calls SceneLoader.StartLoading

image 

Loading a remote scene

This SceneLoader is not a lot of code and does a lot more than is strictly necessary:

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    public string SceneUrl;

    public GameObject Container;

    private bool _sceneIsLoaded;

    public void StartLoading()
    {
        if (!_sceneIsLoaded)
        {
            StartCoroutine(LoadScene(SceneUrl));
            _sceneIsLoaded = true;
        }
    }

    private IEnumerator LoadScene(string url)
    {
        var request = UnityWebRequest.GetAssetBundle(url, 0);
        yield return request.SendWebRequest();
        var bundle = DownloadHandlerAssetBundle.GetContent(request);
        var paths = bundle.GetAllScenePaths();
        if (paths.Length > 0)
        {
            var path = paths[0];
            yield return SceneManager.LoadSceneAsync(path, LoadSceneMode.Additive);
            var sceneHolder = GameObject.Find("SceneHolder");
            foreach (Transform child in sceneHolder.transform)
            {
                child.parent = Container.transform;
            }
            SceneManager.UnloadSceneAsync(path);
        }
    }
}

imageIt downloads the actual AssetBundle using a GetAssetBundle request, then proceeds to extract that bundle using a DownloadHandlerAssetBundle. I already wrote about the the whole rigmarole of specialized requests and accompanying handlers in an earlier post. Then it proceeds to find all scenes in the bundle, picks the first once, and loads this one additive to the current scene. If you comment out all lines after the LoadSceneAsync and run the code, you will be actually able to see what’s happening – a second scene inside the current scene is created.

If you however run the full code, the SubUrb house will appear inside the HologramCollection and no trace of the BuildScene will remain. That’s because the code tries to find a “SceneHolder” object (and you can see that’s the first object in the BuildScene), moves all children (one, the house) to the imageContainer object and once that is done, the additional scene will be unloaded again. But the hologram that we nicked from it still remains, and it even rotates. If you look very carefully in the editor if you click the button, you can actually see the scene appear, see the house being moved from it, and then disappear again.

The result: when you click “Load House” you will see the rotating house, 2 meters before you.

image

Success. Now on to the airplane.

Loading a remote prefab

This is actually less code, or at least – less code than the way I chose to handle scenes:

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class PrefabLoader : MonoBehaviour
{

    public string AssetUrl;

    public GameObject Container;

    private bool _isLoaded;

    public void StartLoading()
    {
        if (!_isLoaded)
        {
            StartCoroutine(LoadPrefab(AssetUrl));
            _isLoaded = true;
        }
    }

    private IEnumerator LoadPrefab(string url)
    {
        var request = UnityWebRequest.GetAssetBundle(url, 0);
        yield return request.SendWebRequest();
        var bundle = DownloadHandlerAssetBundle.GetContent(request);
        var asset = bundle.LoadAsset<GameObject>(bundle.GetAllAssetNames()[0]);
        Instantiate(asset, Container.transform);
    }
}

The first part is the same, then we proceed to use bundle.LoadAsset to extract the first asset by name from the bundle as a game object (there is only one, so that’s always correct for this bundle). And then we instantiate the asset – a prefab, which is a game object, into the hologram collection.

If you click the “Load Plane” button the result is not what you might expect:

image

Uhm, what? We basically did the same as before, actually less. It turns out, the only reason the house rotated fine, was because I used the Horizontal Animator from my own HoloToolkitExtensions. That script is present in both the SceneBuilder project (that I used to create the bundles uploaded to Azure) and the target app, RemoteAssets, that downloads the assets bundles and tries to use them.

But for the airplane to move around, I created a script “MoveAround” that is only present in the SceneBuilder. It does not happen often, dear reader, but I intentionally checked in code that fails, to hammer home the following very important concept:

In an Asset Bundle you can put about anything – complete scenes, prefabs, images, materials and whatnot – everything but scripts.

In order to get this to work, the script and its meta file need to be copied to the target project. Manually.

Untitled

imageIt does not matter much where in the target project it comes, Unity will pick it up, resolve the script reference, the bundle will load successfully if you press the “Load Plane” button. I tend to place it next to the place where it’s used.

And lo and behold: and airplane moving once again like a drunken magpie.

Concluding words

I have shown you various ways to upload various assets – JSON data, images, videos and finally holograms to Azure, how to download them from you app, and what limitations you will need to consider.

Important takeaways from this and previous posts:

  • Yes, I could have downloaded earlier assets using an Unity Asset Bundle as well, in stead of downloading images etc. via a direct URL. Drawback of using an Asset Bundle is you will always need Unity to build it. If you are building an app for a customer that wants to update training images or videos, it’s a big plus if you can just have them uploaded to Azure using the Storage Explorer or some custom (web) app. Whatever the customer can change or maintain themselves, the better it is.
  • You can’t download dynamic behavior, only (static) assets. The most ‘dynamics’ a downloaded asset can have is referring to a script that exists both in the building and the target app. I have seen complex frameworks that tried to achieve downloadable behavior by storing properties into the project file but that usually is a lot of work to achieve only basic functionalities (like moving some parts or changing colors and stuff) but while that may for simple applications, approaches like that are complex to maintain, a lot of work to ‘program’ into your asset, brittle and hard to transfer between project. Plus, it still needs Unity, and your customer is not going to use that.
    Rule of thumb is always: if you want to change the way things look, you can download assets dynamically. If you need new behavior, you will need to update the app.

I hope you enjoyed this brain dump. The project can (still) be found here.

18 April 2018

Preparing and uploading Holograms into Azure for use in Mixed Reality apps

Intro

In my series about remote assets, there’s one major thing left: how to use remote assets that can be used as Holograms in your Mixed Reality app. I will do this in two post:

  1. Prepare for usage and actual upload
  2. Use and download.

And this is, as you guessed, the first post.

Asset bundles

A Unity asset bundle is basically a package that can almost any piece of a Unity app. It’s usually used to create downloadable content for games, or assets that are specific for a platform or game level, so the user does not have to download everything at once. This enhances startup time, but also saves on (initial) bandwidth and local storage. Also, you can update part of the app without actually requiring the user to download a complete new app. There are some limitations, but we will get to that.

General set up

We want to create an app that downloads assets, that it does not have initially included in it. But a Unity project is what we need to actually build the asset bundles. So we need two projects: the actual app that will run, and a ‘builder’ project. That project – which I not completely correctly called “SceneBuilder: – you can find here. I will show you how to prepare an asset bundle that contains a complete scene, and one that only contains a single prefab.

The SceneBuilder contains everything a normal Unity app does: scenes, assets, scripts, the works. And something extra. But first, our runtime app is going to use the Mixed Reality Toolkit, some stuff from the Mixed Reality Toolkit Samples, LeanTween, and my own extensions. I kind of all threw it in because why not. It also contains three scenes in the root.

  • Main (which is empty and not used)
  • BuildScene (a rather darks scene which contains a house from the Low Poly Buildings Lite package, and nothing else)
  • PrefabScene (which contains what seems to be a normal Mixed Reality app, as well an airplane called “Nathalie aguilera Boing 747” (I assume “Boing” should be “Boeing”, and the model is actually a Boeing 737 but whatever – it’s a nice low poly model)

imageimage

The most important thing is the AssetBundle Browser. This is a piece of code provided by Unity to make building asset bundles a lot easier. You can find the AssetBundle Browser in folder “UnityEngine.AssetBundles” under “Assets. You can download the latest version from Unity’s Github repo. It comes with a whole load, but basically you only need everything that’s under UnityEngine.AssetBundles/Editor/AssetBundleBrowser. This you plonk in your Scen eBuilder project’s Asset folder.

imageThe BuildScene

If you open the BuildScene first, you will see there’s actually very little in it. An empty ‘SceneHolder’ object, and a “Suburb House 1” prefab in it. The lack of lighting also makes the scene rather dark. This has a reason: we are going to add this scene to another scene later, and we don’t want all kinds of duplicate stuff like lighting, Mixed Reality toolkit managers, etc. – coming into our existing app. So almost everything is deleted. But to that Prefab, one thing is added: the Horizontal Animator behavior (that debuted in this article). Timagehis is a very simple behavior that will make the house spin round every five seconds. Nothing special – this is to prove a point later.

If you play the scene, you will actually see the house spinning in the scene. The Game window will be black, only saying “Display 1 No cameras rendering”, which is correct, as I deleted everything but the asset we are going to build and upload, so there’s even no camera to show anything.

Build the buildscene asset bundle

First order of business – build the UWP app, as if you are going to create an actual Mixed Reality app for HoloLens and/or immersive head set out of the SceneBuilder. For if you don’t, the AssetBundle browser will kick off that process, and if there’s a syntax error or whatever – it’s modal build window will get stuck, blocking Unity – and the only way out is killing it in the Task manager.

Click Window/AssetBundle Browser and you will see this:

image

I already pre-defined some stuff for you. What I basically did was, in an emtpy AssetBundle browser, drag the BuildScene into the empty screen:

Untitled

and the AssetBundle Browser then adds everything it needs automatically. Click the tab “Build” and make sure the settings are the same as what you see here. Pay particular attention to the build target. That should be WSAPlayer, because that’s the target we use in the app that will download the assets.

Untitled

Hit the ugly wide “Build” button and wait till the build completes. If all works out, you will see four files in SceneBuilder\AssetBundles\WSAPlayer:

  • buildscene
  • buildscene.manifest
  • WSAPlayer
  • WSAPlayer.manifest

Only those where there all along, as I checked the folder with these files in GitHub. But go ahead, delete the files manually, you will see they are re-created. You will see they are all only 1 KB, except for buildscene – that’s 59 KB.

The PrefabScene

This looks more like a normal scene: it has everything in it you would expect in , and the airplane.

image

imageIf you hit the play button in this scene, the airplane will move around in a way that makes you happy you are not aboard it, due to a behaviour called “Move Around”. This script sits in “Assets/App/Scripts” and is nothing more than an start method that creates a group of point relative to the start position and moves it around using LeanTween

void Start()
{
    var points = new List<Vector3>();
    points.Add(gameObject.transform.position);
    points.Add(gameObject.transform.position);
    points.Add(gameObject.transform.position + new Vector3(0,0,1));
    points.Add(gameObject.transform.position + new Vector3(1,0,1));
    points.Add(gameObject.transform.position + new Vector3(-1,0,1));
    points.Add(gameObject.transform.position + new Vector3(-1,1,1));
    points.Add(gameObject.transform.position);
    points.Add(gameObject.transform.position);

    LeanTween.moveSpline(gameObject, points.ToArray(), 3).setLoopClamp();
}

But this script will later cause us some trouble as we will see in the next post (also to prove a point). I created a prefab of this airplane with it’s attached behaviour in Assets/App/Prefabs. Open the AssetBundle Brower again, drag this prefab onto the left pane of the dialog like this:

Untitled

UntitledOnly this creates an impossible long name, so simply right-click on the name and rename it to “airplane”. Select the “Build” tab again, hit the wide Build button again, wait a while.

If everything went according to plan, SceneBuilder\AssetBundles\WSAPlayer should now contain two extra files:

  • airplane
  • airplane.manfest

The manifest file is once again 1 KB, airplane itself should be 87 KB

Uploading to Azure

This is the easiest part. Use the Azure Storage Explorer:

Untitled

Simply drag your the airplaine and buildscene (you don’t need anything else) into a blob container of any old storage account. The right-click on a file, select “Get Shared Access Signature” and click “Create”, but not before paying close attention to the date/time range, particularly the start date and time

image

The Azure Storage explorer tends to generate a start time some time (1.5 hours or so) in the future, and if you use the resulting url right away, it won’t work. Believe me – been there, done that, and I am glad no-one can could hear me ;). So dial back the start time to the present or a little bit in the past, then create the url, and save it. Do this for both the airplane and the buildscene file. We will need them in the next post to actually download and use them.

Conclusion

We have built two assets and uploaded them to Azure without writing any code – only a little code to make the airplane move, but that was not part of the build/upload procedure. Of course, sou can write the code to build asset bundles yourself, but I am lazy smart and tend to use what I can steal borrow from the internet. This was almost all point-and-click, next time we will see more code. For the impatient, the whole finished project can be found here.

04 April 2018

For crying out loud, compile for native ARM!

CHPE works its magic…

Let me get this straight first: Microsoft did and amazing job with CHPE and x86 emulation, that allows x86 to run without any conversion on the new Windows 10 on ARM PCs. Considering what happens under the hood, the performance and reliability x86 apps get is nothing short of stunning. And you still get the amazing battery life we are used to from ARM – but now on full fledged PCs.

Yet, there are still some things to consider. If you are an UWP developer, you by now probably have stopped providing ARM packages for your UWP apps. After all, Windows 10 Mobile is, alas, fading away, and these ARM PCs run x86 apps, so why bother?

… but magic comes at a price

Well this is why. UWP can compile to native ARM code, and now we have these ARM based PCs. Native ARM code can run on the Windows 10 on ARM PCs without using CHPE. Although CHPE is awesome, it still comes at a price – it uses CPU cycles to convert x86 instructions to ARM instructions and then executes those. Skip one step, you gain performance. And depending on what you do, you can gain a lot of performance.

To show you I am not talking nonsense, I actually compiled my HoloLens/Mixed Reality app “Walk the World” not only for x86 (which I need to do for HoloLens anyway) but also for native ARM. I made two videos, of the app running as x86 UWP, and native ARM UWP. Since I don’t use a head set in this demo, I created a special Unity behaviour to control the viewpoint using an Xbox One Controller. I keep the actual PC out of the videos again, but you can clearly see the Continuum dock I wrote about before – I connected the Dell monitor using a DisplayPort, the Xbox One controller using USB, and a USB-to-ethernet dongle to rule out any variations in Wi-Fi signal.

First, watch the x86 version

Then, the ARM version.

You can clearly see: although the x86 version works really well, the native ARM version starts faster, and also downloads the map faster – considerably so. Still, CHPE amazed me again by the fact that the graphics performance was nearly identical – once the map was loaded, panning over it happened at nearly identical speeds. But apparently startup and network code take more time. So there’s your win!

Note: any flickering or artifact you see are the results of the external camera I used, to prevent any suggestion of this being faked. I also did not want to use a screen capture program as this might interfere with the app’s performance.

Message clear? CHPE is to be used for either x86 apps from the Store that are converted using the Desktop Bridge, or none-Store apps that are downloaded ‘elsewhere’ – think of Chrome, Notepad++ – anything that has not been converted yet to UWP or processed via the Desktop Bridge.

One extra checkbox to rule them all

I did not have to change anything to my code just to add the native ARM version. Basically all I had to do was tick a check box:

image

…and the store will do the rest, selecting the optimal package for you user. That one little checkbox gives your app a significant performance boost on these Windows 10 on ARM PCs.

Now one might wonder – why on Earth would you convert an app that started on HoloLens to an UWP desktop app to run on an Windows 10 on ARM PC and how you make it work? Well, stay tuned.

And incidentally, this was the 300th post on this blog since I started in October 2007. And rest assured – I am far from done ;)

02 April 2018

How to detect your Windows Mixed Reality app is actually running on a head set

Very short and small tip – Windows Mixed Reality can run on a HoloLens, on an immersive headset – but although the Store warns about necessary hardware to run the app, there’s basically nothing stopping users from installing the app on a regular Windows PC. We can discuss whether or not that is a smart thing to do, but I approach it from the other side – if a users runs my app on a device that has no immersive capabilities, I can either quit the app, or offer her or him some basic functionality, show menus that can be easily controlled by a mouse click, whatever.

It is actually hilariously simple to detect that: just check

UnityEngine.XR.XRDevice.isPresent

Which returns true if your app is running on either an immersive headset or a HoloLens, and false if the user has started it on some other Windows device.

Thanks to Peter Nolen for the tip!