Sketchfab's API gives you programmatic access to the largest collection of glTF 3D models on the web. This article walks you through a minimal code example to show you how to:
- Let your users authenticate with the Sketchfab API
- Download a 3D model as a zip file containing the glTF
- Load this zip file into ThreeJS
Source code: https://github.com/OmarShehata/threejs-sketchfab-example
How it works
I originally implemented this to let readers of my WebGL outlines tutorial see how the effect looked on test cases of their choosing. Since I kept finding algorithms that didn't work on my specific corner cases (but I wouldn't find out until after I implemented it/downloaded & ran it!)
It's a really easy way to let users bring in their own data (or millions of example models).
You can see how this works in this outlines live demo:
- Open the demo: https://omarshehata.github.io/csb-l01dp/
- Click Login to Sketchfab
- When directed back to the app, paste a link of a model in the Sketchfab URL field, like this: https://sketchfab.com/3d-models/skull-downloadable-1a9db900738d44298b0bc59f68123393
The model must be downloadable by the Sketchfab account that's logged in.
1 - Authenticate with the Sketchfab API
The first step is to register your app with Sketchfab. The instructions for this are here:
https://sketchfab.com/developers/oauth#registering-your-app
The current process at the time of writing is to contact them.
You'll need to pick a redirect URI. This should be the final URI where you will deploy your app.
You'll want to use the Implicit grant type. We can't keep an API key a secret in a web app, so we instead rely on the redirect URI (if someone malicious uses your client ID, Sketchfab will redirect them to your real app after login, regardless of who may have initiated the login).
After your register your app you'll have a Client ID.
You'll use this to send the user to Sketchfab for login as shown here:
const CLIENT_ID = 'YOUR_CLIENT_ID_HERE';
const AUTHENTICATION_URL = `https://sketchfab.com/oauth2/authorize/?state=123456789&response_type=token&client_id=${CLIENT_ID}`;
window.open(AUTHENTICATION_URL, '_blank');
Once the login is done, you'll need to grab the access token from the URL. Here's a snippet that does this and stores it in local storage here:
checkToken() {
// Check if there's a new token from the URL
const url = new URL(window.location)
// Extract the token and save it
const hashParams = url.hash.split('&');
for (let param of hashParams) {
if (param.indexOf("access_token") !== -1) {
const token = param.replace('#access_token=', '');
console.log("Detected Sketchfab token: ", token);
localStorage.setItem("sb_token", token);
}
}
// Load token from local storage
this.token = localStorage.getItem("sb_token");
}
You'll use this token for subsequent API calls.
Note: while you're developing locally, Sketchfab will redirect to the production URI. You'll need to copy the URI params to your localhost to test.
2 - Download the 3D model
Once you have a token, you can use this to fetch a download URI for the glTF model.
Here is the code snippet for fetching the download URI given any Sketchfab URI like this: https://sketchfab.com/3d-models/skull-downloadable-1a9db900738d44298b0bc59f68123393
async getModelDownloadUrl(inputUrl) {
// Extract the model ID from the URL
const input = new URL(inputUrl);
// The ID is always the last string when seperating by '-'
const pieces = input.pathname.split('-');
const modelID = pieces[pieces.length - 1];
const metadataUrl = `https://api.sketchfab.com/v3/models/${modelID}/download`;
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${this.token}`,
},
mode: 'cors'
};
// This call will fail if model can't be downloaded
const response = await fetch(metadataUrl, options);
const metadata = await response.json();
return metadata.gltf.url;
}
Note that this request will fail if the user who is logged in does not have access to download this model. This will be true for store models that require a purchase, or free models that are not downloadable.
You can filter for downloadable models in the Sketchfab search:
This will show you either free models you can download or models that can be purchased.
Now that we have a URL to a zip file containing a glTF model, we can pass that to ThreeJS to load it.
3 - Load the ZIP file into ThreeJS
This was the tricky part of me. Normally ThreeJS requires a direct URL to the glTF file. To load it from a zip file we're going to (1) unzip the contents into memory using JSZip.
We can't just pass the unzipped raw content to ThreeJS, because a glTF file may reference other files by a filepath (image or geometry resources). So we need to (2) create a Blob for each resource and (3) override the resources the glTF file is requesting with the Blob URI's.
For example, if the glTF file is trying to load textures/defaultMat_baseColor.jpeg
as a relative filepath, we detect this, and instead pass this URI:
//Unzip from JSZip
const file = unzippedBaseColorJPEGFile;
// Create a Blob from this file
const blob = await file.async('blob');
// Create a URL to this Blob
const baseColorBlobUrl = URL.createObjectURL(blob);
// Use ThreeJS's loading manager so instead of loading from relative filepaths we load from the blob URI's we created
const loadingManager = new THREE.LoadingManager(); loadingManager.setURLModifier((url) => {
if (url == 'textures/defaultMat_baseColor.jpeg') {
return baseColorBlobUrl;
}
});
We use ThreeJS's LoadingManager to do this.
Here is the code snippet that can take a URL to any zipped glTF and load it in ThreeJS:
async readZip(zipUrl, scene) {
const response = await fetch(zipUrl);
checkStatus(response);
const arrayBuffer = await response.arrayBuffer();
const result = await JSZip.loadAsync(arrayBuffer);
const files = Object.values(result.files).filter(item => !item.dir);
const entryFile = files.find(f => getExtension(f.name) === 'gltf');
// Create blobs for every file resource
const blobUrls = {};
for (const file of files) {
console.log(`Loading ${file.name}...`);
blobUrls[file.name] = await getFileUrl(file);
}
const fileUrl = blobUrls[entryFile.name];
scene.clear();
// Re-add the light
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);
light.position.set(1.7, 1, -1);
const loadingManager = new THREE.LoadingManager();
loadingManager.setURLModifier((url) => {
const parsedUrl = new URL(url);
const origin = parsedUrl.origin;
const path = parsedUrl.pathname;
const relativeUrl = path.replace(origin + '/', "");
if (blobUrls[relativeUrl] != undefined) {
return blobUrls[relativeUrl];
}
return url
});
const gltfLoader = new GLTFLoader(loadingManager);
gltfLoader.load(fileUrl, (gltf) => {
scene.add(gltf.scene);
});
}
4 - Display attribution
Using 3D models from Sketchfab in your application requires that you display attribution to the original author. Read more about this on Sketchfab.
We can automatically get the attribution & license information from this Data API route.
Here is a function that will construct the attribution text given a modelID:
async getAttributionText(modelID) {
const modelDataUrl = `https://api.sketchfab.com/v3/models/${modelID}`;
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${this.token}`,
},
mode: 'cors'
};
const response = await fetch(modelDataUrl, options);
const metadata = await response.json();
const license = { name: metadata.license.label, url: metadata.license.url };
const user = { name: metadata.user.displayName , url: metadata.user.profileUrl };
const model = { name: metadata.name, url: metadata.viewerUrl };
const attributionText =
`This work is based on <a href="${model.url}" target=_blank>${model.name}</a>
by <a href="${user.url}" target=_blank>${user.name}</a>
licensed under <a href="${license.url}" target=_blank>${license.name}</a>.`;
return attributionText;
}
The example app will display the attribution in the bottom left corner, linking to the original model URL, author's Sketchfab profile, and the license.
Known issues
One problem with loading some Sketchfab models is that their scale will be much bigger than the current viewport. Another problem is some models may not be centered around the origin, so they may not be visible when loaded.
Normalizing and scaling models when loading them in ThreeJS would help solve this, similar to how Don McCurdy's glTF Viewer works.
Thanks for reading! If you found this helpful, follow me on Twitter @omar4ur to see more of my work. Other ways to reach me at https://omarshehata.me/.
Top comments (0)