ThreeJS学习笔记二:导入外部模型

外部模型格式

Threejs支持了许多格式的3D模型导入,包括*.obj、 *.sea、*.3mf 、*.amf、*.sea、*.pmd、*.json等。 
这里主要讲解一下obj模型的导入,及将obj文件转成文件更小的json格式导入。

导入obj模型

3Dmax格式转换成obj格式

美术提供的一般为3Dmax项目文件夹,里面包含了.max文件以及贴图图片等资源,用3DMAX打开.max文件可以看到3D模型 
如图:

1469608235365.png

  • 点击菜单里的导出,选择obj格式,点击保存后出现选项

  • 导出比例:几何体选项里的输出比例,默认是1.0,我们根据模型的分辨率,以及需要在网页上呈现的分辨率设置一下比例输出即可。简单的说,你直接按1.0比例输出的如果在浏览器看的时候大小差距太大了,可以在这里导出比例设置一下,另外也可以设置mesh的scale,直接缩放。这个模型需要将比例设置为0.1。几何体的其他选项都不需要修改。

  • 材质:点击材质导出,勾选使用材质路径,选择保存材质的路径。格式为jpg,格式设置中可以设置导出jpg的质量,无特殊要求默认即可,并勾选转换位图,关闭材质设置。

  • 点击导出,导出过程有可能会提示找不到材质,设置材质路径为美术提供的文件夹目录即可

  • 最后会得到一个*.obj文件、一个*mtl文件、若干材质图,包括多个不同通道的贴图jpg和法线贴图jpg

1469608270416.png

1469608312275.png

1469608413927.png

1469610128497.png

导出obj后的二次处理

导出后的文件如图所示

1469613142241.png

其中,test.mtl文件定义了test.obj模型与各贴图间的对应关系。 
由于示例中的模型是由多个子模型组成的模型组,同时设置了多通道贴图,每一个子模型都对应四张贴图。 
打开mtl文件,我们可以看到每个子模型都有一组贴图数据

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# 创建的文件:27.07.2016 17:00:50

newmtl JZ2012_jz_l_1  
   Ns 20.0000
   Ni 1.5000
   d 1.0000
   Tr 0.0000
   Tf 1.0000 1.0000 1.0000
   illum 2
   Ka 0.5882 0.5882 0.5882
   Kd 0.5882 0.5882 0.5882
   Ks 0.4500 0.4500 0.4500
   Ke 0.0000 0.0000 0.0000
   map_Ka JZ2012_jz_d.jpg
   map_Kd JZ2012_jz_d.jpg
   map_Ks JZ2012_jz_s.jpg
   map_Ke JZ2012_jz_e.jpg
   map_bump Map__171_法线凹凸.jpg

newmtl JZ2030_jz_l_1    
......

例如上面例子中关于子模型JZ2012_jz_l_1 贴图描述,其中map_Ka,map_Kd,map_Ks,map_Ke指定了各通道对应的贴图,map_bump则指定了法线贴图:

1469613671197.png

在使用threejs的mtlLoader解析mtl贴图并渲染为模型材质时,threejs仅调用map_Ka通道图片和map_bump法线贴图进行材质渲染。而基于图片数量及文件大小的控制,项目中采用的方法是让设计师将 JZ2012_jz_d.jpg、JZ2012_jz_s.jpg、JZ2012_jz_e.jpg三张图合成并调色后作为map_Ka通道贴图:

1469623501953.png

注:目前未查到threeJS 的mtl_loader是否能自动载入多通道贴图,待研究。 
另外由于自动导出的法线贴图名称中带有中文,需要将mtl中map_bump和对应的法向贴图名称改成非中文命名。 
在做以上图片处理时,需要同时将图片资源做压缩处理,以免资源加载时间过长。

使用mtlLoader和objLoaer加载obj模型及贴图

加载带mtl材质的obj模型,需要先定义mtl对象并加载mtl之外再加载obj模型。此时需要引用threejs两个js库

    <script src="../js/lib/threejs/MTLLoader.js"></script>
   <script src="../js/lib/threejs/OBJLoader.js"></script>

THREE.MTLLoader()函数说明:

  • mtlLoader.setBaseUrl():设置材质路径

  • mtlLoader.setPath():设置mtl文件所在路径

  • mtlLoader.load(filename,onSuccess(materials ),onProgress(xhr),onError(error)):mtl文件名、 加载成功后回调处理(参数为生成的材质库)、加载过程中回调处理(xhr对象属性可计算出已完成加载百分比)、失败回调处理

THREE.OBJLoader() 函数说明:

  • objLoader.setMaterials( materials ):设置obj使用的材质贴图

  • objLoader.setPath( options.objPath ):设置obj文件所在路径

  • objLoader.load( filename,onSuccess(object ),onProgress(xhr),onError(error)):obj文件名、 加载成功后回调处理(参数为生成的三维对象)、加载过程中回调处理(xhr对象属性可计算出已完成加载百分比)、失败回调处理。

在onSuccess(object ){}回调里我们可以对生成的三维对象做一些处理:对材质进行调色、设置透明度、设置贴图模式等,对设置旋转、缩放、位置摆放、自发光颜色、环境光颜色。 
如果obj文件代表的三维对象是由多个子模型构成的模型组合,我们可以调用object.traverse(function(child){})来对每个子模型进行处理。 
以下简单封装成一个函数


function createMtlObj(options){
//      options={
//          mtlBaseUrl:"",
//          mtlPath:"",
//          mtlFileName:"",
//          objPath:"",
//          objFileName:"",
//          completeCallback:function(object){  
//          }
//          progress:function(persent){
//              
//          }
//      }
   THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );
   var mtlLoader = new THREE.MTLLoader();
   mtlLoader.setBaseUrl( options.mtlBaseUrl );//设置材质路径
   mtlLoader.setPath( options.mtlPath );//设置mtl文件路径
   mtlLoader.load( options.mtlFileName, function( materials ) {
       materials.preload();
       var objLoader = new THREE.OBJLoader();
       objLoader.setMaterials( materials );//设置三维对象材质库
       objLoader.setPath( options.objPath );//设置obj文件所在目录
       objLoader.load( options.objFileName, function ( object ) {


           if(typeof options.completeCallback=="function"){
               options.completeCallback(object);
           }
       }, function ( xhr ) {
           if ( xhr.lengthComputable ) {
               var percentComplete = xhr.loaded / xhr.total * 100;
               if(typeof options.progress =="function"){
                   options.progress( Math.round(percentComplete, 2));
               }
               //console.log( Math.round(percentComplete, 2) + '% downloaded' );
           }
       }, function(error){

       });

   });
}

调用示例:


createMtlObj({
   mtlBaseUrl:"../resource/haven/",
   mtlPath: "../resource/haven/",
   mtlFileName:"threejs.mtl",
   objPath:"../resource/haven/",
   objFileName:"threejs.obj",
   completeCallback:function(object){
       object.traverse(function(child) {
           if (child instanceof THREE.Mesh) {
               child.material.side = THREE.DoubleSide;//设置贴图模式为双面贴图
               child.material.emissive.r=0;//设置rgb通道R通道颜色
               child.material.emissive.g=0.01;//设置rgb通道G通道颜色
               child.material.emissive.b=0.05;//设置rgb通道B通道颜色
               child.material.transparent=true;//材质允许透明
               //child.material.opacity=0;//材质默认透明度                        
               //child.material.shading=THREE.SmoothShading;//平滑渲染
           }
       });
       object.emissive=0x00ffff;//自发光颜色
       object.ambient=0x00ffff;//环境光颜色
//      object.rotation.x= 0;//x轴方向旋转角度
       object.position.y = 0;//位置坐标X
       object.position.z = 0;//位置坐标y
       object.scale.x=1;//缩放级别
       object.scale.y=1;//缩放级别
       object.scale.z=1;//缩放级别
       object.name="haven";//刚体名称
       object.rotation.y=-Math.PI;//初始Y轴方向旋转角度
       scene.add(object);//添加到场景中
   },
   progress:function(persent){

       $("#havenloading .progress").css("width",persent+"%");
   }
})

此时如果在之前没有添加光源,在浏览器上看到的可能是一片黑色,通过增加光源可以将物体显现出来。比例添加一个全局光

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

此时在浏览器看到是这样的效果: 
1470037673302.png

导入obj模型的后期渲染

如上图看到,我们导入的模型经过调色虽然比3Dmax显示效果好些,但仍然一片灰蒙蒙,丑不拉叽的。所以后期需要适当添加各种光源来渲染出更好的效果。 
添加一个平行光做为主光源营造反射光面,并添加三个点光源对暗部适当补光后效果如下:

var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( -5, 5, 5).normalize();
scene.add( directionalLight );
var pointlight = new THREE.PointLight(0x63d5ff, 1, 200);
pointlight.position.set(0, 0, 200);
scene.add( pointlight );                
var pointlight2 = new THREE.PointLight(0xffffff, 1, 200);
pointlight2.position.set(-200, 200, 200);
scene.add( pointlight2 );
var pointlight3 = new THREE.PointLight(0xffffff, 1.5, 200);
pointlight3.position.set(-200, 200, 0);
scene.add( pointlight3 );

1470039562664.png

接下再添加一个4000*4000*4000的天空盒子作为背景,并适当调整camera的可视范围,最后显示结果如下:

1470039862826.png

在设置点光源位置时可借助Threejs提供的helper来查看设置的点光源位置,方便调整光源位置

scene.add( new THREE.PointLightHelper( pointlight3 ) );
scene.add( new THREE.PointLightHelper( pointlight2 ) );
scene.add( new THREE.PointLightHelper( pointlight ) );

1470041255056.png

DEMO:

<!DOCTYPE html>
<html>
   <head>
       <meta charset="UTF-8">
       <title></title>
   </head>
   <body>
   <div id="space"></div>  
   <script   src="https://code.jquery.com/jquery-1.12.4.min.js"   integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="   crossorigin="anonymous"></script>
   <script src="../js/lib/threejs/three.js"></script>
   <script src="../js/lib/threejs/MTLLoader.js"></script>
   <script src="../js/lib/threejs/OBJLoader.js"></script>
   <script src="../js/lib/threejs/OrbitControls.js"></script>
   <script>


       var container, stats;

       var camera, scene, renderer;

       var mouseX = 0, mouseY = 0;

       var windowHalfX = window.innerWidth / 2;
       var windowHalfY = window.innerHeight / 2;


       init();
       animate();
       var mesh;

       function init() {

           container = document.getElementById("space")
           camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 8000 );
           camera.position.set(0, 0, 1500);

           scene = new THREE.Scene();

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


           var directionalLight = new THREE.DirectionalLight( 0xffffff );
           directionalLight.position.set( -5, 5, 5).normalize();
           scene.add( directionalLight );

           var pointlight = new THREE.PointLight(0x63d5ff, 1, 200);
           pointlight.position.set(0, 0, 200);
           scene.add( pointlight );                
           var pointlight2 = new THREE.PointLight(0xffffff, 1, 200);
           pointlight2.position.set(-200, 200, 200);
           scene.add( pointlight2 );
           var pointlight3 = new THREE.PointLight(0xffffff, 1.5, 200);
           pointlight3.position.set(-200, 200, 0);
           scene.add( pointlight3 );
           scene.add( new THREE.PointLightHelper( pointlight3 ) );
           scene.add( new THREE.PointLightHelper( pointlight2 ) );
           scene.add( new THREE.PointLightHelper( pointlight ) );


           var path = "../resource/sky/";
           var format = '.jpg';
           var urls = [
                   path + 'px' + format, path + 'nx' + format,
                   path + 'py' + format, path + 'ny' + format,
                   path + 'pz' + format, path + 'nz' + format
               ];
           var skyMaterials = [];
           for (var i = 0; i < urls.length; ++i) {
               var loader = new THREE.TextureLoader();
               loader.setCrossOrigin( this.crossOrigin );
               var texture = loader.load( urls[i], function(){}, undefined, function(){} );

               skyMaterials.push(new THREE.MeshBasicMaterial({
                   //map: THREE.ImageUtils.loadTexture(urls[i], {},function() { }),
                   map: texture,
                   overdraw: true,
                   side: THREE.BackSide,
                   //transparent: true,
                   //needsUpdate:true,
                   premultipliedAlpha: true
                   //depthWrite:true,

   //              wireframe:false,
               })
               );

           }

           var cube = new THREE.Mesh(new THREE.CubeGeometry(4000, 4000,4000), new THREE.MeshFaceMaterial(skyMaterials));
           cube.name="sky";
           scene.add(cube);

           createMtlObj({
               mtlBaseUrl:"../resource/haven/",
               mtlPath: "../resource/haven/",
               mtlFileName:"threejs.mtl",
               objPath:"../resource/haven/",
               objFileName:"threejs.obj",
               completeCallback:function(object){
                   object.traverse(function(child) {
                       if (child instanceof THREE.Mesh) {
                           child.material.side = THREE.DoubleSide;
                           child.material.emissive.r=0;
                           child.material.emissive.g=0.01;
                           child.material.emissive.b=0.05;
                           child.material.transparent=true;
                           //child.material.opacity=0;                    
                           //child.material.shading=THREE.SmoothShading;
                       }
                   });

                   object.emissive=0x00ffff;
                   object.ambient=0x00ffff;
                   //object.rotation.x= 10/180*Math.PI;
                   object.position.y = 0;
                   object.position.z = 0;
                   object.scale.x=1;
                   object.scale.y=1;
                   object.scale.z=1;
                   object.name="haven";
                   object.rotation.y=-Math.PI;
                   scene.add(object);
               },
               progress:function(persent){

                   $("#havenloading .progress").css("width",persent+"%");
               }
           })
           renderer = new THREE.WebGLRenderer();
           renderer.setPixelRatio( window.devicePixelRatio );
           renderer.setSize( window.innerWidth, window.innerHeight );
           container.appendChild( renderer.domElement );
           document.addEventListener( 'mousemove', onDocumentMouseMove, false );
           window.addEventListener( 'resize', onWindowResize, false );
       }

       var controls = new THREE.OrbitControls(camera,container);
       //controls.maxPolarAngle=1.5;
       //controls.minPolarAngle=1;
       controls.enableDamping=true;
       controls.enableKeys=false;
       controls.enablePan=false;
       controls.dampingFactor = 0.1;
       controls.rotateSpeed=0.1;
//      controls.enabled = false;
       //controls.minDistance=1000;
       //controls.maxDistance=3000;
       function createMtlObj(options){
       //      options={
       //          mtlBaseUrl:"",
       //          mtlPath:"",
       //          mtlFileName:"",
       //          objPath:"",
       //          objFileName:"",
       //          completeCallback:function(object){  
       //          }
       //          progress:function(persent){
       //              
       //          }
       //      }
               //THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );
           var mtlLoader = new THREE.MTLLoader();
           mtlLoader.setBaseUrl( options.mtlBaseUrl );
           mtlLoader.setPath( options.mtlPath );
           mtlLoader.load( options.mtlFileName, function( materials ) {
               materials.preload();
               var objLoader = new THREE.OBJLoader();
               objLoader.setMaterials( materials );
               objLoader.setPath( options.objPath );
               objLoader.load( options.objFileName, function ( object ) {
                   if(typeof options.completeCallback=="function"){
                       options.completeCallback(object);
                   }
               }, function ( xhr ) {
                   if ( xhr.lengthComputable ) {
                       var percentComplete = xhr.loaded / xhr.total * 100;
                       if(typeof options.progress =="function"){
                           options.progress( Math.round(percentComplete, 2));
                       }
                       //console.log( Math.round(percentComplete, 2) + '% downloaded' );
                   }
               }, function(error){

               } );

           });
       }
       function onWindowResize() {
           windowHalfX = window.innerWidth / 2;
           windowHalfY = window.innerHeight / 2;
           camera.aspect = window.innerWidth / window.innerHeight;
           camera.updateProjectionMatrix();
           renderer.setSize( window.innerWidth, window.innerHeight );
       }
       function onDocumentMouseMove( event ) {
           mouseX = ( event.clientX - windowHalfX ) / 2;
           mouseY = ( event.clientY - windowHalfY ) / 2;
       }
       function animate() {
           requestAnimationFrame( animate );
           render();
       }
       function render() {
//              camera.position.x += ( mouseX - camera.position.x ) ;
//              camera.position.y += ( mouseY - camera.position.y ) ;
           camera.lookAt( scene.position );
           renderer.render( scene, camera );

       }

   
</script>
   </body>
</html>


使用jsonLoader加载3D模型

threejs提供了对json格式的支持,相对obj格式的文件来说要小很多,可以提高模型的加载速度。 
将obj文件转为了json格式需要借助一个软件:blender以及THREEJS提供的插件io_three; 
软件下载地址:https://www.blender.org/ 
io_three插件在three代码包里的路径:three.js-master/utils/exporters/blender/addons

安装插件的方法

  • 将io_three 复制到blender安装目录下的addons目录中,如:D:\blender-2.77a-windows64\2.77\scripts\addons

  • 打开blender,点击菜单->用户设置,向下拖动保存用户设置按钮所在的区域,会显示所有的用户设置内容

  • 选择add-ons选项卡,在左侧搜索框输入three ,将右侧出现的结果勾选上,到此则已经启用该插件。

1470192336842.png

转换格式

  • 在blender中新建 一个项目,删除默认添加的正方体等对象

  • 切换回info菜单栏,点击文件->导入->*.obj,选择obj之后,blender中会显示模型

  • 点击文件->导出->Threejs(.json),保存为haven.json,存储到材质所在的路径。导出设置如下:

1470209341377.png

使用JsonLoader载入json

var loader = new THREE.JSONLoader();
loader.load( '../resource/haven/haven.json', function ( geometry, materials ) {
   var object = new THREE.Mesh( geometry, materials[0] );
   object.material.side = THREE.DoubleSide;
   object.material.emissive.r=0;
   object.material.emissive.g=0.01;
   object.material.emissive.b=0.05;
   object.material.transparent=true;
   //object.material.opacity=0;                        
   object.material.shading=THREE.SmoothShading;
   object.position.y = 0;
   object.position.z = 0;
   object.scale.x=1;
   object.scale.y=1;
   object.scale.z=1;
   scene.add(object);
})

这时我们看到的图像是这样的

1470209408071.png

是的,没错,blender只能将当前选中的单个模型导出json文件,我们使用的obj文件是由多个子模型构成的模型组合,需要对每个子模型单独导出为json文件,并在页面中使用jsonLoader单独载入。

本文转载于网易游戏FEG:http://feg.netease.com/archives/301.html

本文由 w3cmark_前端笔记 版权所有,转载时请注明出处。
注明出处格式:w3cmark (http://www.w3cmark.com/2016/threejs-mark-02.html)

分享到:

关注w3cmark
微信公众号 w3cmark_com