模型在软件中的导入与导出。这个项目中涉及到单个模型和动画模型,而不同模型的导入导出有差异下面就告诉大家我是如何将坑填平的。1、单个模型因为自己不会使用3D软件建模只能求助公司大神设计师来一起搞。刚开始的想法是直接用3d软件建模然后直接导出obj格式来用然后设计师用C4D做好了一个测试模型发现模型数量少的话网页的大小还可以接受但是由于项目的模型数量比较多然后粗算了一下模型总的大小发现超出了预想所以得另寻它法。接着在网上搜索发现Blender这玩意由于设计师对C4D情有独钟不会Blender软件所以决定用C4D做好模型然后导出obj格式接着再导入到Blender里面再经由Blender导出需要的格式。因为是第一次倒腾这个软件所以并不会导出。然后就在网上搜素了怎么用Blender导出的json、js文件。经过测试js文件导出比较大最后果断选择json。在软件中如何导入导出如图所示图一 (左边为导入obj右边导出json)2、动画模型由于设计师出一个动画模型也没有这么快就没法进行导出测试。于是看到threejs官网里有demo中使用的动画模型我就拿过来进行测试发现动画模型跟单个模型导出选择有差异然后发现更单个模型的导出有出入经过反复的测试得到导动画模型需要注意的几点1选择好动画的帧数如果没选择导出的json文件会有空帧。并且文件也会相对增大。2选择好导出选项中Animation一般就选择Morph Animation、Embed Animation选项。单个模型以及动画模型导出选项如下图所示图二 (左边为导出单个模型右边导出动画模型)注导出单个带材质模型需要在导出选项的时候需要在shading选项中选择Face Materials。拿着设计师做好的动画模型导出json格式后碰到了一些问题虽然json格式的大小相比obj格式的要小一点不过项目中有人物的动画模型导出的json格式大小还是太大。然后为了解决这个问题跟设计师进行讨论然后得到以下解决方案1将模型的面和顶点在不影响正常显示的情况下进行删减2对动画模型的帧数、面、顶点也进行删减经过反复的修改和测试终于将动画模型控制在500-1000KB左右单个模型控制在100K左右。模型的导出问题解决了然后是对模型进行导入到页面中去。二、模型加载到页面在threejs官网上看到利用obj格式加载的demo比较多所以就直接使用的是obj的格式模型进行加载根据demo利用THREE.OBJLoader()、THREE.OBJMTLLoader()进行加载。然后设计大神给了我一个带材质的模型让我进行测试发现两问题1模型材质丢失2模型的大小太大模型量少大小还可以接受考虑到此次项目中的模型量多估算了一下大概有70-80M左右由于模型大小太大所以放弃。所以改选用THREE.JSONLoader()进行加载。在这一步由于是直接导出带材质的json格式材质对应到模型的各个面是一个问题。然后在官网的demo上看到THREE.MeshFaceMaterial()方法,查看了一下文档然后迅速解决这个了这个问题。另外threejs提供了各种模型的加载方法具体可去threejs.org查询。虽然解决了模型加载和面的问题但是模型在网页的表现与软件中渲染的差别太大。刚开始以为是模型方面以及导入导出的方式不对于是和设计师进行各种修改然后反复的测试发现没什么变化。然后求助stackoverflow找到了答案。是由于模型的shading的原因造成的然后根据网上提供的解决方案在材质中加上materials.shading THREE.FlatShading来解决。不过有些Android手机上会出现材质无法解析的错误。而且在r72的版本中MeshLambertMaterial已经移除了shading这个属性。下图就是在网页中渲染的结果对比。图三 (左边为未加shading右边加了shading)静态模型的导入没有问题了然后是动画模型的导入参考官网demo直接套用基本的动画没有太大问题只是项目中有一个人物运球的动画模型比较难折腾刚开始的时候直接是按照动画模型的导出直接导然后同步到页面中发现只能导出一个动画另外一个丢失了定位了一下问题好像是Blender不能同时导出多个动画具体是不是待研究。最后想了一个办法就是采取分开导出再创建一个obj包裹两个动画操作这个obj。来解决多个动画问题如有更好的办法求大神指导。至此将模型放到页面中的准备工作都做完了。接着就是模型上的事件与动画模型上的各种事件整的头都大了然而到现在我还是有一些东西没有弄清楚原理还得继续研究。三、模型的圆周运动刚开始项目中有个需求就是进入页面中模型需要做一个圆周运动圆周运动以前在数学中学过但是一直没用所以就忘了然后就在网上找有关圆周运动计算的方法。这里不做过多的解释用下面一张图来完整解释怎么来计算圆周运动。图四 (圆周运动计算)在页面中测试的结果如下图所示图五 (圆周运动测试结果)功能代码如下var clock new THREE.Clock(); //时间跟踪 //圆周运动 var time clock.getElapsedTime() * 1; loadMesh.position.x Math.cos( time ) * 10; loadMesh.position.y Math.sin( time ) * 10;老板看了一下然后脑补一下整个页面的效果说还是不要这样子很多模型都这样运动的话画面太乱了最后决定的是简单点直接把所有模型摆放成一个球体形状然后模型不单独运动而是整体绕中心转这个实现起来比较简单思路是直接设置外层模型y轴旋转就可以了。四、所有模型在空间里的位置整体的运动效果描绘出来了接着就是开始实施了。接着遇到了一个算是比较坑的问题。那就是模型在空间位置的确认了由于对3D场景的不熟悉将所有模型摆出一个球体就有点困难了。只能求助设计大神了然后他在C4D中将所有模型摆成一个球体之后然后像操作模型一样导了一份obj给我。然后我利用Blender打开如下图六所示然后我看了下每个模型在软件中都存在一个x,y,z值我抱着侥幸的心里把所有模型的x,y,z记录下来然后填到页面中。最后发现球体的形状出来了只是距离有点差异接着想了个投机取巧的办法把所有的x,y,z进行等比的放大缩小改完效果还不错。最后就是拉着设计师疯狂调整细节方面的问题。效果如下图七所示。图六 (球体模型)图七 (页面中渲染效果)页面的基本样子出来了剩下的就是页面的交互了整个难点基本都在这里了。项目需求在页面中选中模型然后选择模型现在在屏幕中间然后用手指进行360度滑动点击关闭按钮回到原型原有的位置。思路选中模型--移动某个东西--绑定旋转事件--回位。可以说项目大部分的时间都花在实现这个操作过程中。下面就简单说一下我是怎么去填这些坑的。五、在页面中选中模型之后的所有操作都要基于这个模型去做所有第一步就需要选中这个模型。这个跟以前做的完全不同然后在官网demo和stackoverflow游荡因为涉及到屏幕坐标和世界坐标这个概念有种完全懵逼的感觉。还好官网上有demo的支持参考了demo之后发现首先获取屏幕坐标的x,y然后想办法转换成向量接着标准化向量通过raycaster.intersectObjects的检测来获取选中的模型。功能代码如下mouse.x ( e.clientX / window.innerWidth ) * 2 - 1; //鼠标的x到屏幕y轴的距离与屏幕宽的一半的比值 绝对值不超过1 mouse.y - ( e.clientY / window.innerHeight ) * 2 1; //鼠标的y到屏幕x轴的距离与屏幕宽的一半的比值 绝对值不超过1 //新建一个三维变换半单位向量 假设z方向就是0.5,这样左右移的时候还会有前后移的效果 //屏幕和场景转换工具根据照相机把这个向量从屏幕转化为场景中的向量 var vector new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject( camera ); //变换过后的向量vector减去相机的位置向量后标准化 var raycaster new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() ); //新建一条从相机的位置到vector向量的一道光线 var intersects raycaster.intersectObjects( objects ); if ( intersects.length 0 ) { //把选中的对象放到全局变量SELECTED中 SELECTED intersects[ 0 ].object; }参考 http://threejs.org/examples/webgl_octree_raycasting.html模型选中之后然后开始下一步。六、相机的移动因为要让选中的模型显示在屏幕中间于是想了两种方案1改变选中物体的x,y,z值经过反复的测试发现改变x,y,z值模型会在空间中乱窜把握不好位置。于是就思考其它的方案。2移动相机移动相机这一块被坑了无数次因为刚开始对这个相机的原理不是很清楚就随意试了几个值(可以利用threejs提供的相机辅助线来操作具体参考three.js docs)来证明自己的方向是正确的发现模型确实出现在不同的位置了然后就继续往下深挖。首先在google中寻找有关threejs中相机的原理具体参考threejs学习一对原理有一定了解之后然后就想怎么让相机出现在自己想要的位置上。首先获取到选中模型的世界坐标然后再根据当前的坐标值改变相机的x,y,z让相机直接照在当前模型上。测试了一下发现选中模型出现在屏幕中间不过根据模型的不同位置上有偏差这个后来设置了默认值来修正偏差。效果下图所示图八 (相机移动)功能实现了然后发现选择之后出现的太突然了没有体现相机移动的效果然后就想到使用TweenMax这个动画库来实现平滑过渡的动画。代码如下TweenMax.to(camera.position, 1, { x: x, y: y, z: z, ease:Expo.easeInOut, onComplete: function (){} })加上之后效果如下图所示图九 (相机平滑移动)模型已经放大显示在屏幕中然后点击关闭按钮让模型回到原位这个就直接用TweenMax将相机的position值设置为初始值就可以了。接下来就是开发用手指操作模型旋转的功能了。七、操作模型旋转这个功能我卡了好久里面涉及到数学中的矩阵、四元数、欧拉角、向量乘积、轴-角啥的完全都忘了。在二维中旋转可以通过角度来控制而在三维空间中需要通过四元数或者矩阵来实现万般无奈只能求助万能的google来了解怎么在三维空间中对物体进行旋转操作。 通过了解在3D中表现旋转有三种方法矩阵、欧拉角、四元组。 最后选用四元组来实现旋转的方法简单点说原因就是四元组的是围绕一个轴来做旋转而且在threejs中也提供了THREE.Quaternion()方法然后在threejs的包中找到了一些写好的鼠标控制的类TrackballControls.js、OrbitControls.js等然后参考着源码将方法虽然写出来了但是有时候操作起来会有意向不到的bug所以里面的细节还有待深挖。下面简述一下旋转的思路。首先四元数控制旋转需要的是一个旋转轴和一个旋转弧度直接上图清楚明了图十然后就想办法得到这个两个东西,接着开始想怎么弄到旋转弧度首先获取到点击开始和结束的x,y值然后得到两个向量之间的夹角得到一个弧度然后在设置一个默认的旋转系数两者相乘得到弧度。接着通过开始和结束的向量乘积得到旋转轴最后通过setFromAxisAngle(axis, angle)得到旋转四元数。核心代码如下function rotateMatrix(rotateStart, rotateEnd){ var axis new THREE.Vector3(), quaternion new THREE.Quaternion(); //得到开始和结束向量间的夹角 var angle Math.acos(rotateStart.dot(rotateEnd) / rotateStart.length() / rotateEnd.length()); if (angle){ //如果夹角等于0 说明物体没有旋转 axis.crossVectors(rotateStart, rotateEnd).normalize(); //rotateStart,rotateEnd向量乘积 标准化 得到旋转轴 angle * _that.rotationSpeed; //rotationSpeed旋转系数 得到旋转弧度 quaternion.setFromAxisAngle(axis, angle); //从一个旋转轴和旋转弧度得到四元组 如果要让物体相反方向旋转 设置angle为负 } return quaternion; //返回一个旋转的四元数 } this.handleRotation function(object){ _that.rotateEndPoint _that.projectOnTrackball(_that.deltaX, _that.deltaY); var rotateQuaternion rotateMatrix(_that.rotateStartPoint, _that.rotateEndPoint); var curQuaternion object.quaternion; curQuaternion.multiplyQuaternions(rotateQuaternion, curQuaternion); //设置四元组 a x b curQuaternion.normalize(); object.setRotationFromQuaternion(curQuaternion); //方法通过规范化的旋转四元数直接应用旋转 参数必须normalize() };在这里有个小坑就是所有模型的外观大小不同当旋转的时候可能会出现误操作然后用了一个小技巧就是用一个透明的方体包裹模型这样做就相当于旋转一个cube了而且设置方体有网格时对排除bug有帮助。如下图所示图十一 (外层包裹框)虽然旋转的效果做出来了但是旋转里面涉及的东西还有一些理解的不是很清楚还需要继续深入研究等我研究透彻了再重新整理一下(还望大神指点一下)。参考四元数旋转、cube旋转、TrackballControls.js源码八、 灯光最后就是灯光的控制因为NBA的主色调是红蓝色设计大神就想模型在页面中有红蓝光打在模型上的感觉于是照着这个方向他开始在软件中调试灯光调整好之后我按照设计师在软件中调整好的位置摆放灯光发现跟预想的有点差异页面中的颜色显的太深了于是在整个空间中加上了一个白色的全局光来提亮整体的亮度然后对灯光进行反复的调整就到了现在页面中呈现的样子了。灯光调整的过程如图所示图十二 (灯光调整过程)注 threejs提供很多种灯光具体可以在threejs文档中查看three.js docs而灯光调整可以借助threejs提供的辅助线来调整THREE.PointLightHelper()、THREE.SpotLightHelper()等。这样有助于迅速定位到问题。最后就是手机兼容性的测试了(因为是微信的活动页所以其它浏览器未测试)。在iPhone下整体体验较好在Android下使用r71版本发现模型会出现菱角不分明的情况如图三所示之后改用r72版本用高版本的Android测试发现问题解决了然后拿我自己的mx2测试时出现另外一个问题直接卡在加载页面进不去页面然后通过调试工具发现在控制台中有方法报错(小米2也是同等情况)倒腾了好久然而并没有什么卵用因为是threejs内部报错无奈只能放大招做一个Android版本来解决这个问题。最后附上体验地址页面中意想不到的bug肯定是还有的扫码体验第一次在实战中使用threejs开发还有很多的不足需要弥补希望下一次能做的更好。自己也继续的在webgl的坑中挣扎挣扎挣扎(重要的要说三遍)还望有大神指点迷津附上公司大神设计师的站酷wen0创作者主页_深圳设计爱好者-站酷ZCOOL另外附上一些自己在填坑中找到的对3D学习有帮助的网站、博客、书籍各种计算 Mathematics - Martin Baker许多有趣的东西 アーカイブコンピュータ・シミュレーション講座NPO法人 natural science大叔很厉害的 http://learningthreejs.com/threejs源码注释 https://github.com/omni360/three.js.sourcecode关于threejs的书籍只有英文原版的《Three.Js Essentials》、《threejs-cookbook》、《Learning Three.js》免责声明本内容来自平台创作者博客园系信息发布平台仅提供信息存储空间服务。好文要顶 关注我 收藏该文 微信分享At丶Sea粉丝 - 49 关注 - 0加关注370升级成为会员» 下一篇 Phaser-游戏之旅posted 2016-02-29 11:08 At丶Sea 阅读(94318) 评论(40) 收藏 举报刷新页面返回顶部