玩大了! 阿里工程师的年会竟然这样搞?
    2018-06-21    阿里技术

不用邀请函、不用签字板,“笑容”才是唯一的入场凭证。年会还有这样的操作?

 

最近在阿里巴巴内部就掀起这样一阵“笑容签到”风潮。在年会开场,大家是这样排队刷脸的:

 

 

 

而刷脸成功后,入场人员的花名、头像以及“笑容指数”会实时同步显示在会场大屏幕上,并汇聚成一个“笑脸星球”。

  

 

 这是阿里巴巴信息平台一群90后工程师,用两周时间开发的一套年会开场秀系统:

 

观众在经过人脸识别后,系统会根据人脸表情特征,识别出观众的花名(名字)与微笑指数,同时吐出一句“评语”。当笑容越灿烂,评价就会越高。同时通过技术手段,这些信息还会上屏展示与互动。

 

 工程师们在现场调试

 

不仅是部门年会,在内部一些大型比赛、重要颁奖典礼等活动开场,也逐步用起了这一套科技感满满的开场秀。同时根据活动不同主题,还可以进行个性化定制呈现。

 

 阿里内部一些重要大会陆续使用这套系统开场

 

究竟这套年会开场秀系统如何实现的?今天,阿里妹邀请到该项目团队中的开发小哥哥探源,来聊聊背后的实现原理与技术方案。

 

背景

 

传统大会通常采用的暖场方式是,在大屏幕上循环播放宣传视频,等观众差不多到齐时,一个321的倒计时宣告开始,既老套又缺乏互动。更是白白错过一次向观众展示公司技术与公司人才的机会。

 

那有没有一种可以将科技感与互动感结合的开场秀呢?恰逢新一财年信息平台部门大会在即,我们想以此为切入点,给大家玩一些不一样的开场。

 

解决方案

 

任何一个“靠谱”的前端工程师,肯定会下意识想用H5动画来替代视频。我们的方案是:使用酷炫的H5动画,并且结合智慧园区团队的人脸识别技术实现人员扫脸签到+大屏展示的技术方案。既能运用及展示内部的技术,又能起到不错的暖场效果。

 

 

根据方案构想,很快就形成了初版需求demo。梳理一下,可以拆分出下面几点功能:

 

  • 初始状态下,是默认做自转运动的星球,周围有围绕的卫星

  • 当人员进场时,人脸识别成功后,在屏幕上展示

  • 展示完之后,照片要飞回到自转的球面上,跟着球一起做自转运动

  • 会议开始前,人工控制照片做粒子效果,over

 

技术方案

 

当功能拆解后,便是研究怎么实现方案了。出于对效果考虑,准备选择3维动画来创作,并最终采用了Three.js作为3D引擎库,结合tween.js作为配套的动画库,以及基于webpack的脚手架nowa链接作为技术支持。

 

whyThree.js?

 

目前市面上比较成熟的3D库有Three.js和Babylon.js,但对比两者后可发现:

 

  • Babylon.js常用于制作3d游戏引擎,倾向于游戏开发;而Three.js是纯渲染引擎。

  • Babylon.js诞生不久(13年),社区活跃;而Three.js相对久一些(比webgl还久),且比较流行,代码易读,团队成员有相关的开发经验。

  • Babylon.js已经支持了webgl2的多数特性,并且能够平滑降级;而Three.js还是处于提需求阶段,并没有支持。

 

所以最后选择的是Three.js,想尝试下webgl2.0的小伙伴可以试下Babylon.js。

 

技术实现

 

介绍完技术引擎后,接下来将结合整体的功能流程,为大家解析下如何通过技术实现。

 

 

 

step1:让球体运动起来

 

 

这是动画展示效果的第一步。通过Three.js的基本方法,很容易实现在场景中添加对象的功能。如上图所示,在场景中添加了一个球体,并让它做顺时针的转动,而让球表面的图做逆时针的转动,并向场景中添加了不断向z轴负方向运动的星星,和做一定角度的公转运动的卫星。

 

 

 

三维坐标系中的点坐标x,y,z可以转换成了(上图)中的极坐标r,θφ(半径r和两个角度θ 和φ),同样可以用来表示点的位置(反过来也能推导)。

 

球面上的方片也是由对象构成。人员签到成功之后,方片上会变成照片,这里需要事先计算出方片相对于球面的位置以便生成方片。根据前期配置的到场人数n,从而让球面生成均匀分布的n个点,得到每个点的极坐标系位置,再转换成点坐标位置,这些位置就是方片位置。

 

step2:加入“微笑”展示效果

 

 

前端通过轮询定时请求后端接口,来拉取人脸识别数据,展示之后告诉后端已经展示过了。由于现场人脸识别后的“微笑”需要实时展示在主屏上,每场年会活动到场人数不等(约500-1500人),在这一状况下,如何保证在规定时间内完成人员入场与展示。我们的方案是:展示时间可以动态调节,人流量大时识别成功之后大屏就展示快一些,反之则展示慢一些

 

因此,需要在前端维护了一个队列,用于存储识别成功但还未展示人员,轮询拉取的数据会进入到队列中,展示过的数据则会从队列中移除,一个展示动画的时长会根据队列的长度动态调节,取值范围2.5-5s不等。

 

step3:让“微笑”随球体一起运动

 

“微笑”展示后,还有个照片飞回球面并跟着球体一起运动的效果。因为牵涉到坐标系的相对运动球面的位置是相对于父元素来实现的),这一效果较难实现。

 

通过step1,可以计算出方片的位置position(position是个三维向量:xyz),因为位置是相对球体的,所以从球体看来方片的位置永远是xyz,而在全局看来,方片的位置是永远在变化的,而这个位置坐标怎么获取是一个问题。

 

当时,小伙伴们想到了两种方案:

 

第一种:让照片相对球体运动,这样就不用去考虑方片位置了,因为这个位置是不变的。但这样的弊端是,因为球体在运动,照片也需要一直运动。如果要达到视觉上看“照片不动”的效果,就需要照片永远对着摄像头。

 

第二种:让照片相对全局运动,在每一帧的时候去获取方片相对于全局的坐标。然后在这一帧内慢慢趋近于这个坐标,最后实现位置的重叠。

 

经过讨论,决定采用第二种方案。因为核心是要解决坐标系相对转换的问题,但第一种方案并没有解决,只是把问题方式转移了。还好,Three.js提供了现成的方法:getWorldPosition用于返回表示对象在世界空间中的位置向量。

    

这样,就可以得到方片的全局坐标。

 

现在知道了照片的位置和最后需要到达的位置。但如果直接从初始位置向最后位置运动,还会有个问题:照片可能直接穿过球体而达到最后的位置(不符合运动规律),所以这里需要尽可能做曲线运动,而不是让照片穿过球体。

 

我们还是用到了上面的坐标系转极坐标系的公式,通过初始的极坐标位置(sourceR,sourcePhi,sourceTheta)和目标位置的(targetR,targetPhi,targetTheta)。根据线性差值公式计算出在每一帧中的中间位置(currentR,currentPhi,currentTheta),再转换成点坐标系下的位置,即为每一帧时照片的位置,从轨迹上来看是一个曲线运动,且不会穿过球体。

 

step4:粒子动画开场

 

终于进入最后的环节了。在活动正式开场前,“笑脸星球“会散开成粒子状态,通过粒子运动拼成本场活动的主题文案。这里运用到粒子动画效果,需要事先知道每个粒子最后运动的位置。我们参考了canvas粒子动画的效果(具体可以自行查一下),简化如下:

 

  1. newImage加载图片

  2. 用context.drawImage把图片画到一张canvas画布上

  3. 通过context.getImageData就可以获取画布上指定区域的像素数据

  4. 比较每个点的像素值就可以记录下需要像素点的位置信息,转换成3D的坐标

    这边会涉及坐标系的转换,需要特别注意一下,平面canvas的坐标系和THREE.js的坐标系是不一样的)。

  5. 创造粒子,让粒子做飞散状运动起来,最后到达指定位置即可

  6. 事先准备两张图片,一张白底透明,一张彩色透明,粒子运动完成之后白底渐渐显示

  7. 然后白底渐隐,彩色的渐渐显示

 

性能优化

 

客观因素如:大屏分辨率、设备显卡等不考虑,但需要更多从可控条件来解决部分性能问题。一般衡量动画是否卡顿用fps衡量,如果在30-60帧之内肉眼基本无感知,再往下,会感受到很明显的卡顿。第一版开发完成之后,在mac上的帧率只有20+到40。肯定达不到标准,所以从以下几个方面开始优化。

 

1.尽量重用Material和Geometry,或者缓存模型;

 

BufferGeometry 会缓存网格模型,性能要高效点。网格模型生成原理。

 

  • Geometry 生成的模型是这样的 (代码)-> (CUP 进行数据处理,转化成虚拟3D数据) -> (GPU 进行数据组装,转化成像素点,准备渲染) -> 显示器第二次操作时重复走这些流程。

 

  • BufferGeometry 生成模型流程 (代码) -> (CUP 进行数据处理,转化成虚拟3D数据) -> (GPU 进行数据组装,转化成像素点,准备渲染) -> (丢入缓存区) -> 显示器第二次修改时,通过API直接修改缓存区数据,流程就变成了这样(代码) -> (CUP 进行数据处理,转化成虚拟3D数据) -> (修改缓存区数据) -> 显示器。

 

2.减少渲染的对象,或者渲染的时候让对象不可见:渲染过不再需要的对象可以设置visible = false,或者直接从scene中remove,两者的区别可以参考Three.js scene.remove vs. visible=false;

 

3.谨慎地在render()中操作:一般FPS为60也就意味着一秒会执行60次如果render()中有有实例化或是赋值操作很容易会崩溃;

 

4.选择合适的对象:粒子我就是用Sprite代替Mesh实现的;

 

5.考虑光源的影响:会影响场景中对象的渲染;

 

6.渲染的对象时side属性尽量用FrontSide,DoubleSide会导致更多的渲染,也是减少渲染的方法;

 

7.可以用着色器来渲染,用更底层的glsl来实现。

 

优化完之后的状态:在mac上基本上都是60fps。保证了最终效果的实现:

 

 

未来规划

 

万万没想到,一套专为部门年会做的方案,正逐步演变成一个成熟产品。并且通过可视化、定制化的配置,结合人脸识别技术已经支持集团多个部门的年会与活动。

 

随着使用场景的不断丰富,未来我们还将针对活动主题,在活动过程中增加互动。比如根据入场时的人脸识别,支持抽奖等现场互动,实现年会大屏、观众手机、人脸闸机等多屏互动。此外,在面向参会观众,还将生成现场图片等素材,方便收藏。

 

信息平台事业部是阿里巴巴经济体服务的基础平台,为阿里巴巴提供生态化、国际化、数据化、移动化以及安全稳定的企业信息服务。我们希望能有更多同学加入,通过技术创新,挑战自己的不可能,同时为阿里巴巴更多可能性的发生贡献力量。

评论加载中...