#ThreeJS学习笔记( 二)——导入外部模型
外部模型格式
Threejs支持了许多格式的3D模型导入,包括*.obj、 *.sea、*.3mf 、*.amf、*.sea、*.pmd、*.json等。
这里主要讲解一下obj模型的导入,及将obj文件转成文件更小的json格式导入。
导入obj模型
3Dmax格式转换成obj格式
美术提供的一般为3Dmax项目文件夹,里面包含了.max文件以及贴图图片等资源,用3DMAX打开.max文件可以看到3D模型
如图:
- 点击菜单里的导出,选择obj格式,点击保存后出现选项
- 导出比例:几何体选项里的输出比例,默认是1.0,我们根据模型的分辨率,以及需要在网页上呈现的分辨率设置一下比例输出即可。简单的说,你直接按1.0比例输出的如果在浏览器看的时候大小差距太大了,可以在这里导出比例设置一下,另外也可以设置mesh的scale,直接缩放。这个模型需要将比例设置为0.1。几何体的其他选项都不需要修改。
- 材质:点击材质导出,勾选使用材质路径,选择保存材质的路径。格式为jpg,格式设置中可以设置导出jpg的质量,无特殊要求默认即可,并勾选转换位图,关闭材质设置。
- 点击导出,导出过程有可能会提示找不到材质,设置材质路径为美术提供的文件夹目录即可
- 最后会得到一个*.obj文件、一个*mtl文件、若干材质图,包括多个不同通道的贴图jpg和法线贴图jpg
导出obj后的二次处理
导出后的文件如图所示
其中,test.mtl文件定义了test.obj模型与各贴图间的对应关系。
由于示例中的模型是由多个子模型组成的模型组,同时设置了多通道贴图,每一个子模型都对应四张贴图。
打开mtl文件,我们可以看到每个子模型都有一组贴图数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 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则指定了法线贴图:
在使用threejs的mtlLoader解析mtl贴图并渲染为模型材质时,threejs仅调用map_Ka通道图片和map_bump法线贴图进行材质渲染。而基于图片数量及文件大小的控制,项目中采用的方法是让设计师将 JZ2012_jz_d.jpg、JZ2012_jz_s.jpg、JZ2012_jz_e.jpg三张图合成并调色后作为map_Ka通道贴图:
注:目前未查到threeJS 的mtl_loader是否能自动载入多通道贴图,待研究。
另外由于自动导出的法线贴图名称中带有中文,需要将mtl中map_bump和对应的法向贴图名称改成非中文命名。
在做以上图片处理时,需要同时将图片资源做压缩处理,以免资源加载时间过长。
使用mtlLoader和objLoaer加载obj模型及贴图
加载带mtl材质的obj模型,需要先定义mtl对象并加载mtl之外再加载obj模型。此时需要引用threejs两个js库
1 2 | < 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){})来对每个子模型进行处理。
以下简单封装成一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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){ }); }); } |
调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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+ "%" ); } }) |
此时如果在之前没有添加光源,在浏览器上看到的可能是一片黑色,通过增加光源可以将物体显现出来。比例添加一个全局光
1 2 | var ambient = new THREE.AmbientLight( 0xffffff ); scene.add( ambient ); |
此时在浏览器看到是这样的效果:
导入obj模型的后期渲染
如上图看到,我们导入的模型经过调色虽然比3Dmax显示效果好些,但仍然一片灰蒙蒙,丑不拉叽的。所以后期需要适当添加各种光源来渲染出更好的效果。
添加一个平行光做为主光源营造反射光面,并添加三个点光源对暗部适当补光后效果如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 ); |
接下再添加一个4000*4000*4000的天空盒子作为背景,并适当调整camera的可视范围,最后显示结果如下:
在设置点光源位置时可借助Threejs提供的helper来查看设置的点光源位置,方便调整光源位置
1 2 3 | scene.add( new THREE.PointLightHelper( pointlight3 ) ); scene.add( new THREE.PointLightHelper( pointlight2 ) ); scene.add( new THREE.PointLightHelper( pointlight ) ); |
DEMO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | <! 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 ,将右侧出现的结果勾选上,到此则已经启用该插件。
转换格式
- 在blender中新建 一个项目,删除默认添加的正方体等对象
- 切换回info菜单栏,点击
文件->导入->*.obj
,选择obj之后,blender中会显示模型- 点击
文件->导出->Threejs(.json)
,保存为haven.json,存储到材质所在的路径。导出设置如下:
使用JsonLoader载入json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); }) |
这时我们看到的图像是这样的
是的,没错,blender只能将当前选中的单个模型导出json文件
,我们使用的obj文件是由多个子模型构成的模型组合,需要对每个子模型单独导出为json文件,并在页面中使用jsonLoader单独载入。
手机阅读请扫描下方二维码:
有在线预览地址吗? 我教程走 根本没用啊……
报错
Uncaught TypeError: Cannot read property 'visible' of undefined
at p (three.min.js:134)
at p (three.min.js:134)
at Xd.render (three.min.js:173)
at render (main.js?v=fsd:195)
at animate (main.js?v=fsd:189)
我的代码里没有设置visible属性的,看一下是否你自己的代码里main属性里的animate方法里的脚本是否有多余的设置
咨询一个问题,向blender导入文件的时候,为什么就只导入模型,没有图片。这样模型没有皮肤。很奇怪。
[...]http://feg.netease.com/archives/301.html[...]
test
你好,我是北京网易传媒的,有个3dmax方面的问题请教一下,你们3dmax贴图的材质球是用的默认扫描线渲染器下的材质球吗?但是看你们的mtl文件中的代码,是newmtl,这个应该是mental rey或v-rey渲染器下的名字,我用这两个渲染器下的材质球,导出obj时,贴图导不出来,只能在默认扫描线渲染器下的材质球导出obj才能导出贴图。这是我遇到的问题以及总结http://jingyan.baidu.com/article/fec7a1e5d9e7011190b4e7b6.html,我觉得您是用插件渲染器导出的贴图,求指教,谢谢啦!
找到答案了,已经解决!
3Dmax是由美术同学提供的。具体用哪个渲染器我就不知道了
找到什么答案可以分享么~最近也踩了你说的坑
天啦噜,找到答案了也不说一下,还出来炫耀一下。。。
这个问题,obj本身不带贴图,需要mtl同时导入,另外导出obj+mtl时,如果贴图路径没设置正确,可以用文本编辑器打开mtl文件,把图片路径修改为项目的相对路径。
单独导入obj是没有贴图的。如果只有obj可以用threejs的textureload导入贴图,并在导入obj后, 把obj的mesh.materital设置为导入的贴图。
您好,我导出obj格式后,没有导出贴图文件,mtl文件中也没有贴图路径是为什么呢
你好,请问将单独的obj文件导入threejs中,贴图的时候只会改变obj 的颜色而看不到实际的纹理,可能在那些方面出问题?
我的代码里没有设置visible属性的,看一下是否你自己的代码里main属性里的animate方法里的脚本是否有多余的设置
能否给demo包,新手求老司机领路
直接上threejs.org上找就可以了
加载obj的是这个 demo
https://threejs.org/examples/?q=obj#webgl_loader_obj_mtl
能否给个demo包,新手求老司机带路
直接上threejs.org上找就可以了
加载obj的是这个 demo
https://threejs.org/examples/?q=obj#webgl_loader_obj_mtl
想请教一个问题,three.js 导入 Obj 时,如果 Obj 本身是带有分组的,会把分组下的原件重复添加进去。比如在3D建模中,Room作为一个模型,编组下面有 Desk 对象。那么 three.js 导入 obj 后,Desk 这个对象会被导入两次,生成一个 Desk_1。这个怎么解决?如果不在建模时分组,那么我又怎么样在导入 Obj 之后把 Desk 和 Room 重新编组呢?例如 Room.add(Desk); 这样的形式。希望有时间的话可以回复,谢谢。
你这个room应该只是一个group,你可以只循环mesh添加,或者只添加group,这样就只会导入一次,重新编组,可以var g=new THREE.Group(); g.add(mesh1);g.add(mesh2),这样子,mesh1 和mesh2的position参考系就以g的中心点为原点。
问一下,max2012导出obj包含贴图并转换格式之后,如dds转tga。dds转xxx,导出的贴图都变暗了。用2010的倒是没问题。另外 使用obj导出的贴图不能带通道吗?源贴图是有通道的,导出之后通道就消失了?
我之前也遇过这个问题,后来发现threejs只贴了一个通道的图,最后我只能把所有的通道都合并成一张图,或者使用lightMAP来贴光影图。
用ligthMAP还需要把UV展开,搞2层uv,在原来的uv上贴上默认贴图,在uv2上贴光影图。而obj不支持2UV,只能blender加载包含2uv的模型,然后导出带2uv信息的json,再用threejs导入json。
你好,请问下如果我要加载不同的obj模型,建模出来的obj模型大小位置都不统一,这时候不能把camera的位置写死吧,该如何做到设置camera位置适配不同的模型啊
建议调整模型的position 和scale 来统一,而不调整camera。调camera的位置更难统一视觉效果。。
好厉害呀,学习一下
好文
asd
非常感谢
你好,我最近刚学threejs,在加载了包含贴图的mtl时爆了错误,Handlers.get() has been removed. Use LoadingManager.getHandler() instead.和这个错误Cannot read property 'setCrossOrigin' of undefined,这个错误是什么原因导致的呢?我怀疑是贴图路径的原因,但是路径我已经改为英文的了,还是报这个错误,有哪位大佬能帮我解惑
我也遇到了这个问题 求问怎么解决
1

1

1

1

1
