Three.jsでOBJファイルを読み込む(webpackを使って)

Three.jsでOBJファイルを読み込む時にMTLLoaderとOBJLoaderを使いたいけど、webpackを使った時に導入方法がよくわからなかったので色々調べたメモ。
ついでにStatsでFPSの計測とOrbitControlsを使ったマウス操作にも対応。

前提
・webpackでビルドできる環境ができてる
・objモデルはどこからか拾ってきてください

まずは"three.js"と、three.jsのexampleに保存されているファイルをインポートするために
“imports-loader"と"expose-loader"をpackage.jsonに追加しておく。

package.json

"devDependencies": {
  "imports-loader": "^0.7.0",
  "expose-loader": "^0.7.0",
  "three": "^0.88.0",
  ・・省略・・
}

 

表示するHTMLはwebpackで処理されたapp.jsを読み込む。それだけ。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>three.js sample</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>
<script src="js/app.js"></script>
</body>
</html>

   

んで、これが実際の処理

app.js

var THREE = require('three');

// Threeのサンプルソースからインポート
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/loaders/MTLLoader'
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/loaders/OBJLoader'
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/controls/OrbitControls.js'
// ついでにStatsも使えるように
require("expose-loader?Stats!../../node_modules/three/examples/js/libs/stats.min.js");

(function(){
  var container;
  var stats;
  var scene,camera,ambient;
  var renderer;
  var initialized = false;

 
  init();
  animate();

  function init() {

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

    var width = window.innerWidth,
        height = window.innerHeight;
        
    scene = new THREE.Scene();

    // 読み込むモデルの指定
    var modelPath = "cat/";   // mtlとobjの保存されてるパス
    var mtlName = "cat.mtl";  // mtlファイル名
    var objName = "cat.obj";  // objファイル名


    var objLoader = new THREE.OBJLoader();
    var mtlLoader = new THREE.MTLLoader();
    
    // onProgressとonErrorの引数にはProgressEventってオブジェクトが渡されます
    var onProgress = function ( evt ) {
      if ( evt.lengthComputable ) {
          var completed = evt.loaded / evt.total * 100;
          console.log( Math.round(completed, 2) + '% downloaded' );
      }
    };
    var onError = function ( evt ) {
        console.error("モデルの読み込みに失敗したみたい");
        var xhr = evt.target;
        if (xhr){
          console.error(xhr.responseURL + " status:" + xhr.status + "(" + xhr.statusText + ")");
        }
    };

    mtlLoader.setPath( modelPath ); //パスを指定。これを指定しないとテクスチャ画像の読み込み先が変なことになる。
    mtlLoader.load(mtlName, function (materials){
        materials.preload();

        objLoader.setMaterials(materials);
        objLoader.setPath( modelPath);
        objLoader.load(objName, function (object){
            
            scene.add( object );

            initialized = true;
        }, onProgress, onError);

    }, onProgress, onError);


    ambient = new THREE.AmbientLight(0xffffff);
    scene.add(ambient);

    camera = new THREE.PerspectiveCamera(45, width / height, 1, 5000);
    camera.position.set(0, 100, 200);
    camera.lookAt(new THREE.Vector3(0, 0, 0));


    renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    renderer.shadowMap.enabled = true;
    container.appendChild( renderer.domElement );


    // マウスで操作可能とする
    var controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.maxPolarAngle = Math.PI * 0.5;
    controls.minDistance = 100;
    controls.maxDistance = 3000;

    // FPSなどのモニタリング
    stats = new Stats();
    container.appendChild( stats.dom );
  }

  function animate() {
    requestAnimationFrame( animate );
    if (!initialized) return;

    renderer.render( scene, camera );
    stats.update();
  }

})();

   

よくあるThree.jsのサンプルと変わらないけど、先頭のインポートたちが重要。
この記述

import 'imports-loader?THREE=three!../../node_modules/three/・・省略・・'

でnode_modulesの中に展開されているjsファイルをインポート。

“mtlLoader.load"あたりでインポートされたMTLLoaderとOBJLoaderを使ってモデルを読み込んでます。

 

注意点

たまに正しく読み込めないOBJファイルがある。(Free3Dにあるブガッディとかはダメだった)
なんか変だな?と思ったら別のOBJファイル、MTLファイルを使ってみて。