这段时间加班累成狗,终于尾声了,水一篇提升一下百度活跃度。
点击 预览 先看一下效果。
获取模型 使用常规的 3D 建模软件即可。拿到 3D 模型之后再进行处理,处理的软件使用的是Blender 兼容性比较好的是 fbx 格式,然后通过给 Blender 安装插件之后,将模型转换为便于 three.js 读取的 JSON 格式。当然我并不会建模,所以直接从UP2017 腾讯互动娱乐年度发布会 - 腾讯互动娱乐 上直接把粒子模型“偷”下来:
初始工作 首先,初始化 threejs 三大元素:场景,相机,渲染器。我们需要一个用于切换的载体粒子体系和多个环境粒子体系(为了简单,在这只初始化了一个上下转动的环境粒子体系)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 camera = new THREE .PerspectiveCamera ( 105 , window .innerWidth / window .innerHeight , 300 , 10000 ); camera.position .z = 750 ; scene = new THREE .Scene (); scene.fog = new THREE .FogExp2 (0x05050c , 0.0005 ); renderer = new THREE .WebGLRenderer (); renderer.setPixelRatio (window .devicePixelRatio ); renderer.setSize (window .innerWidth , window .innerHeight ); container.appendChild (renderer.domElement ); geometry = new THREE .Geometry (); around = new THREE .Geometry (); const textureLoader = new THREE .TextureLoader ();const mapDot = textureLoader.load ("assets/gradient.png" );
初始化载体粒子体系:载体粒子体系的粒子数量要比所有模型的顶点数量的最大值还要大,这样才能保证切换到每一个模型,都不会出现缺失的情况,而多余的点呢就让他们从头开始重叠好了。当然不是越多越好,我的电脑是 MBP2018,20000 的时候就开始力不从心了,30000 直接无响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for (let i = 0 ; i < 15000 ; i++) { const vertex = new THREE .Vector3 (); vertex.x = 800 * Math .random () - 400 ; vertex.y = 800 * Math .random () - 400 ; vertex.z = 800 * Math .random () - 400 ; geometry.vertices .push (vertex); geometry.colors .push (new THREE .Color (255 , 255 , 255 )); } material = new THREE .PointsMaterial ({ size : 4 , sizeAttenuation : true , color : 0xffffff , transparent : true , opacity : 1 , map : mapDot, }); material.vertexColors = THREE .VertexColors ; particles = new THREE .Points (geometry, material); scene.add (particles);
将获取到的 3D 模型,通过 JSONLoader 加载后,得到的 geometry 对象放入一个数组 glist 中,用于模型切换。
加载模型 loadObject:
新版的threejs 在r98 → r99
的时候废弃了JSONLoader
,模型在网站也比较久了,ObjectLoader
也不能加载,所以要么使用旧版的,要么单独下载JSONLoader
。
r98 → r99
WebGLRenderTarget.texture.generateMipmaps
is now set to false
by default.
There is a new (not backwards compatible) implementation for SSAOShader
and SSAOPass
.
JSONLoader
has been removed from core. It is now located in examples/js/loaders/deprecated/LegacyJSONLoader.js
.
Removed Geometry
support from ObjectLoader
. You have to include LegacyJSONLoader
if you still want to load geometry data of type Geometry
.
Removed Geometry
support from SkinnedMesh
. Use BufferGeometry
instead.
Removed SkinnedMesh.initBones()
. The SkinnedMesh
constructor does not build the bone hierarchy anymore. You have to do this by yourself and then call SkinnedMesh.bind() in order to bind the prepared skeleton.
在这引入最新的three.min.js
和LegacyJSONLoader
:
1 2 <script src ="./js/three.min.js" > </script > <script src ="./js/LegacyJSONLoader.js" > </script >
加载模型 loadObject:
1 2 3 4 5 6 7 8 const loader = new THREE .LegacyJSONLoader ();loader.load ("assets/qr.json" , function (geo, materials ) { geo.center (); geo.normalize (); geo.scale (800 , 800 , 800 ); glist.push (geo); });
添加页面事件监听
按住鼠标拖动,可以旋转场景中的粒子体系。
滚动鼠标滚轮,切换模型。
手机上点击页面切换模型。
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 function onDocumentMouseMove (event ) { if (!canMouseMove) { return false ; } geometry.rotateY (((event.pageX - mouseX) / 2000 ) * 2 * Math .PI ); geometry.rotateX (((event.pageY - mouseY) / 2000 ) * 2 * Math .PI ); event.preventDefault (); mouseX = event.pageX ; mouseY = event.pageY ; } function onDocumentMouseWheel (e ) { canMouseMove = false ; if (flag) { return false ; } e.deltaY > 0 ? objIndex++ : objIndex--; if (objIndex > 4 ) { objIndex = 0 ; } else if (objIndex < 0 ) { objIndex = 4 ; } tweenObj (objIndex); flag = true ; } function onDocumentTouchStart ( ) { canMouseMove = false ; if (flag) { return false ; } objIndex++; if (objIndex > 4 ) { objIndex = 0 ; } tweenObj (objIndex); flag = true ; }
切换动画 使用的tweenjs
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 function tweenObj (index ) { let ani = null ; geometry.vertices .forEach (function (e, i, arr ) { ani = new TWEEN .Tween (e); const length = glist[index].vertices .length ; const o = glist[index].vertices [i % length]; ani .to ( { x : o.x , y : o.y , z : o.z , }, 1000 ) .easing (TWEEN .Easing .Exponential .In ) .delay (400 * Math .random ()) .start (); }); ani.onComplete (function (params ) { canMouseMove = true ; flag = false ; }); }
使用tween.delay(animationDuration*Math.random());
是动画不那么生硬。
最后 渲染整个画面:
1 2 3 4 5 6 7 8 function render ( ) { around.rotateX (Math .PI / 1000 ); TWEEN .update (); camera.lookAt (scene.position ); geometry.verticesNeedUpdate = true ; geometry.colorsNeedUpdate = true ; renderer.render (scene, camera); }
TWEEN.update()
和geometry.verticesNeedUpdate = true
共同决定了粒子体系切换动画可以展示出来。
GitHub 地址
总结 还有很多地方不完善:
切换时旋转画面会导致粒子位置计算错误,撕裂模型,暂时在切换时禁止了旋转事件。
雾化的位置和原版有很大差异,原版使用了三方的composer
,这里用的是alteredq 的一系列EffectComposer
,包括过亮效果、暗角、电视效果等。
KV
的动画没有实现裙边的效果。
二维码的材质还是点,所以扫不出来,可以把材质的map
设置为null
即可是方形(没有纹理默认方形)。
1 2 3 4 5 new THREE .PointsMaterial ({ ... map : null , ... });
流畅度方面,使用vertex shader
性能可能更好,感觉好高深,等大神踩踩坑。
参考链接:
threejs+tweenjs 实现 3D 粒子模型切换 。
3D 粒子效果在网页端实现分享 。