THREE.js中加载不同格式的模型及动画(fbx、json和obj)

注:本文章内容基于 Three.js 88dev 实现

作为刚接触three.js的小萌新,励志将自己开荒历程记录下来,希望对后来人有所帮助。
网上有很多demo,文档却不多。每次都是,照搬别人的数据没问题,换成自己的模型/动画总会报错! (╯‵□′)╯︵┻━┻
多次踩坑后,总结出三种常用格式的加载方法。

1、fbx文件

three.js有官方的fbx插件,可以直接将模型加载至网页,并且支持动画数据,代码量也是最少的。
但是,该格式存在很大弊端:插件对文件格式的规范很严格,换言之,插件支持性不太好。从网上下载的fbx动画,十有八九会加载失败。

首先需要引入FBXLoader.js插件,如果 报错 “Error: THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js ,则还需引入inflate.min.js文件。

var fbx_loader = new THREE.FBXLoader(manager);

1.1、静态模型

fbx_loader.load('./models/miku/miku.fbx', function(object) {
    object.scale.multiplyScalar(.1);    // 缩放模型大小
    scene.add(object);
}, onProgress, onError);

1.2、动画

fbx_loader.load('./models/gf/run.fbx', function(object) {
    object.mixer = new THREE.AnimationMixer(object);
    mixers.push(object.mixer);
    var action = object.mixer.clipAction(object.animations[0]);
    action.play();
    object.scale.multiplyScalar(.5);
    scene.add(object);
}, onProgress, onError);
1、如果遇到报错 “URIError: URI malformed”,说明fbx文件格式不符合插件要求。可能是fbx版本过低导致的。

检测方法:
新版blender导入该fbx文件,如果提示 “Version xxxx unsupported, must be xxxx or later”,说明你的模型文件版本太低。

解决方案有三:
(1)导入3dmax,再重新导出成fbx文件,将低版本转换为最新版本。经实践,虽然在3dmax和blender中动画显示正常,但是载入网页后模型变得支离破碎。可能是坐标丢失,目前还在寻找原因及解决办法。
(2)用官方提供的插件将fbx文件转换成json动画数据。
(3)根据 [blender]version 6100 unsupported,must be 7100 or later问题怎么办 提供的方法,可通过FBX_Converter_2013将低版本fbx转换成可支持的fbx文件,亲测可用。

2、如果遇到报错 “TypeError: Cannot read property 'has' of undefined”,同样是因为模型文件不符合规范。

检测方法:
Chrome Devtools断点调试,你会发现很多参数都是undefined。

解决方案:
同上(2)。

2、json文件

three.js自带了加载json的方法,所以不需要额外引用插件。

Three.js展示模型问题总结 中讲到:

现在的JSON格式有两个类型,一个是Geometry类型,需要JSONLoader加载;一个是Object类型,需要ObjectLoader加载。

用错loader.js的话,会报错 “THREE.ObjectLoader: Can't load xxx.json. Use THREE.JSONLoader instead.” 或者 “THREE.JSONLoader: xxx.json should be loaded with THREE.ObjectLoader instead.”

2.1、静态模型

2.1.1、Geometry类型
var js_loader = new THREE.JSONLoader(manager);
js_loader.load('./models/hmj/frame001.json', function(geometry, materials) {
    var material = new THREE.MultiMaterial(materials);    // 多个纹理
    var mesh = new THREE.Mesh(geometry, material);
    mesh.scale.multiplyScalar(.06);
    scene.add(mesh);
}, onProgress, onError);
var object_loader = new THREE.ObjectLoader(manager);
object_loader.load('./models/teapot-claraio.json', function(object) {
    object.scale.multiplyScalar(5);
    scene.add(object);
var js_loader = new THREE.JSONLoader(manager);
js_loader.load('./models/body/climb.js', function(geometry, materials) {
    for(var i = 0; i < materials.length; i++) {
        materials[i].skinning = true;
    var material = new THREE.MultiMaterial(materials);
    var mesh = new THREE.SkinnedMesh(geometry, material);    // 划重点啊!!!
    var mixer = new THREE.AnimationMixer(mesh);
    mixer.clipAction(geometry.animations[0]).play();
    mixers.push(mixer);
    mesh.scale.multiplyScalar(.05);
    mesh.lookAt(new THREE.Vector3(0, 0, 0));
    scene.add(mesh);
}, onProgress, onError);

说到json格式动画,一把辛酸泪(╥﹏╥)。从动画制作到maya导出,再到网页载入,无不有坑。

1、由于不了解three.js的数据需求,动画制作方用maya插件advancedSkeleton进行绑骨,导致动画可以展示,数据却导出不来,只能自掏腰包重做。
  制作动画时,切记要做成能导出fbx格式的。

2、用maya做动画,虽然官方有提供maya转three.js的插件,不过导出数据时,还是碰到了不少问题。
  maya2016用官方插件报错 “IOError: file C:\Program Files\Autodesk\Maya2016\bin\python27.zip\shutil.py line 82: 2”Maya 导出动画到THREE.js 的博主给出修改后的插件,亲测可以使用。然而还是不可避免地掉了坑。
  Maya导出动画到THREE.js(补充) 对修改后的插件进行了总结,其中隐藏网格和报错 “More than 4 influences on a vertex in xxx” 的问题我这边也遇到了。

当然,以上两个bug最直接有效的解决办法是,在做动画时就让设计师删除隐藏网格并导出可用的fbx文件。

3、json动画载入网页时,报错 “THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.”,google了好久才查出,是因为自己创建的不是骨骼蒙皮网格对象(代码划重点部分)。
  从3dMax导出供threeJS使用的带动作... 里 “代码中如何加载动态模型” 有具体讲解。

3、obj文件

obj格式文件不支持动画数据存储,只用于静态模型。

首先需要引入OBJLoader.js插件,如果纹理贴图是tga或dds格式的,则还需要另外引入TGALoader.js或DDSLoader.js(纹理贴图问题同样适用于其他模型格式)。

var obj_loader = new THREE.OBJLoader(manager);
obj_loader.setPath('./models/mooncake/');    // 设置文件路径

自己总结了两种加载方法。

3.1、外部载入纹理

官方文档 - MeshPhongMaterial

var tga_loader = new THREE.TGALoader();
var material = new THREE.MeshPhongMaterial({
    map: tga_loader.load('./models/mooncake/Diffuse.tga'),
    normalMap: tga_loader.load('./models/mooncake/Normal.tga'),
    specularMap: tga_loader.load('./models/mooncake/S.tga'),
    bumpMap: tga_loader.load('./models/mooncake/Bump.tga')
});    // 存在多个纹理材质,具体参数查看[官方文档 - MeshPhongMaterial]
obj_loader.load('mooncake.obj', function(group) {
    var geometry = group.children[0].geometry;
    geometry.attributes.uv2 = geometry.attributes.uv;
    geometry.center();
    var mesh = new THREE.Mesh(geometry, material);
    mesh.scale.multiplyScalar(.1);
    scene.add(mesh);
}, onProgress, onError);

3.2、obj+mtl

需要额外引用MTLLoader.js文件

THREE.Loader.Handlers.add(/\.tga$/i, new THREE.TGALoader());    // 划重点!
var mtl_loader = new THREE.MTLLoader();
mtl_loader.setPath('./models/mooncake/');
mtl_loader.load('mooncake.mtl', function(materials) {
    materials.preload();
    obj_loader.setMaterials(materials);
    obj_loader.load('mooncake.obj', function(object) {
        object.scale.multiplyScalar(.1);
        scene.add(object);
    }, onProgress, onError);
obj静态模型 - obj+mtl
一开始打算用obj+mtl方法加载obj模型,但由于项目是tga格式贴图,参考大佬的代码,却怎么都显示不了纹理,最后放弃转而琢磨出第一种方法来。
后来偶然看到THREE.Loader.Handlers.add(/\.tga$/i, new THREE.TGALoader());这句代码。经实践,果然能载入某些不常用格式的纹理材质。
2018.07.13更新

1、fbx文件1.2、动画解决方案中新增加了fbx低版本转高版本的方法