How do I export a Three.js scene into GLB format?

I have a simple Three.js scene with only one cube. The cube has multiple materials and I am trying to send this cube to a backend. To do so I think the best approach would be to export the cube into GLTF or GLB format using GLTFExporter, download the .gltf or .glb file and then send it to the backend.

My main issue is that I am unable to convert the cube into a GLTF or GLB format properly. The materials are not staying intact. If the 6 faces of the cube have 6 different materials, then after exporting the .gltf file or .glb file has 6 faces of the cube with only one material applied on all the faces.

How do I resolve this issue?

This issue is also reported in the official GitHub repository. Here is the link to the issue - GLTFExporter does not honor groups with non-indexed geometry. · Issue #21538 · mrdoob/three.js · GitHub

Strange, indexed geometries with groups should be supported by the exporter. Do you mind sharing a demo that shows what you are doing?

1 Like

This sandbox should be a good starting point - pensive-microservice-iq2vp - CodeSandbox

I tweaked that code a bit and tried downloading GLB instead of GLTF. The GLB model didn’t even load though. I wonder why🤔

This is what I tried. I set the value of binary to true rather than false to download the model in GLB format rather than GLTF.-

function download() {
  const exporter = new GLTFExporter();
  exporter.parse(scene, function (gltfJson) {
    console.log(gltfJson);
    const jsonString = JSON.stringify(gltfJson);
    console.log(jsonString);

    // The following doesn't seem to work due to iframe sandboxing.
    // But please save the gltf json from the Console to obtain the file.
    const blob = new Blob([jsonString], { type: "application/json" });
    saveAs(blob, "colored-square.glb");
    console.log("Download requested");
  }, { binary: true});
}

The reason I thought that GLB will be better to use rather than GLTF is that GLB has all the materials and other metadata of the model embedded inside one binary file i.e colored-square.glb

You have this line in your code sandbox:

geometry = geometry.toNonIndexed();

This transform the geometry from indexed to non-indexed. Non-indexed geometries with groups are not supported like mentioned in the issue.

Alright. So I commented out the lines that add group to the geometry.

I am still unable to export it in GLB format though. I tried GLTF and it worked perfectly but I need to export it in GLB format. Thus, I have made the changes. Unfortunately, I am still not able to see the GLB model in 3D Viewer. Am I going wrong somewhere in the below code?


import "./styles.css";

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter";
import { saveAs } from "file-saver";

var renderer, scene, camera;

init();
render();

function init() {
  // renderer
  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setClearColor( 0x000000, 0.0 );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  // scene
  scene = new THREE.Scene();

  // camera
  camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
  camera.position.set( 15, 20, 30 );
  scene.add( camera );

  // controls
  var controls = new OrbitControls( camera, renderer.domElement );
  controls.addEventListener( 'change', render );
  controls.minDistance = 10;
  controls.maxDistance = 50;

  // ambient
  scene.add( new THREE.AmbientLight( 0xffffff, 0.1 ) );

  // light
  var light = new THREE.PointLight( 0xffffff, 1 );
  camera.add( light );

  // geometry
  var geometry = new THREE.BoxBufferGeometry( 10, 10, 10 );
  // geometry.clearGroups();
  // geometry.addGroup( 0, 6, 0 );
  // geometry.addGroup( 6, 6, 1 );
  // geometry.addGroup( 12, 6, 2 );
  // geometry.addGroup( 18, 6, 3 );
  // geometry.addGroup( 24, 6, 4 );
  // geometry.addGroup( 30, 6, 5 );
  // geometry = geometry.toNonIndexed(); // Important for repro

  // materials
  var materials = [
    new THREE.MeshBasicMaterial( { color: 0xFFFFFF, visible: true } ),
    new THREE.MeshBasicMaterial( { color: 0xFF0000, visible: true } ),
    new THREE.MeshBasicMaterial( { color: 0x00FF00, visible: true } ),
    new THREE.MeshBasicMaterial( { color: 0x0000FF, visible: true } ),
    new THREE.MeshBasicMaterial( { color: 0xFFFF00, visible: true } ),
    new THREE.MeshBasicMaterial( { color: 0x00FFFF, visible: true } )
  ];

  // mesh
  var mesh = new THREE.Mesh(geometry, materials );
  scene.add( mesh );

  render();
}

function render() {
	renderer.render( scene, camera );
}

function download() {
  const exporter = new GLTFExporter();
  exporter.parse(scene, function (gltfJson) {
    console.log(gltfJson);
    const jsonString = JSON.stringify(gltfJson);
    console.log(jsonString);

    // The following doesn't seem to work due to iframe sandboxing.
    // But please save the gltf json from the Console to obtain the file.
    const blob = new Blob([jsonString], { type: "application/json" });
    saveAs(blob, "colored-square.glb");
    console.log("Download requested");
  }, { binary: true});
}

var btn = document.createElement("button");
document.body.appendChild(btn);
btn.textContent = "Download .gltf";
btn.onclick = download;

If you do it exactly like in the official example, it works:

var btn = document.createElement('button');
document.body.appendChild(btn);
btn.textContent = 'Download .glb';
btn.onclick = download;

function download() {
  const exporter = new GLTFExporter();
  exporter.parse(
    scene,
    function (result) {
      saveArrayBuffer(result, 'scene.glb');
    },
    { binary: true }
  );
}

function saveArrayBuffer(buffer, filename) {
  save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
}

const link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link); // Firefox workaround, see #6594

function save(blob, filename) {
  link.href = URL.createObjectURL(blob);
  link.download = filename;
  link.click();

  // URL.revokeObjectURL( url ); breaks Firefox...
}
3 Likes

Thanks a lot for the code! It works perfectly and I was able to modify it according to my project requirements.

2 Likes

I’m trying to download my scene in R3F, but it only downloads a file thats small and when i try to import it into Blender for example it gives me the following error: Bad glTF: json error: Expecting value: line 1 column 2 (char 1).

thats my Code:

const { gl, scene, camera } = useThree();

function download() {
    const exporter = new GLTFExporter();
    exporter.parse(
      scene,
      function (result) {
        saveArrayBuffer(result, "scene.glb");
      },
      { binary: true }
    );
  }

  function saveArrayBuffer(buffer, filename) {
    save(new Blob([buffer], { type: "application/octet-stream" }), filename);
  }

  const link = document.createElement("a");
  link.style.display = "none";
  document.body.appendChild(link); // Firefox workaround, see #6594

  function save(blob, filename) {
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
  }

I’m calling the function in my Experience.jsx where all my Components come together. What am I doing wrong?

You seem to be missing the error callback of GLTFExporter, try like so…

const { gl, scene, camera } = useThree();

function download() {
    const exporter = new GLTFExporter();
    exporter.parse(
      scene,
      function (result) {
        saveArrayBuffer(result, "scene.glb");
      },
	  // called when there is an error in the generation
	  function ( error ) {

		  console.log( 'An error happened' );

	  }, 
      { binary: true }
    );
  }

  function saveArrayBuffer(buffer, filename) {
    save(new Blob([buffer], { type: "application/octet-stream" }), filename);
  }

  const link = document.createElement("a");
  link.style.display = "none";
  document.body.appendChild(link); // Firefox workaround, see #6594

  function save(blob, filename) {
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
  }
3 Likes

Thanks alot, that worked perfectly!!

I had the same issue as the other guy, but with this code, it worked! Thanks bro. I appreciate you!!