“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);
}
}
}
}
Hello,
You created a very good tutorial : it works well on windows PC with my glb file.
I try to do the same in webgl.
I modify in your ModelLoader script :
private void Start()
{
filePath = $”{Application.persistentDataPath}/Files/”;
by filePath = Application.dataPath;
I put my 3dfile in https://mywebsite.com/gltfreader/build/my.glb file.
I build and I put to my website the webgl publish in https://mywebsite.com/gltfreader/
But when I click online on the button there is no 3D object .
Could you help me ?
Hi Slidefactory, your tuto is so great, but when I build into a Mac app or IOS app, I can not load the file, it said I found the file but I can not see them load into scenes.
Do you know why?
Is this also possible with obj files?
Hi, i wanted to know how did you go about adding your custom glb/gltf files ? Did you convert it to .json and uploaded it on server ? If yes, how did you do that ? Thank you for your help.