Exporting an animated scene with GLTFExporter in ThreeJS?

Viewed 967

I'm trying to export an animation scene in ThreeJS with GLTFExporter, but the animation is not being exported. Everything is correctly exported, the shapes, the color... This project is using the example to export: https://threejs.org/examples/#misc_exporter_gltf, and the example to animate: https://threejs.org/examples/#misc_animation_keys, but they don't work together. Any idea?

If I open the file with https://gltf-viewer.donmccurdy.com/, I see an error: enter image description here

But if I log the export options when exporting, I can see the animations are there: enter image description here

Here's the code:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>three.js webgl - exporter - gltf</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
    <div id="info">
        <button id="export_scene">Export Scene</button>
    </div>

    <script type="module">

        import * as THREE from '../../build/three.module.js';
        import { GLTFExporter } from './jsm/exporters/GLTFExporter.js';

        function exportGLTF(input, animationClip) {
            const gltfExporter = new GLTFExporter();

            const options = {
                binary: true,
                maxTextureSize: 4096,
                animations: [animationClip],
                includeCustomExtensions: true
            };

            console.log(options);
            gltfExporter.parse(input, function (result) {

                if (result instanceof ArrayBuffer) {

                    saveArrayBuffer(result, 'scene.glb');

                } else {

                    const output = JSON.stringify(result, null, 2);
                    console.log(output);
                    saveString(output, 'scene.gltf');

                }

            }, options);

        }

        document.getElementById('export_scene').addEventListener('click', function () {
            exportGLTF(scene, clip);
        });

        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();
        }

        function saveString(text, filename) {
            save(new Blob([text], { type: 'text/plain' }), filename);
        }

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

        let clock;
        let camera, geometry, scene, renderer, mixer, clip;
        let gridHelper, sphere, smallSphere;

        init();
        animate();

        function init() {
            scene = new THREE.Scene();
            scene.name = 'scene';

            // Perspective Camera
            camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
            camera.position.set(10, 300, 0);

            camera.name = "PerspectiveCamera";
            scene.add(camera);

            // Ambient light
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
            ambientLight.name = 'AmbientLight';
            scene.add(ambientLight);

            // DirectLight
            const dirLight = new THREE.DirectionalLight(0xffffff, 1);
            dirLight.target.position.set(0, 0, - 1);
            dirLight.add(dirLight.target);
            dirLight.lookAt(- 1, - 1, 0);
            dirLight.name = 'DirectionalLight';
            scene.add(dirLight);

            //Axes 
            /*
            const axes = new THREE.AxesHelper(500);
            axes.name = "AxesHelper";
            scene.add(axes);*/

            // Sphere

            const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true });
            sphere = new THREE.Mesh(new THREE.SphereGeometry(70, 10, 10), material);
            sphere.position.set(0, 0, 0);
            sphere.name = "Sphere";
            scene.add(sphere);

            // Small sphere
            smallSphere = new THREE.Mesh(new THREE.SphereGeometry(20, 10, 10), material);
            smallSphere.position.set(80, 0, 0);
            smallSphere.name = "SmallSphere";
            scene.add(smallSphere);

            // POSITION
            const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 90, 60, 30, 0, 60, 70, 40, 50]);

            // SCALE
            const scaleKF = new THREE.VectorKeyframeTrack('.scale', [0, 1, 2], [1, 1, 1, 2, 2, 2, 1, 1, 1]);

            // ROTATION
            // Rotation should be performed using quaternions, using a THREE.QuaternionKeyframeTrack
            // Interpolating Euler angles (.rotation property) can be problematic and is currently not supported

            // set up rotation about x axis
            const xAxis = new THREE.Vector3(1, 0, 0);

            const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
            const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
            const quaternionKF = new THREE.QuaternionKeyframeTrack('.quaternion', [0, 1, 2], [qInitial.x, qInitial.y, qInitial.z, qInitial.w, qFinal.x, qFinal.y, qFinal.z, qFinal.w, qInitial.x, qInitial.y, qInitial.z, qInitial.w]);

            // COLOR
            const colorKF = new THREE.ColorKeyframeTrack('.material.color', [0, 1, 2], [1, 0, 0, 0, 1, 0, 0, 0, 1], THREE.InterpolateDiscrete);

            // OPACITY
            const opacityKF = new THREE.NumberKeyframeTrack('.material.opacity', [0, 1, 2], [1, 0, 1]);

            // Clip
            clip = new THREE.AnimationClip('Action', 3, [scaleKF, positionKF, quaternionKF, colorKF, opacityKF]);

            // Mixer
            mixer = new THREE.AnimationMixer(smallSphere);
            const clipAction = mixer.clipAction(clip);
            clipAction.play();

            // Clock
            clock = new THREE.Clock();

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            window.addEventListener('resize', onWindowResize);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            render();
        }

        function render() {
            const timer = Date.now() * 0.0001;

            const delta = clock.getDelta();

            if (mixer) {
                mixer.update(delta);
            }

            camera.position.x = Math.cos(timer) * 400;
            camera.position.z = Math.sin(timer) * 400;

            camera.lookAt(scene.position);
            renderer.render(scene, camera);
        }

    </script>

</body>

</html>

Any idea?

2 Answers

As Don McCurdy explained in the comments, there were two problems:

  • It's not possible to export animations for the material properties (opacity and color).
  • I had to specify the object name before each pointer: <object-name>.position.

Thank you Don McCurdy.

You need to set a name to your Object3D:

const mesh = new THREE.Mesh(geometry, material);
mesh.name = 'myMesh'; // <- set a name
scene.add(mesh);

When you create your KeyframeTrack use that object's name:

const positionKF = new THREE.VectorKeyframeTrack('myMesh.position', times, values); // <-- use that object's name
Related