Three.jsでJSON形式でモデルを読み込んでBoneアニメーションさせるときの注意点


ちょっと(だいぶ?)ハマったのでメモ。

いやー、Three.jsはupdateが激しいのと、そもそも日本語記事が少ないのでだいぶ苦戦した。(というか、まだ全然しっかり把握できてないけど)

とりあえず モデルを読み込んでアニメーションさせる 、というWebGLでなにかやるときに避けて通れない処理でハマったのでメモ。

まず結論から言うと、**Boneアニメーションをさせる際は、Boneの数が問題になる**ぽい。 boneの数はどうやら関係ないぽい。改めて書きだしてみたら正常にアニメーションしました。

ちなみにモデルデータはBlenderでThree.jsのJSON形式にExportしたものを使用。

Export設定

Export設定

モデルを作成してBoneをいくつか適当に追加してから(17くらい?)Exportして読み込ませたところ、モデル自体は正常に読み込まれているものの、アニメーションしない。

試しにBoneをふたつ程度に減らしてExport→読み込み、ってしたら動いた。
なので、Boneを複数使って複雑な動きをさせる場合はモデルを分けてThree.js側で合成するなどの処理が必要かもしれない。

ただ、さすがにそれはめんどくさいのでもう少し調べて見ることにする。

ちなみにアニメーションしたソースをいちおう載せておく

animation.js
var container;
var camera, scene, projector, renderer;
var mesh;
var animation;
var composer;

init();

function init() {

    container = document.createElement('div');
    document.body.appendChild(container);

    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = 800;
    camera.target = new THREE.Vector3(0, 0, 0);

    scene = new THREE.Scene();

    var light = new THREE.DirectionalLight( 0xefefff, 2);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);

    var light = new THREE.DirectionalLight(0xffefef, 2);
    light.position.set(-1, -1, -1).normalize();
    scene.add(light);

    var loader = new THREE.JSONLoader(true);
    var url = 'models/some-model.js';
    loader.load(url, function(geometry, materials) {
      var material = new THREE.MeshFaceMaterial(materials);
      mesh = new THREE.SkinnedMesh(geometry, material);
      mesh.scale.x = mesh.scale.y = mesh.scale.z = 150.0;

      //enable skinning
      mesh.material.materials.forEach(function (mat) {
          mat.skinning = true;
      });

      scene.add(mesh);

      if (mesh.geometry.animation.name) {
        THREE.AnimationHandler.add(mesh.geometry.animation);
        animation = new THREE.Animation(mesh, mesh.geometry.animation.name, THREE.AnimationHandler.CATMULLROM);
        animation.play();
      }

      animate();
    });

    renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
    renderer.sortObjects = false;
    renderer.setSize(window.innerWidth, window.innerHeight);

    container.appendChild(renderer.domElement);

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

    //ポストプロセスの設定
    composer = new THREE.EffectComposer(renderer);
    composer.addPass(new THREE.RenderPass(scene, camera));

    //オリジナルのポストプロセスを追加
    composer.addPass(new THREE.ShaderPass({
      vertexShader: document.getElementById('vshader').textContent,
      fragmentShader: document.getElementById('fshader').textContent,
      uniforms: {
        ef: { type: 't', value: 0.2 },
        tDiffuse: { type: 't', value: null }
      }
    }));

    var toScreen = new THREE.ShaderPass(THREE.CopyShader);
    toScreen.renderToScreen = true;
    composer.addPass(toScreen);
}

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

function animate() {
    requestAnimationFrame(animate);
    animation && animation.update(0.03);
    render();
}

function render() {

    //theta += 0.1;
    //cnt++;

    //camera.position.x = radius * Math.sin(THREE.Math.degToRad(theta));
    //camera.position.z = radius * Math.cos(THREE.Math.degToRad(theta));
    //camera.lookAt(camera.target);

    //if (mesh && mesh.morphTargetInfluences) {

    //    // Alternate morph targets

    //    var time = Date.now() % duration;

    //    var keyframe = Math.floor(time / interpolation);

    //    if (keyframe != currentKeyframe) {

    //        mesh.morphTargetInfluences[lastKeyframe] = 0;
    //        mesh.morphTargetInfluences[currentKeyframe] = 1;
    //        mesh.morphTargetInfluences[keyframe] = 0;

    //        lastKeyframe = currentKeyframe;
    //        currentKeyframe = keyframe;
    //    }

    //    mesh.scale.set(100, 100, 100);
    //    mesh.morphTargetInfluences[keyframe] = (time % interpolation) / interpolation;
    //    mesh.morphTargetInfluences[lastKeyframe] = 1 - mesh.morphTargetInfluences[keyframe];
    //}

    //renderer.render(scene, camera);
    composer.render();
}

参考リンク