Loading 3D Models from the Web at Runtime in Unity

by SlideFactory

“Learn how loading 3D models (GLTF and GLB) from a web server works and then load them into Unity at runtime with local file caching.”

Overview

In this blog post, we’re going to learn about loading 3D models from a web server into Unity and then displaying them at run time (while the app is running). Previously, we learned how to use UnityWebRequest to download various file types in Part I. A lot of the same concepts apply here, but we’ll be focusing specifically on downloading GLTF and GLB files using the DownloadHandlerFile helper class from Unity.

GLTF and GLB model files are an open source file type that use JSON to enable users to share and download 3D models over the web with ease. They are becoming more popular as data from the web is integrated into apps more frequently. It’s going to be good to be familiar with these file types, so let’s get started!

If you are coming from the video you can find the scripts inside the scripts section below.

Requirements for Loading 3D Models

In a fresh Unity project, we’ll need a couple dependencies.

  • Json.NET – Required to read our GLTF/GLB files.
  • GLTFUtility – Required to load the files at runtime.

Installing Json.Net

The easiest way to install Json.Net is by opening up the asset store and searching for “JSON .NET For Unity.” Once you find it, download it and import it into your project.

Installing GLTFUtility

There are a couple of ways to install the package documented on Github. However, to get the latest and greatest, we recommend downloading the zip file of master. Then, extracting it and moving the GLTFUtility-master folder into your Unity project’s Assets folder.

Unity Scene Setup

Once we have our dependencies installed, let’s quickly set up our scene with the basics we’ll need to get started. Click Create -> UI -> Button. In the Button’s Inspector click the Anchor Presets box, hold shift + alt and click the top left. This will position the button at the top left of the canvas. Add an empty GameObject and call it ModelLoader, then create the ModelLoader.cs script and add it to the new GameObject. Click on Main Camera and set it’s Z to -5. That’s it for now.

Downloading Models from the Web

With our UI setup, let’s focus on downloading our model file from the web into Unity at runtime. If you followed along with Part I, using the UnityWebRequest class should be pretty familiar. Let’s open up the ModelLoader.cs script and add the following:

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

public class ModelLoader : MonoBehaviour
{
    string filePath;

    private void Start()
    {
        // An example file path we'll use later
        filePath = $"{Application.persistentDataPath}/Files/test.gltf";
    }
    public void DownloadFile(string url)
    {
        StartCoroutine(GetFileRequest(url, (UnityWebRequest req) =>
        {
            if (req.isNetworkError || req.isHttpError)
            {
                // Log any errors that may happen
                Debug.Log($"{req.error} : {req.downloadHandler.text}");
            } else
            {
                // Success!
                Debug.Log(req.downloadHandler.text);
            }
        }));
    }

    IEnumerator GetFileRequest(string url, Action<UnityWebRequest> callback)
    {
        using(UnityWebRequest req = UnityWebRequest.Get(url))
        {
            yield return req.SendWebRequest();
            callback(req);
        }
    }
}

All we are doing here is sending a GET request to a web server and logging the returned file. Let’s test it out. Go to your scene and click on your Button. Then add a click listener and drag in the ModelLoader GameObject. After that, select the DownloadFile method and add a GLTF or GLB file URL on the click event. We are using https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/BoxVertexColors/glTF-Embedded/BoxVertexColors.gltf to test. Hit Play and click on your button. You should see the log match the file contents from the URL.

Loading 3D Models at Runtime

Now that we have our files downloading, let’s work on loading the 3D Models into Unity at runtime. First, we need to save our downloaded data into a local file. Second, we can load that file with GLTFUtility. Luckily, UnityWebRequest has the DownloadHandlerFile helper class that automatically downloads the data into a file. In GetFileRequest, right above where we

yield return req.SendWebRequest();

add the following:

req.downloadHandler = new DownloadHandlerFile(filePath);

After that, we need to remove our Debug.Log after our “Success!” comment and load our 3D Model.

// ...
using Siccity.GLTFUtility;

public class ModelLoader : MonoBehaviour
{
    GameObject wrapper;
    string filePath;

    private void Start()
    {
        filePath = $"{Application.persistentDataPath}/Files/test.gltf";
        wrapper = new GameObject
        {
            name = "Model"
        };
    }

    public void DownloadFile(string url)
    {
        StartCoroutine(GetFileRequest(url, (UnityWebRequest req) =>
        {
            if (req.isNetworkError || req.isHttpError)
            {
                // Log any errors that may happen
                Debug.Log($"{req.error} : {req.downloadHandler.text}");
            } else
            {
                // Save the model into our wrapper
                ResetWrapper();
                GameObject model = Importer.LoadFromFile(filePath);
                model.transform.SetParent(wrapper.transform);
            }
        }));
    }
    
    // ...

    void ResetWrapper()
    {
        if (wrapper != null)
        {
            foreach(Transform trans in wrapper.transform)
            {
                Destroy(trans.gameObject);
            }
        }
    }
}

After making the script updates, go back to your Unity scene and hit Play. When you click your button, the model at the URL you selected should load into the scene.

Loading 3D Models from Cache

We have our 3D models loading, but we can make our loading even faster for larger models by first checking if we already have the file saved locally before downloading it.

// ...
using System.IO    
// ...
    
    public void DownloadFile(string url)
    {
        if (File.Exists(filePath))
        {
            Debug.Log("Found file locally, loading...");
            LoadModel();
            return;
        }

        StartCoroutine(GetFileRequest(url, (UnityWebRequest req) =>
        {
            if (req.isNetworkError || req.isHttpError)
            {
                // Log any errors that may happen
                Debug.Log($"{req.error} : {req.downloadHandler.text}");
            } else
            {
                // Save the model into our wrapper
                LoadModel();
            }
        }));
    }

    void LoadModel()
    {
        ResetWrapper();
        GameObject model = Importer.LoadFromFile(filePath);
        model.transform.SetParent(wrapper.transform);
    }
    // ...

One thing to note, because we set our filePath to always be the same place different files won’t benefit from caching. To fix that, we’d need to save the downloaded models into different files. The completed ModelLoader.cs script below includes a basic version of this. It extracts and uses the filename from the URL.

Conclusion

There you have it, loading 3D Models into Unity at runtime can be a very powerful feature. This allows you the flexibility of adding additional content to your game without requiring a new build. Alternatively, you can let your users choose their own 3D models to load into your apps. GTLFUtility has additional features, such as loading animations from 3D models, so be sure to check it out!

Scripts

using System;
using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using Siccity.GLTFUtility;

public class ModelLoader : MonoBehaviour
{
    GameObject wrapper;
    string filePath;

    private void Start()
    {
        filePath = $"{Application.persistentDataPath}/Files/";
        wrapper = new GameObject
        {
            name = "Model"
        };
    }
    public void DownloadFile(string url)
    {
        string path = GetFilePath(url);
        if (File.Exists(path))
        {
            Debug.Log("Found file locally, loading...");
            LoadModel(path);
            return;
        }

        StartCoroutine(GetFileRequest(url, (UnityWebRequest req) =>
        {
            if (req.isNetworkError || req.isHttpError)
            {
                // Log any errors that may happen
                Debug.Log($"{req.error} : {req.downloadHandler.text}");
            } else
            {
                // Save the model into a new wrapper
                LoadModel(path);
            }
        }));
    }

    string GetFilePath(string url)
    {
        string[] pieces = url.Split('/');
        string filename = pieces[pieces.Length - 1];

        return $"{filePath}{filename}";
    }

    void LoadModel(string path)
    {
        ResetWrapper();
        GameObject model = Importer.LoadFromFile(path);
        model.transform.SetParent(wrapper.transform);
    }

    IEnumerator GetFileRequest(string url, Action<UnityWebRequest> callback)
    {
        using(UnityWebRequest req = UnityWebRequest.Get(url))
        {
            req.downloadHandler = new DownloadHandlerFile(GetFilePath(url));
            yield return req.SendWebRequest();
            callback(req);
        }
    }

    void ResetWrapper()
    {
        if (wrapper != null)
        {
            foreach(Transform trans in wrapper.transform)
            {
                Destroy(trans.gameObject);
            }
        }
    }
}

Share this article:

You might also like: