注册
web

终于把国外大佬的跨窗口量子纠缠粒子效果给肝出来

前言


上篇文章 尝试实现了国外大佬用Web做出来跨窗口渲染动画效果反响很大,但是仅仅只是实现了跨窗口动画效果,严格说就没有动画,还有些bug和遗憾,尤其是粒子效果,得入three.js坑,怎么办?跳啊!


硬肝了两天,实在肝不动了,看效果吧。


第一版v2效果,大粒子,粒子数量较少:
v2 (1).gif


第二版v2.1,小粒子,粒子数量多:
v2.1 (1).gif


three.js


官方文档:threejs.org/,中文文档:three.js docs


我第一次接触three.js,之前只是听说过,比如能弄车展啥的,感觉很厉害,就想借此机会学习下,跳进坑里才发现,这坑也太深了。随便找个教程,里面各种名词就给我弄吐了。


先按文档 three.js docs 画个立方体,跑起来了,但是我想要球体啊,还有粒子围着中心转,这么多api学不起啊,搜教程也是杂乱无章,无从学起,咋整?找现成的啊!


看到官网有很多现成的例子,找了一个相近的:threejs.org/examples/#w…,截了个静态图长这样:
image.png


找源码:three.js/examples/we…,copy到本地,代码就200行,用到的three api就几个,搜下api大概了解代码各部分的功能,基本都能看懂,然后删了多余功能,一个粒子绕球中心旋转功能就出来了。


现在关于粒子移动、渲染、球体旋转缩放等变化api都已经基本搞懂了,然后就是痛苦折磨的计算调试了,不想再回忆了。


动画效果的移动都是靠循环对象、计算坐标,改变粒子的position来实现的,感觉应该会有更好的现成api能简化这个过程,而且有缓冲、阻尼效果等。如果有更好的例子,欢迎大佬分享。


总结下用到的api吧,就几个:


构造方法



  1. THREE.PerspectiveCamera:透视投影相机,3D场景的渲染中使用得最普遍的投影模式。
  2. THREE.SceneTHREE.WebGLRenderer:场景和渲染器。
  3. THREE.TextureLoader:创建纹理,用于加载粒子贴图。
  4. THREE.SpriteMaterial:创建精灵材质。
  5. THREE.Sprite:创建精灵,用于表示粒子。
  6. THREE.Gr0up:创建对象容器,用于整体控制多个粒子,达到旋转等效果。

属性



  1. .position.x\y\z:坐标位移;
  2. .rotation.x\y\z:粒子绕球体旋转;
  3. .position.multiplyScalar(radius):对三个向量x\y\z上分别乘以给定标量radius,用于设置粒子距球体中心距离;
  4. .scale.set:设置粒子自身的缩放
  5. .visible:控制Gr0up或粒子显隐;

难点


THREE.PerspectiveCamera透视投影相机下,由于是模拟人的眼睛从远处看的,所以会导致坐标上的单位跟html里的像素单位是不一致的,有一定的比例。但是判断浏览器窗口位置都是像素单位的,所以得算出这个比例、或者找到一种办法让两个单位是一致的。在外网搜到一个方案:forum.babylonjs.com/t/how-to-se…


const perspective = 800;
const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PI;
const camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, perspective);

这样设置相机后,俩个单位就是一致的。至于原理。。。看见Math.atan就头大,过!


BroadcastChannel


BroadcastChannel的api很简单,在一个窗口中postMessage,另一个窗口就会通过message事件接受到了。


const channel = new BroadcastChannel('editor_channel');
channel.postMessage({ aa: '123' });
channel.addEventListener('message', ({ data }) => {
console.log(data);
});

在此例逻辑是,进页面初始化时、或者坐标改变时,需要把当前窗口坐标postMessage发送到别的窗口中,然后再把所有窗体坐标数据都存在js全局变量里使用。


但是这里有个问题,如果刷新其中一个窗口时,没办法立即获取别的窗口数据,因为别的窗口只有在坐标变化时才会发送数据(为了提高效率,不会在requestAnimationFrame里一直发数据),这样就得主动postMessage一个标记到别的窗口,然后别的窗口再把自己的数据postMessage回来,是个异步过程,有些繁琐。


使用上不比LocalStorage简单多少,不过BroadcastChannel确实可以解决LocalStorage的全局影响和缓存不自动清空问题。有兴趣可以自己实现下。(可以重写storage.js里方法)


优化窗口数据监听与更新



  • 注册window storage事件,监听storage变化时(当其它窗口位置变化时),判断最新窗口总数,当数量变化时,在当前窗口重新实例化所有球体及粒子对象。
  • 注册window resize事件,更新摄像机比例和渲染器size。
  • 将所有窗口数据保存在js全局变量里,用于在requestAnimationFrame中读取渲染动画,并且只在需要时更新:

    • 其它窗口位置变化时(通过window storage事件);
    • requestAnimationFrame中判断当前窗口位置变化时(比较全局变量与当前window位置),更新全局变量和storage;



通过以上逻辑优化,可以有效提高渲染速度,减少代码重复执行,减小客户端压力。


待改进



  1. three.js实现上:学习的还是太浅了,有些动画效果应该会有更好的实现方式,希望有大佬能指点下。
  2. three.js效果:跟国外原大佬比不了,他那是粒子,我这个就是个球。
  3. 拖动窗口位置时的球体移动阻尼效果,这个实现了下,有了个效果,但是卡顿感明显,不顺畅,而且在连线动画下效果更差。
  4. 当改变窗口大小时,球体大小会随着窗口大小变化,想固定大小没找到解决方法,然后计算球体位置也没有考虑窗体大小,所以现在多窗口要求窗口大小必须是一样的。
  5. 球体之间的连线粒子移动效果不佳,特别在窗口移动时,还需优化算法。

总结


总结下相比之前的例子 尝试实现了国外大佬用Web做出来跨窗口渲染动画效果,有以下提升:



  1. 引入three.js,画出了球体、粒子旋转动画,多窗口球体,球体间粒子连线动画效果。
  2. BroadcastChannel代替LocalStorage。(技术选型没选上,未实现)
  3. 支持多个窗口(理论上没有限制),并且窗口重叠时不会有连线缺失。

跨窗口通信、存储窗口坐标、在每个窗口画出所有球体和连线,这个机制流程已经很成熟了,没有太大的优化提升空间了,所以要实现国外大佬视频效果,就只剩three.js了,实在是学不动了,水太深。


源码已上传至GitHub,代码里有详细注释,希望能有所帮助:github.com/markz-demo/…


做了两版效果,可以通过代码里注释查看效果,README.md 中有说明。


Demo:markz-demo.github.io/mark-cross-…


作者:Mark大熊
来源:juejin.cn/post/7307057492059471899

0 个评论

要回复文章请先登录注册