Three.js(十四)—— 模型文件加载

文章目录

  • 14、Three.js 加载外部模型文件
    • 14.1 Three.js 数据结构、导入导出
      • Threejs导出模型信息
      • 自定义模型加载器文件
      • 加载Three.js导出的模型数据
    • 14.2 加载 stl 文件并解析
      • stl文件数据结构
      • 通过STLLoader.js加载.stl文件
      • 使用点模型渲染STL文件
    • 14.3 加载obj文件(几何体、材质、贴图)
      • 只加载obj文件
      • 同时加载obj文件和mtl文件
      • obj包含多个网格模型
      • 模型纹理贴图
      • 导出.obj和.mtl的名称、路径问题
      • .obj文件不包含信息
    • 14.4 加载FBX并解析骨骼动画
      • 加载器FBXLoader.js
      • 加载fbx模型文件
      • 查看FBX模型帧动画数据
      • 解析fbx模型骨骼动画
    • 14.5 手镯在线预览
      • 设置纹理贴图
      • 切换颜色
    • 14.6 心脏预览(法线、高光、环境贴图)
      • 心脏次时代模型加载设置

14、Three.js 加载外部模型文件

实际开发中,大多数项目,通常是3D美术设计师或建筑、机械等行业工程师提供的由3dmx、blender、substence、Solidworks等软件创建好的三维模型文件。

本章节第一小节以Threejs引擎自身为例,讲解Threejs模型导入导出,该小节对Threejs模型文件本身进行了讲解,让你明白你加载的三维模型文件里面都是包含什么内容。其它章节展示了一些加载外部不同格式模型的案例,除了讲解实现代码外,还对模型的数据结构进行了简单讲解,尽管加载一个模型不一定需要知道它的内容结构,但是如果你理解模型本质上就是顶点、材质等数据,那么随意给你一种格式模型,你至少会有了解该模型能够包含哪些数据的意识。

在这里插入图片描述

14.1 Three.js 数据结构、导入导出

Threejs导出模型信息

你可以通过下面代码导出模型的各类信息,然后在浏览器控制台打印出来模型数据,然后复制浏览器控制台模型数据粘贴到json文件中,最后可以尝试加载解析这些Threejs导出的json文件。之所以这么做,是为了让你理解其它三维软件,比如3dmax、blender软件导出的三维模型文件本质上是什么。

查看Threejs文档Geometry、Material、Light、Object3D等类,你可以发现这些类都提供了一个方法.toJSON()通过这个方法可以导出Threejs三维模型的各类数据,该方法的功能就是把Threejs的几何体、材质、光源等对象转化为JSON格式导出。

导出几何体信息。

var geometry = new THREE.BoxGeometry(100, 100, 100);
// 控制台查看立方体数据
console.log(geometry);
// 控制台查看geometry.toJSON()结果
console.log(geometry.toJSON());
// JSON对象转化为字符串
console.log(JSON.stringify(geometry.toJSON()));
// JSON.stringify()方法内部会自动调用参数的toJSON()方法
console.log(JSON.stringify(geometry));

导出材质信息。

var material = new THREE.MeshLambertMaterial({color: 0x0000ff,
}); //材质对象Material
console.log(material);
console.log(material.toJSON());
console.log(JSON.stringify(material));

导出场景scene信息。

var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
console.log(scene);
console.log(scene.toJSON());

自定义模型加载器文件

实际开发中,加载一种特定格式的模型文件,Threejs在three.js-master\examples\js\loaders目录下会提供一系列的加载器,这些加载器本质上都是解析模型文件的字符串,通过正则表达式提取相关的顶点、材质等信息转化为Threejs自身的类表示的对象。为了让大家更理解这些常见的加载器,课件中提供了一个源码案例,编写了一个非常非常简易的自定义加载器,然后使用自定义的加载器去加载一个文件,让大家明白这些加载器怎么来的。
在这里插入图片描述

// 如果编写通用的材质加载器需要枚举所有的材质,这里没有列举完
var typeAPI = {MeshLambertMaterial: THREE.MeshLambertMaterial,MeshBasicMaterial: THREE.MeshBasicMaterial,MeshPhongMaterial: THREE.MeshPhongMaterial,PointsMaterial: THREE.PointsMaterial,
}
// 创建一个文件加载器,该加载器是对异步加载的封装
var loader = new THREE.FileLoader();
loader.load('material.json', function(elem) {console.log(elem);// 查看加载返回的内容var obj = JSON.parse(elem);// 字符串转JSON对象console.log(obj);// 查看转化结果var geometry = new THREE.BoxGeometry(100, 100, 100);/*** 解析材质数据*/// 根据type的值判断调用threejs的哪一个APIvar material = new typeAPI[obj.type]();// 从obj.color中提取颜色// 16711935对应颜色0xFF00FF  255对应颜色0x0000FFmaterial.color.r = (obj.color >> 16 & 255) / 255; //获取颜色值R部分material.color.g = (obj.color >> 8 & 255) / 255; //获取颜色值G部分material.color.b = (obj.color & 255) / 255; //获取颜色值B部分var mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshscene.add(mesh); //网格模型添加到场景中
})

加载Three.js导出的模型数据

缓冲几何体数据加载器。

/****/
var loader = new THREE.BufferGeometryLoader();
loader.load('bufferGeometry.json',function (geometry) {// 控制台查看加载放回的threejs对象结构console.log(geometry);var material = new THREE.MeshLambertMaterial({color: 0x0000ff,}); //材质对象Materialvar mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshscene.add(mesh); //网格模型添加到场景中
})

网格模型Mesh加载,包含几何体Geometry和材质Material

var loader = new THREE.ObjectLoader();
loader.load('model.json',function (obj) {console.log(obj);
console.log(obj.type);obj.scale.set(100,100,100)scene.add(obj)
})

加载组Group对象,模型对象构成的树结构

loader.load('group.json', function(obj) {console.log(obj);console.log(obj.type);scene.add(obj)
})

加载场景对象,场景对象不仅包含模型,还包括光源对象

loader.load('scene.json',function (obj) {console.log(obj);console.log(obj.type);obj.scale.set(100,100,100)scene.add(obj)
})

14.2 加载 stl 文件并解析

基本所有的三维软件都支持导出.stl格式的三维模型文件,.stl格式的三维模型不包含材质Material信息,只包含几何体顶点数据的信息,你可以简单地把stl文件理解为几何体对象Geometry,本节课素材box.STL是一个立方体, 你可以用记事本或代码编辑器打开文件box.STL查看stl的数据结构。

stl文件数据结构

.stl文件格式的数据结构,对于大多数普通开发者来说,如果仅仅为了加载显示一个三维模型,也没必要掌握,这里之所以要强调,不是为了让你记住,而是为了从底层了解Threejs模型加载的原理,达到举一反三的目的

三个位置坐标和一个三角形面的法线方向向量是一组数据,这一组数据表示一个三角形面的信息,可以回忆下第二章关于三角形面Face3的讲解。

表示一个三角形面信息的一组数据

//三角面1facet normal 0 0 -1    //三角形面法向量outer loopvertex 50 50 -50   //顶点位置vertex 50 -50 -50  //顶点位置vertex -50 50 -50  //顶点位置endloopendfacet

一个立方体有6个矩形平面,每个矩形平面至少需要两个三角形拼接而成。那么立方体6个矩形平面至少需要12个三角形面构成,你可以查看文件box.STL中的12个三角形信息。

solid box  //文件名字
//三角面1facet normal 0 0 -1    //三角形面法向量outer loopvertex 50 50 -50   //顶点位置vertex 50 -50 -50  //顶点位置vertex -50 50 -50  //顶点位置endloopendfacet
//三角面2facet normal 0 0 -1    //三角形面法向量outer loopvertex -50 50 -50   //顶点位置vertex 50 -50 -50   //顶点位置vertex -50 -50 -50  //顶点位置endloopendfacetfacet normal 0 1 0..........
//三角面12facet normal -1 0 0outer loopvertex -50 -50 -50vertex -50 50 50vertex -50 50 -50endloopendfacet
endsolid

通过STLLoader.js加载.stl文件

如果你想通过Threejs加载.stl格式三维模型文件,可以使用Threejs提供的一个扩展库stl加载器STLLoader.js,你可以在Three.js-master包中找到STLLoader.js文件,具体路径是three.js-master\examples\js\loaders.

.html文件中引入Threejs的扩展库STLLoader.js,引入该文件后,就可以在代码中使用构造函数THREE.STLLoader()实例化一个加载器。

<!--引入STLLoader.js文件-->
<script src="STLLoader.js"></script>

通过构造函数THREE.STLLoader()可以把.stl文件中几何体顶点信息提取出来转化为Three.js自身格式的几何体对象BufferGeometry。如果你有兴趣可以阅读STLLoader.js源码,尤其是你想独立开发自己公司特定格式加载器的情况下,更有必要参照学习。

// THREE.STLLoader创建一个加载器
var loader = new THREE.STLLoader();
loader.load('立方体.stl',function (geometry) {// 加载完成后会返回一个几何体对象BufferGeometry,你可以通过Mesh、Points等方式渲染该几何体
})
/*** stl数据加载*/
var loader = new THREE.STLLoader();
// 立方体默认尺寸长宽高各200
loader.load('立方体.stl',function (geometry) {// 控制台查看加载放回的threejs对象结构console.log(geometry);// 查看顶点数,一个立方体6个矩形面,每个矩形面至少2个三角面,每个三角面3个顶点,// 如果没有索引index复用顶点,就是说一个立方体至少36个顶点console.log(geometry.attributes.position.count);// 缩放几何体// geometry.scale(0.5,0.5,0.5);// 几何体居中// geometry.center();// 平移立方体// geometry.translate(-50,-50,-50);var material = new THREE.MeshLambertMaterial({color: 0x0000ff,}); //材质对象Materialvar mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshscene.add(mesh); //网格模型添加到场景中
})

使用点模型渲染STL文件

你可以使用下面代码代替上面代码,查看Three.js如果通过点模型Points渲染stl文件中的顶点数据。

/*** 点渲染模式*/loader.load('离心叶轮.stl',function (geometry) {var material = new THREE.PointsMaterial({color: 0x000000,size: 0.5//点对象像素尺寸}); //材质对象var points = new THREE.Points(geometry, material); //点模型对象scene.add(points); //点对象添加到场景中})

14.3 加载obj文件(几何体、材质、贴图)

使用三维软件导出.obj模型文件的时候,会同时导出一个材质文件.mtl, .obj和.stl文件包含的信息一样都是几何体顶点相关数据,材质文件.mtl包含的是模型的材质信息,比如颜色、贴图路径等。

加载.obj三维模型的时候,可以只加载.obj文件,然后借助three.js引擎自定义材质Material,也可以同时加载.obj和.mtl文件。

只加载obj文件

只加载obj文件,引入路径three.js-master/examples/js/loaders/OBJLoader.js下的OBJLoader.js文件即可

<!-- 引入obj模型加载库OBJLoader.js -->
<script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script>

文件加载

/*** OBJ文件加载  只加载obj文件中的几何信息,不加载材质文件.mtl*/
var loader = new THREE.OBJLoader();
// 没有材质文件,系统自动设置Phong网格材质
loader.load('./立方体/box.obj',function (obj) {// 控制台查看返回结构:包含一个网格模型Mesh的组Groupconsole.log(obj);// 查看加载器生成的材质对象:MeshPhongMaterialconsole.log(obj.children[0].material);scene.add(obj);
})

加载文件返回的对象插入场景中后,你也可以做一些自定的设置,比如缩放、居中等操作。

// 加载后的一些编辑操作
obj.children[0].scale.set(20,20,20);//网格模型缩放
obj.children[0].geometry.center();//网格模型的几何体居中
obj.children[0].material.color.set(0xff0000);//设置材质颜色

同时加载obj文件和mtl文件

mtl文件包含了模型的材质信息,比如模型颜色、透明度等信息,还有纹理贴图的路径,比如颜色贴图、法线贴图、高光贴图等等。

<!-- 引入obj模型加载库OBJLoader.js -->
<script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script>
<!-- 引入obj模型材质加载库MTLLoader.js -->
<script src="../../three.js-master/examples/js/loaders/MTLLoader.js"></script>
/*** OBJ和材质文件mtl加载*/
var OBJLoader = new THREE.OBJLoader();//obj加载器
var MTLLoader = new THREE.MTLLoader();//材质文件加载器
MTLLoader.load('./立方体/box.mtl', function(materials) {// 返回一个包含材质的对象MaterialCreatorconsole.log(materials);//obj的模型会和MaterialCreator包含的材质对应起来OBJLoader.setMaterials(materials);OBJLoader.load('./立方体/box.obj', function(obj) {console.log(obj);obj.scale.set(10, 10, 10); //放大obj组对象scene.add(obj);//返回的组对象插入场景中})
})

obj包含多个网格模型

obj文件可以包含多个网格模型对象,不一定就是一个,这些网格模型对象全部是并列关系,无法通过父子关系构建一个树结构层级模型。

// 没有材质文件,系统自动设置Phong网格材质
OBJLoader.load('./多个模型/model.obj',function (obj) {// 控制台查看返回结构:包含一个网格模型Mesh的组Groupconsole.log(obj);scene.add(obj);// 加载后的一些编辑操作obj.scale.set(20,20,20);//网格模型缩放// 设置其中一个网格模型的颜色obj.children[0].material.color.set(0xff0000);
})

模型纹理贴图

obj模型的mtl文件可能包含纹理贴图,也可能不包含,这主要看3D美术是否设置。

一个包含纹理贴图路径的.mtl文件,如果路径有问题,可能会无法加载,可以仿照该案例修改。

// 一个包含纹理贴图路径的.mtl文件
newmtl material_1Ns 32d 1Tr 0Tf 1 1 1illum 2Ka 0.5880 0.5880 0.5880Kd 0.9880 0.9880 0.9880Ks 0.1200 0.1200 0.1200map_Kd ./贴图/Earth.pngmap_ks ./贴图/EarthSpec.pngnorm ./贴图/EarthNormal.png

mtl和threejs贴图对应关系

mtl贴图 Threejs贴图
map_kd map
map_ks specularMap
map_bump/bump bumpMap
/*** OBJ和材质文件mtl加载*/
var OBJLoader = new THREE.OBJLoader(); //obj加载器
var MTLLoader = new THREE.MTLLoader(); //材质文件加载器
MTLLoader.load('./贴图/material.mtl', function(materials) {// 返回一个包含材质的对象MaterialCreatorconsole.log(materials);//obj的模型会和MaterialCreator包含的材质对应起来OBJLoader.setMaterials(materials);OBJLoader.load('./贴图/model.obj', function(obj) {console.log(obj);scene.add(obj); //返回的组对象插入场景中// 加载后操作obj.children[0].scale.set(2, 2, 2); //缩放球体网格模型// 通过调节参数,地球表面的凹凸感更强obj.children[0].material.normalScale.set(3, 3);})
})

导出.obj和.mtl的名称、路径问题

3dmax导出的obj和mtl模型文件有时候需要修改一下个别位置字符,比如.obj中.mtl文件的名称可能是乱码mtllib �����.mtl,.mtl文件中贴图的路径要设置正确,比如导出的是绝对路径还是相对路径,可以打开看下。

.obj文件不包含信息

.obj文件不包含场景的相机Camera、光源Light等信息,不能导出骨骼动画、变形动画,如果希望导出光照信息、相机信息、骨骼动画信息、变形动画信息,可以选择.fbx、.gltf等格式。

14.4 加载FBX并解析骨骼动画

加载器FBXLoader.js

引入FBX加载器相关文件

<!-- 引入fbx模型加载库FBXLoader -->
<script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/FBXLoader.js"></script>
<!-- 辅助文件 -->
<script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/libs/inflate.min.js"></script>

加载fbx模型文件

加载模型文件,加载完成后,如果模型显示位置不符合要求,可以让3D美术修改,也可以通过Threejs程序进行平移、缩放等操作。

var loader = new THREE.FBXLoader();//创建一个FBX加载器
loader.load("SambaDancing.fbx", function(obj) {// console.log(obj);//查看加载后返回的模型对象scene.add(obj)// 适当平移fbx模型位置obj.translateY(-80);
})

查看FBX模型帧动画数据

stl、obj都是静态模型,不可以包含动画,fbx除了包含几何、材质信息,可以存储骨骼动画等数据。

解析之前可以先在浏览器控制台查看动画相关的数据是如何存储的。你可以看到obj.animations属性的数组包含两个剪辑对象AnimationClip,obj.animations[0]对应剪辑对象AnimationClip包含多组关键帧KeyframeTrack数据,obj.animations[1]对应的剪辑对象AnimationClip没有关键帧数据,也就是说没有关键帧动画。具体的开发中,可能美术提供的模型有很多包含关键帧动画的剪辑对象AnimationClip,你可以根据自己的需要解析某个剪辑对象AnimationClip对应的动画。

var loader = new THREE.FBXLoader();//创建一个FBX加载器
loader.load("SambaDancing.fbx", function(obj) {...// 可以在控制台打印obj对象,找到animations属性console.log(obj)// 查看动画数据  2个剪辑对象AnimationClip,一个有关键帧动画,一个没有console.log(obj.animations)})

解析fbx模型骨骼动画

var mixer=null;//声明一个混合器变量
var loader = new THREE.FBXLoader();//创建一个FBX加载器
loader.load("SambaDancing.fbx", function(obj) {// console.log(obj)scene.add(obj)obj.translateY(-80);// obj作为参数创建一个混合器,解析播放obj及其子对象包含的动画数据mixer = new THREE.AnimationMixer(obj);// 查看动画数据console.log(obj.animations)// obj.animations[0]:获得剪辑对象clipvar AnimationAction=mixer.clipAction(obj.animations[0]);// AnimationAction.timeScale = 1; //默认1,可以调节播放速度// AnimationAction.loop = THREE.LoopOnce; //不循环播放// AnimationAction.clampWhenFinished=true;//暂停在最后一帧播放的状态AnimationAction.play();//播放动画
})
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {renderer.render(scene, camera); //执行渲染操作requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧if (mixer !== null) {//clock.getDelta()方法获得两帧的时间间隔// 更新混合器相关的时间mixer.update(clock.getDelta());}
}
render();

14.5 手镯在线预览

在线预览产品的时候,一个产品可能会提供不同的系列,比如颜色不同、造型款式不同。本节课的玉镯案例模型提供了三种颜色款式,不同的颜色款式本质上就是网格模型的颜色贴图.map不同。
在这里插入图片描述

设置纹理贴图

model.obj文件中已经包含纹理映射的UV坐标数据,直接给模型颜色贴图属性.map赋值即可。

/*** OBJ文件加载  只加载obj文件中的几何信息,不加载材质文件.mtl*/
var loader = new THREE.OBJLoader();
loader.load('./model.obj',function (obj) {// 控制台查看返回结构:包含一个网格模型Mesh的组Group// console.log(obj);//加载纹理贴图texture1.pngvar texture = new THREE.TextureLoader().load('texture1.png');// 颜色贴图中已经包含了光照信息,所以直接使用不受光照影响的基础网格材质MeshBasicMaterialobj.children[0].material= new THREE.MeshBasicMaterial({map:texture,//设置颜色纹理贴图})scene.add(obj);obj.children[0].scale.set(5,5,5);//网格模型缩放
})

切换颜色

模型提供了texture1.png、texture2.png和texture3.png三张贴图,上面代码加载了texture1.png作为颜色贴图。

你可以在上面代码.load()回调函数中插入下面语句,给玉镯设置其它的颜色款式。

// 更换纹理贴图
var texture = new THREE.TextureLoader().load('texture2.png');
obj.children[0].material.map=texture

实际开发的时候,可能会通过前端UI进行颜色交互(颜色交互体验),其实实现也比较简单,比如一个按钮表示绿色,前端代码只要给该按钮绑定一个函数,函数中执行代码mesh.material.map=Texture改变颜色贴图属性.map的值即可。不过本节课主要是讲解Threejs,关于前端交互内容就不做过多描述,你可以自己创建一个按钮测试这个思路。

14.6 心脏预览(法线、高光、环境贴图)

加载一个心脏的次时代模型,模型包含颜色贴图.map、法线贴图.normalMap、高光贴图.specularMap、环境贴图.envMap,关于这些贴图的相关属性可以查看高光网格模型材质的文档MeshPhongMaterial。
在这里插入图片描述
在这里插入图片描述

心脏次时代模型加载设置

/*** OBJ文件加载  只加载obj文件中的几何信息,不加载材质文件.mtl*/
var loader = new THREE.OBJLoader();
// 没有材质文件,系统自动设置Phong网格材质
var mesh = null; //声明一个网格模型变量
loader.load('./heart/model.obj', function(obj) {// 控制台查看返回结构:包含一个网格模型Mesh的组Groupconsole.log(obj);scene.add(obj);mesh = obj.children[0]; //获得心脏网格模型mesh.scale.set(10, 10, 10); //网格模型缩放// 创建一个纹理加载器var textureLoader = new THREE.TextureLoader();
...
})

设置模型的颜色贴图.map。

 //加载颜色纹理var texture = textureLoader.load('./heart/color.png');mesh.material.map = texture;

设置模型的法线贴图.normalMap,表面细节更丰富,为了压缩模型顶点数量,也就是降低文件大小,3D美术通常会给程序员提供法线贴图。

var textureNormal = textureLoader.load('./heart/normal.png');
mesh.material.normalMap = textureNormal
// 设置深浅程度
mesh.material.normalScale.set(1.5, 1.5)

对于心脏模型而言,模型外表面不同区域的粗糙度不同,对光线的镜面反射程度不同,所以可以把这些不同区域的不同光反射信息记录在一个贴图上,即高光贴图.specularMap,设置高光贴图需要高光网格模型材质MeshPhongMaterial。

// 设置高光贴图,一个网格模型不同的区域反射光线的能力不同
var textureSpecular = textureLoader.load('./heart/Specular.png');
mesh.material.specularMap = textureSpecular;
mesh.material.specular.set(0xffffff);// 高光反射颜色
mesh.material.shininess = 100;// 高光高亮程度,默认30

通过类CubeTextureLoader来加载六张纹理贴图’px.jpg’, ‘nx.jpg’, ‘py.jpg’, ‘ny.jpg’, ‘pz.jpg’, ‘nz.jpg’。

设置环境贴图.envMap,反射周围环境效果,渲染更逼真。

var textureCube = new THREE.CubeTextureLoader().setPath('环境贴图/').load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
mesh.material.envMap = textureCube;

渲染模型的时候,合理的设置光源是很必要的,比如光源强度太低,模型就会比较灰暗,光源强度太高,模型会过于明亮。

/*** 光源设置*/
//点光源
var point = new THREE.PointLight(0xffffff, 0.3);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
// 环境光
var ambient = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambient);
// 方向光1
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(400, 200, 300);
scene.add(directionalLight);
// 方向光2
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(-400, -200, -300);
scene.add(directionalLight);

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注