我们如何构建GitHub Globe

2020-12-31 21:23:29

GitHub是全世界构建软件的地方。全世界有超过5600万开发人员在GitHub上构建和协作。通过我们的新主页,我们想展示开源开发如何超越我们所生活的边界,并通过开发者的旅程来讲述我们的产品故事。

现在它已经上线了,我们很乐意直接从设计师和开发人员的声音中分享我们如何构建主页。在这个由五部分组成的系列文章中,我们将讨论:

在2019年的Satellite上,我们的CEO Nat展示了30天跨GitHub上开源活动的可视化。庞大的数量和全球影响力令人惊讶,我们知道我们希望在这个故事的基础上继续前进。

我们着手在全球设计和开发中实现的主要目标是:

相互联系的社区。我们探索了许多不同的选择,但最终落入了请求请求。事实证明,这是在世界某个地方打开并在另一个地方关闭的拉取请求的漂亮可视化。

现在展示真实工作。我们从简单地显示拉动请求的弧线和尖顶开始,但是很快意识到我们需要“生命力证明”。弧线可以很容易地只是设计动画而不是实际工作。我们对提供更多详细信息的方法进行了迭代,并通过清晰的悬停状态(显示拉动请求,回购,时间戳,语言和位置)发现了最多的共鸣。 Nat的想法是使每条线都可点击,这确实提升了体验并使其更加身临其境。在这里阅读更多。

注意细节和性能。对于我们而言,极为重要的是,地球仪不仅看起来令人鼓舞,美丽,而且在所有设备上都表现出色。我们经历了许多次细化的迭代,还有许多工作要做。

从最基本的角度来看,地球在由three.js支持的WebGL上下文中运行。我们向它提供通过JSON文件在全球范围内创建并合并的最近请求请求的数据。场景由五层组成:一个光环,一个地球仪,地球区域,用于打开请求请求的蓝色尖峰和用于合并请求请求的粉红色弧。我们不使用任何纹理:我们将四个光源指向一个球体,使用大约12,000个五边形圆来渲染地球区域,并使用一个简单的自定义着色器在球体的背面绘制光晕。

要绘制地球区域,我们首先定义所需的圆密度(这将取决于您的计算机的性能而有所不同,稍后会进一步说明),然后在嵌套的for循环中遍历经度和纬度。我们从南极开始,然后向上,计算每个纬度的周长,然后沿该线均匀分布圆,并环绕球体:

for(let lat = -90; lat< = 90; lat + = 180 / rows){const radius = Math.cos(Math.abs(lat)* DEG2RAD)* GLOBE_RADIUS; const周长=半径* Math.PI * 2; const dotForLat =圆周* dotDensity; for(let x = 0; x< dotsForLat; x ++){const long = -180 + x * 360 / dotsForLat;如果(!this.visibilityForCoordinate(long,lat))继续; //设置并保存圆矩阵数据}}

为了确定一个圆是否应该可见(它是水还是陆地?),我们加载一个包含世界地图的小PNG,通过canvas的context.getImageData()解析其图像数据,并将每个圆映射到一个像素通过visibilityForCoordinate(long,lat)方法绘制地图。如果该像素的Alpha值至少为90(255个像素),则我们绘制圆圈;如果没有,我们跳到下一个。

收集了我们需要通过这些小圆圈可视化地球区域所需的所有数据之后,我们创建了CircleBufferGeometry的实例,并使用InstancedMesh渲染了所有几何形状。

当您进入新的GitHub主页时,我们要确保在地球出现时可以看到自己的位置,这意味着我们需要弄清楚您在地球上的位置。我们希望在不延迟IP查找后的第一个渲染的情况下实现这种效果,因此我们将地球仪的起始角度设置为以格林威治为中心,查看设备的时区偏移量,然后将该偏移量转换为围绕地球仪自身轴的旋转(以弧度为单位):

const date = new Date(); const timeZoneOffset = date.getTimezoneOffset()|| 0; const timeZoneMaxOffset = 60 * 12; rotationOffset.y = ROTATION_OFFSET.y + Math.PI *(timeZoneOffset / timeZoneMaxOffset);

它不是您所在位置的精确度量,但它可以快速完成任务。

当然,全球范围内的主要行为是可视化在全球范围内正在打开和合并的所有拉动请求。使之成为可能的数据工程本身就是一个不同的话题,我们将在下一篇文章中分享如何实现这一点。在这里,我们希望向您概述我们如何可视化所有拉动请求。

让我们集中讨论合并的拉取请求(粉红色弧线),因为它们比较有趣。每个合并的拉取请求条目都有两个位置:打开位置和合并位置。我们将这些位置映射到地球上,并在这两个位置之间绘制贝塞尔曲线:

这些曲线有3个不同的轨道,而且两点之间的距离越长,我们将越往外拉出任何特定的弧线进入太空。然后,我们使用TubeBufferGeometry的实例沿这些路径生成几何,以便可以使用setDrawRange()对线的出现和消失进行动画处理。

当每条线进行动画处理并到达其合并位置时,我们会在一个实线圆圈中生成该动画并对其进行动画处理,而该实线会在存在该线时保持不变,并且一个环会放大并立即消失。通过将速度(在此为0.06)与目标(1)和当前值(animated.dot.scale.x)之差相乘,并将其添加到现有的比例值中,可以创建这些动画的缓动缓动。换句话说,对于每一帧,我们都离目标越近6%,并且随着我们离目标越近,动画自然会放慢速度。

//实心圆圈const scale = animation.dot.scale.x +(1-animation.dot.scale.x)* 0.06; animateated.dot.scale.set(scale,scale,1); //淡出constScaleUpFade =动画.dotFade.scale.x +(1-动画.dotFade.scale.x)* 0.06; animated.dotFade.scale.set(scaleUpFade,scaleUpFade,1); animated.dotFade.material.opacity = 1 -scaleUpFade;

主页和全球都需要在各种设备和平台上保持良好的性能,这在早期就给我们带来了一些创作上的限制,并使我们广泛关注于创建经过优化的页面。尽管某些现代计算机和平板电脑可以在打开抗锯齿功能的情况下以60 FPS的速度渲染地球图像,但并非所有设备都如此,因此我们决定尽早关闭抗锯齿功能并优化性能。这使我们在地球的左上边缘有一条清晰的像素化线条,因为地球的突出显示的边缘与背景的深色相遇:

这鼓励我们探索可能隐藏该像素化边缘的光晕效果。我们使用自定义着色器创建了一个球体,该球体在比球体稍大的球体的背面绘制渐变,将球体放置在球体后方,并稍微倾斜其侧面以强调左上角的效果:

这样可以平滑锐利的边缘,同时比打开抗锯齿功能更高效。不幸的是,取消抗锯齿也产生了相当显着的波纹效果,因为组成世界的所有圈子在靠近地球边缘时都越来越近。我们通过为圆使用片段着色器来减少这种效果并模拟更浓厚的气氛,其中每个圆的alpha值是其与相机的距离的函数,随着每个圆的进一步远离,淡出每个圆:

如果(gl_FragCoord.z&g​​t; fadeThreshold){gl_FragColor.a = 1.0 +(fadeThreshold-gl_FragCoord.z)* alphaFallOff;}

我们不知道地球将以多快(或缓慢)的速度加载到特定设备上,但我们想确保主页上的标题组合始终保持平衡,并且给人以地球快速加载的印象即使在渲染第一帧之前稍有延迟。

我们仅在Figma中使用渐变创建了地球的裸露版本,并将其导出为SVG。在HTML文档中嵌入此SVG几乎不会增加开销,但要确保在页面加载时立即可见。一旦准备好渲染地球的第一帧,就可以使用Web Animations API在两个元素之间进行交叉淡化并按比例放大,从而在SVG和canvas元素之间进行过渡。使用Web Animations API,我们可以在过渡过程中完全不接触DOM,从而确保它尽可能无死角。

const keyframesIn = [{不透明度:0,变换:' scale(0.8)' },{不透明度:1,变换:' scale(1)' }]; const keyframesOut = [{不透明度:1,变换:' scale(0.8)' },{opacity:0,transform:' scale(1)' }]; const options = {fill:' both&#39 ;,持续时间:600,缓动:' ease' }; this.renderer.domElement.animate(keyframesIn,options); const placeHolderAnim = placeholder.animate(keyframesOut,options); placeHolderAnim.addEventListener(' finish&#39 ;,()=> {{placeholder.remove( );});

我们的目标是维持60 FPS,同时尽我们所能呈现出美丽的地球,但要找到这种平衡是很棘手的-那里有成千上万的设备,根据它们所运行的浏览器和心情,它们的性能各不相同。我们会不断监控已达到的FPS,如果在过去的50帧中未能保持55.5 FPS,我们将开始降低场景质量。

质量分为四个等级,对于每次降级,我们都会减少昂贵的计算量。这包括降低像素密度,降低射线发射频率(弄清楚光标在场景中的悬浮状态)以及在屏幕上绘制的几何图形的数量,这使我们回到构成地球区域的圆上。当我们遍历质量等级时,我们降低了所需的圆密度并重建了地球的区域,这里从原来的约12000个圆圈变为约8000个圆圈:

//将像素密度降低到1.5(从2.0降低)this.renderer.setPixelRatio(Math.min(AppProps.pixelRatio,1.5)); //减少在任何给定时间可视化的PR数量this.indexIncrementSpeed = VISIBLE_INCREMENT_SPEED / 3 * 2 ; //减少射线广播(等待4个额外的帧)this.raycastTrigger = RAYCAST_TRIGGER + 4; //为地球区域绘制较少的几何图形this.worldDotDensity = WORLD_DOT_DENSITY * 0.65; //删除世界this.resetWorldMap(); //生成从新设置重新世界this.buildWorldGeometry();

这些是我们用于渲染地球仪的一些技术,但是地球仪的创建和新首页是一个较长故事的一部分,涉及多个团队,学科和部门,包括设计,品牌,工程,产品和通讯。我们将在这个分为5部分的系列文章中继续进行深入研究,因此请稍后再回来,或者在Twitter @GitHub上关注我们,以获取该项目的所有最新更新以及更多信息。

同时,不要错过GitHub Illustration Team的新GitHub Globe壁纸,从您的台式机或移动设备上欣赏地球:

喜欢新的GitHub主页或您在此处看到的任何作品吗?加入我们的团队!