您好,欢迎来到意榕旅游网。
搜索
您的当前位置:首页threejs使用各种坑实验过程

threejs使用各种坑实验过程

来源:意榕旅游网
threejs使⽤各种坑实验过程

第⼀次使⽤threejs到实际项⽬中,开始的时候⼼情有点⼩激动,毕竟是第⼀次嘛,然⽽做着做着就感受到这玩意⽔好深,满满的都是坑,填都填不过来。经过⽼板20天惨⽆⼈道的摧残,终于⼩有成就。

因为第⼀次搞这玩意,相对的遇到的问题也是⼤把的,让我来⼀⼀诉说⼀路上遇到的各种问题。开发使⽤: C4D、Blender2.75、[threejs-r72](http://threejs.org/)

万事开头难,第⼀个问题就是怎么才能把3d软件中做好的模型显⽰在浏览器中。⼀、模型在软件中的导⼊与导出。

这个项⽬中涉及到单个模型和动画模型,⽽不同模型的导⼊导出有差异,下⾯就告诉⼤家我是如何将坑填平的。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提供了各种模型的加载⽅法具体可去查询。

虽然解决了模型加载和⾯的问题,但是模型在⽹页的表现与软件中渲染的差别太⼤。刚开始以为是模型⽅⾯以及导⼊导出的⽅式不对,于是和设计师进⾏各种修改然后反复的测试,发现没什么变化。然后求助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; }

参考:

模型选中之后然后开始下⼀步。

六、相机的移动

因为要让选中的模型显⽰在屏幕中间,于是想了两种⽅案: (1)改变选中物体的x,y,z值

经过反复的测试发现改变x,y,z值,模型会在空间中乱窜,把握不好位置。于是就思考其它的⽅案。 (2)移动相机

移动相机这⼀块被坑了⽆数次,因为刚开始对这个相机的原理不是很清楚,就随意试了⼏个值(可以利⽤threejs提供的相机辅助线来操作具体参考:),来证明⾃⼰的⽅向是正确的,发现模型确实出现在不同的位置了,然后就继续往下深挖。⾸先在google中寻找有关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有帮助。如下图所⽰:

图⼗⼀ (外层包裹框)

虽然旋转的效果做出来了,但是旋转⾥⾯涉及的东西还有⼀些理解的不是很清楚,还需要继续深⼊研究,等我研究透彻了再重新整理⼀下(还望⼤神指点⼀下)。参考:、、TrackballControls.js源码⼋、 灯光

最后就是灯光的控制,因为NBA的主⾊调是红蓝⾊,设计⼤神就想模型在页⾯中有红蓝光打在模型上的感觉,于是照着这个⽅向,他开始在软件中调试灯光,调整好之后我按照设计师在软件中调整好的位置摆放灯光,发现跟预想的有点差异,页⾯中的颜⾊显的太深了,于是在整个空间中加上了⼀个⽩⾊的全局光来提亮整体的亮度,然后对灯光进⾏反复的调整,就到了现在页⾯中呈现的样⼦了。灯光调整的过程如图所⽰:

图⼗⼆ (灯光调整过程)

注: threejs提供很多种灯光具体可以在threejs⽂档中查看,⽽灯光调整可以借助threejs提供的辅助线来调整,THREE.PointLightHelper()、THREE.SpotLightHelper()等。这样有助于迅速定位到问题。

最后就是⼿机兼容性的测试了(因为是微信的活动页,所以其它浏览器未测试)。在iPhone下整体体验较好,在Android下使⽤r71版本发现模型会出现菱⾓不分明的情况

如图三所⽰,之后改⽤r72版本,⽤⾼版本的Android测试发现问题解决了,然后拿我⾃⼰的mx2测试时出现另外⼀个问题,直接卡在加载页⾯进不去页⾯,然后通过调试⼯具发现在控制台中有⽅法报错(⼩⽶2也是同等情况),倒腾了好久,然⽽并没有什么卵⽤,因为是threejs内部报错,⽆奈只能放⼤招,做⼀个Android版本来解决这个问题。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- yrrf.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务