首个threejs项目-前端填坑指南

第一次使用threejs到实际项目中,开始的时候心情有点小激动,毕竟是第一次嘛,然而作着作着就感觉到这玩意水好深,满满的都是坑,填都填不过来。通过老板20天惨无人道的摧残,终于小有成就。

由于第一次搞这玩意,相对的遇到的问题也是大把的,让我来一一诉说一路上遇到的各类问题。

开发使用: C4D、Blender2.7五、[threejs-r72](http://threejs.org/)

万事开头难,第一个问题就是怎么才能把3d软件中作好的模型显示在浏览器中。

1、模型在软件中的导入与导出。

这个项目中涉及到单个模型和动画模型,而不一样模型的导入导出有差别,下面就告诉你们我是如何将坑填平的。

一、单个模型: 

由于本身不会使用3D软件建模,只能求助公司大神设计师来一块儿搞。刚开始的想法是直接用3d软件建模而后直接导出obj格式来用,而后设计师用C4D作好了一个测试模型,发现模型数量少的话网页的大小还能够接受,可是因为项目的模型数量比较多,而后粗算了一下模型总的大小,发现超出了预想,因此得另寻它法。

接着在网上搜索发现Blender这玩意,因为设计师对C4D情有独钟不会Blender软件,因此决定用C4D作好模型而后导出obj格式接着再导入到Blender里面,再经由Blender导出须要的格式。

由于是第一次倒腾这个软件,因此并不会导出。而后就在网上搜素了怎么用Blender导出的json、js文件。通过测试js文件导出比较大,最后果断选择json。

在软件中如何导入导出如图所示:html

图一 (左边为导入obj,右边导出json)


二、动画模型:

因为设计师出一个动画模型也没有这么快,就无法进行导出测试。因而看到threejs官网里有demo中使用的动画模型,我就拿过来进行测试,发现动画模型跟单个模型导出选择有差别而后发现更单个模型的导出有出入,通过反复的测试,获得导动画模型须要注意的几点,

      (1)选择好动画的帧数,若是没选择,导出的json文件会有空帧。而且文件也会相对增大。

       (2)选择好导出选项中Animation,通常就选择Morph Animation、Embed Animation选项。

单个模型以及动画模型导出选项以下图所示:web


图二 (左边为导出单个模型,右边导出动画模型)json



注:导出单个带材质模型须要在导出选项的时候须要在shading选项中选择Face Materials。

拿着设计师作好的动画模型导出json格式后碰到了一些问题,虽然json格式的大小相比obj格式的要小一点,不过项目中有人物的动画模型导出的json格式大小仍是太大。而后为了解决这个问题,跟设计师进行讨论,而后获得如下解决方案:

     (1)将模型的面和顶点在不影响正常显示的状况下进行删减

      (2)对动画模型的帧数、面、顶点也进行删减

通过反复的修改和测试终于将动画模型控制在500-1000KB左右,单个模型控制在100K左右。

模型的导出问题解决了而后是对模型进行导入到页面中去。

2、模型加载到页面

在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。来解决多个动画问题(若有更好的办法求大神指导)。

至此将模型放到页面中的准备工做都作完了。接着就是模型上的事件与动画,模型上的各类事件整的头都大了,然而到如今我仍是有一些东西没有弄清楚原理还得继续研究。less

3、模型的圆周运动

刚开始项目中有个需求就是进入页面中模型须要作一个圆周运动,圆周运动之前在数学中学过,可是一直没用因此就忘了,而后就在网上找有关圆周运动计算的方法。这里不作过多的解释,用下面一张图来完整解释怎么来计算圆周运动。ide

图四 (圆周运动计算)工具

在页面中测试的结果以下图所示:测试


图五 (圆周运动测试结果)动画


功能代码以下:

         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轴旋转就能够了。

4、全部模型在空间里的位置


总体的运动效果描绘出来了,接着就是开始实施了。接着遇到了一个算是比较坑的问题。那就是模型在空间位置的确认了,因为对3D场景的不熟悉,将全部模型摆出一个球体就有点困难了。只能求助设计大神了,而后他在C4D中将全部模型摆成一个球体以后,而后像操做模型同样导了一份obj给我。而后我利用Blender打开(以下图六所示),而后我看了下,每一个模型在软件中都存在一个x,y,z值,我抱着侥幸的内心把全部模型的x,y,z记录下来而后填到页面中。最后发现球体的形状出来了,只是距离有点差别,接着想了个投机取巧的办法把全部的x,y,z进行等比的放大缩小,改完效果还不错。最后就是拉着设计师疯狂调整细节方面的问题。效果以下图七所示。


图六 (球体模型)

图七 (页面中渲染效果)


页面的基本样子出来了,剩下的就是页面的交互了,整个难点基本都在这里了。

项目需求:在页面中选中模型,而后选择模型如今在屏幕中间,而后用手指进行360度滑动,点击关闭按钮回到原型原有的位置。

思路:选中模型-->移动某个东西-->绑定旋转事件-->回位。

能够说项目大部分的时间都花在实现这个操做过程当中。下面就简单说一下我是怎么去填这些坑的。

5、在页面中选中模型

以后的全部操做都要基于这个模型去作,全部第一步就须要选中这个模型。这个跟之前作的彻底不一样,而后在官网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

模型选中以后而后开始下一步。

6、相机的移动

由于要让选中的模型显示在屏幕中间,因而想了两种方案:

    (1)改变选中物体的x,y,z值


           通过反复的测试发现改变x,y,z值,模型会在空间中乱窜,把握很差位置。因而就思考其它的方案。


     (2)移动相机

             移动相机这一块被坑了无数次,由于刚开始对这个相机的原理不是很清楚,就随意试了几个值(能够利用threejs提供的相机辅助线来操做具体参考:http://threejs.org/docs/index.html#Reference/Extras.Helpers/CameraHelper),来证实本身的方向是正确的,发现模型确实出如今不一样的位置了,而后就继续往下深挖。首先在google中寻找有关threejs中相机的原理,具体参考:http://www.flowers1225.com/lessons/2015/12/08/1,对原理有必定了解以后,而后就想怎么让相机出如今本身想要的位置上。

首先获取到选中模型的世界坐标,而后再根据当前的坐标值改变相机的x,y,z让相机直接照在当前模型上。测试了一下发现选中模型出如今屏幕中间,不过根据模型的不一样位置上有误差,这个后来设置了默认值来修正误差。效果下图所示:


图八 (相机移动)


功能实现了,而后发现选择以后出现的太忽然了,没有体现相机移动的效果,而后就想到使用TweenMax这个动画库来实现平滑过渡的动画。代码以下:

复制代码
    TweenMax.to(camera.position, 1, {
         x: x,
         y: y,
         z: z,
         ease:Expo.easeInOut,
         onComplete: function (){}
    })
复制代码

加上以后效果以下图所示:



图九 (相机平滑移动)  

模型已经放大显示在屏幕中,而后点击关闭按钮让模型回到原位这个就直接用TweenMax将相机的position值设置为初始值就能够了。

接下来就是开发用手指操做模型旋转的功能了。


7、操做模型旋转

这个功能我卡了很久,里面涉及到数学中的矩阵、四元数、欧拉角、向量乘积、轴-角啥的,彻底都忘了。在二维中旋转能够经过角度来控制,而在三维空间中须要经过四元数或者矩阵来实现,万般无奈只能求助万能的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源码

8、 灯光

最后就是灯光的控制,由于NBA的主色调是红蓝色,设计大神就想模型在页面中有红蓝光打在模型上的感受,因而照着这个方向,他开始在软件中调试灯光,调整好以后我按照设计师在软件中调整好的位置摆放灯光,发现跟预想的有点差别,页面中的颜色显的太深了,因而在整个空间中加上了一个白色的全局光来提亮总体的亮度,而后对灯光进行反复的调整,就到了如今页面中呈现的样子了。灯光调整的过程如图所示:


图十二 (灯光调整过程)

注: threejs提供不少种灯光具体能够在threejs文档中查看http://threejs.org/docs/index.html#Reference/Lights,而灯光调整能够借助threejs提供的辅助线来调整,THREE.PointLightHelper()、THREE.SpotLightHelper()等。这样有助于迅速定位到问题。最后就是手机兼容性的测试了(由于是微信的活动页,因此其它浏览器未测试)。在iPhone下总体体验较好,在Android下使用r71版本发现模型会出现菱角不分明的状况如图三所示,以后改用r72版本,用高版本的Android测试发现问题解决了,而后拿我本身的mx2测试时出现另一个问题,直接卡在加载页面进不去页面,而后经过调试工具发如今控制台中有方法报错(小米2也是同等状况),倒腾了很久,然而并无什么卵用,由于是threejs内部报错,无奈只能放大招,作一个Android版原本解决这个问题。

相关文章
相关标签/搜索