制作世界奇观 3D 地球仪

发布于 2022-01-25 12:46:19 字数 6670 浏览 1077 评论 0

如果您在 查看了最近推出的 Google World Wonders 支持 WebGL 的浏览器上 网站,您可能会在屏幕底部发现一个精美的旋转地球。 本文让您了解地球的工作原理以及我们用来构建它的内容。

为了让您快速了解一下,World Wonders 地球仪是 Google 数据艺术团队对 WebGL 地球仪进行了重大调整的版本。 我们采用了原始地球仪,去除了条形图位,更改了着色器,添加了精美的可点击 HTML 标记和来自 Mozilla 的 GlobeTweeter 演示的自然地球大陆几何图形(非常感谢 Cedric Pinson!)所有这些都是为了制作一个与网站的匹配的漂亮的动画地球仪配色方案并为网站增加了一层额外的复杂性。

地球的设计简介是在世界遗产地的顶部放置一个带有可点击标记的漂亮动画地图。 考虑到这一点,我开始寻找合适的东西。 首先想到的是由 Google 数据艺术团队构建的 WebGL Globe。 这是一个地球仪,看起来很酷。 你还需要什么,嗯?

设置 WebGL 地球

制作地球小部件的第一步是下载 WebGL Globe 并启动并运行它。 WebGL Globe 在 Google Code ,下载和运行都很简单。 下载并解压 zip 、 cd 到其中并运行一个基本的网络服务器: python -m SimpleHTTPServer. (请注意,默认情况下它没有启用 UTF-8; 您可以使用它 。)现在,如果您导航到 http://localhost:8000/globe/globe.html,您应该会看到 WebGL Globe。

随着 WebGL Globe 的启动和运行,是时候切断所有不需要的部分了。 我编辑了 HTML 以删除 UI 位,并从地球初始化函数中删除了地球条形图设置内容。 在该过程结束时,我的屏幕上有一个非常准系统的 WebGL Globe。 你可以旋转它,它看起来很酷,但仅此而已。

为了减少不需要的东西,我从地球的 index.html 中删除了所有 UI 元素,并将初始化脚本编辑为如下所示:

if(!Detector.webgl){
  Detector.addGetWebGLMessage();
} else {
  var container = document.getElementById('container');
  var globe = new DAT.Globe(container);
  globe.animate();
}

添加大陆几何

我们想让相机靠近地球表面,但是当我们测试地球放大时,缺乏纹理分辨率变得很明显。 放大后,WebGL Globe 的纹理变得块状和模糊。 我们本可以使用更大的图像,但这会使地球的下载和运行速度变慢,因此我们选择使用陆地和边界的矢量表示。

对于陆地几何,我求助于开源 GlobeTweeter 演示并将其中的 3D 模型加载到 Three.js。 随着模型的加载和渲染,是时候开始完善地球的外观了。 第一个问题是地球陆地模型的球形不够与 WebGL Globe 齐平,所以我最终编写了一个快速网格分割算法,使陆地模型更加球形。

使用球形陆地模型,我能够将其放置在稍微偏离地球表面的位置,创建漂浮的大陆,在它们下方用黑色 2px 线勾勒出某种阴影。 我还尝试了霓虹色的轮廓来制作一种类似 Tron 的外观。

随着地球和陆地的渲染,我开始尝试不同的地球外观。 当我们想要低调的单色外观时,我坚持使用灰度地球和陆地。 除了前面提到的霓虹灯轮廓外,我还尝试了一个在浅色背景上带有深色陆地的深色地球,实际上看起来很酷。 但它的对比度太低,难以阅读,也不符合项目的感觉,所以我放弃了它。

我对地球仪外观的另一个早期想法是让它看起来像釉面瓷器。 那个我没有设法尝试,因为我没有设法编写一个着色器来做瓷器外观(视觉材质编辑器会很好)。 我尝试过的最接近的东西是这个带有黑色大陆的白色发光地球。 它有点整洁,但对比度太高。 而且它看起来不是很好。 所以又是一个垃圾堆。

黑白球体中的着色器使用了一种虚假的漫反射背光照明。 球体的亮度取决于垂直于屏幕平面的表面的距离。 因此,地球中央指向屏幕的像素是暗的,而地球边缘的像素是亮的。 结合浅色背景,您可以看到地球反射漫射明亮背景的位置,营造出优雅的陈列室外观。 黑色地球仪也使用 WebGL Globe 纹理作为光泽贴图,因此大陆架(浅水区域)与地球其他部分相比看起来更闪亮。

这是黑色地球的海洋着色器的样子。 非常基本的顶点着色器和一个 hacky。

    'ocean' : {
      uniforms: {
        'texture': { type: 't', value: 0, texture: null }
      },
      vertexShader: [
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
          'vNormal = normalize( normalMatrix * normal );',
          'vUv = uv;',
        '}'
      ].join('\n'),
      fragmentShader: [
        'uniform sampler2D texture;',
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'vec3 diffuse = texture2D( texture, vUv ).xyz;',
          'float intensity = pow(1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0);',
          'float i = 0.8-pow(clamp(dot( vNormal, vec3( 0, 0, 1.0 )), 0.0, 1.0), 1.5);',
          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * intensity;',
          'float d = clamp(pow(max(0.0,(diffuse.r-0.062)*10.0), 2.0)*5.0, 0.0, 1.0);',
          'gl_FragColor = vec4( (d*vec3(i)) + ((1.0-d)*diffuse) + atmosphere, 1.0 );',
        '}'
      ].join('\n')
    }

最后,我们选择了一个黑色的地球,上面有浅灰色的陆地。 它最接近设计简介,看起来漂亮且可读。 此外,让地球的对比度稍低,使标记和其他内容在比较中更加突出。 下面的版本使用完全黑色的海洋,而生产版本有深灰色的海洋和略有不同的标记。

使用 CSS 创建标记

说到标记,随着地球和陆地的工作,我开始研究地标。 我决定为标记使用 CSS 样式的 HTML 元素,以使标记的制作和样式更容易,并可能在团队正在处理的 2D 地图中重用标记。 当时我还不知道一种使 WebGL 标记可点击的简单方法,也不想编写额外的代码来加载/创建标记模型。 事后看来,CSS 标记运行良好,但当浏览器合成器和渲染器处于不断变化的时期时,偶尔会遇到性能问题。 从性能的角度来看,在 WebGL 中做标记会是一个更好的选择。 再说一次,CSS 标记节省了大量的开发时间。

CSS 标记由两个绝对定位的 div 组成,这些 div 具有 CSS 变换属性。 标记的背景是 CSS 渐变,标记的三角形部分是旋转的 div。 标记有一个小的阴影,可以将它们从背景中弹出。 标记的最大问题是使它们表现得足够好。 听起来很可悲,绘制几十个四处移动并在每一帧上更改其 z-index 的 div 是触发各种浏览器渲染陷阱的好方法。

标记与 3D 场景同步的方式并不太复杂。 每个标记在 Three.js 场景中都有一个对应的 Object3D,用于跟踪标记。 为了获得屏幕空间坐标,我为地球和标记取了 Three.js 矩阵,并将一个零向量与这些矩阵相乘。 从那里我得到了标记的场景位置。 为了获得标记的屏幕位置,我通过相机投影场景位置。 生成的投影矢量具有标记的屏幕空间坐标,可以在 CSS 中使用。

var mat = new THREE.Matrix4();
var v = new THREE.Vector3();

for (var i=0; i<locations.length; i++) {
  mat.copy(scene.matrix);
  mat.multiplySelf(locations[i].point.matrix);
  v.set(0,0,0);
  mat.multiplyVector3(v);
  projector.projectVector(v, camera);
  var x = w * (v.x + 1) / 2; // Screen coords are between -1 .. 1, so we transform them to pixels.
  var y = h - h * (v.y + 1) / 2; // The y coordinate is flipped in WebGL.
  var z = v.z;
}

最后,最快的方法是使用 CSS 转换来移动标记,而不是使用不透明度衰减,因为它会触发 Firefox 上的慢速路径并将所有标记保留在 DOM 中,而不是在它们离开地球时移除它们。 我们还尝试使用 3D 变换而不是 z-indexes,但由于某种原因,它在应用程序中不能正常工作(但它确实在简化的测试用例中工作,如图),距离发布还有几天那时,因此不得不将该部分留给发布后的维护。

当您单击标记时,它会扩展为可单击的地名列表。 这都是普通的 HTML DOM 东西,所以写起来超级容易。 所有的链接和文本渲染都可以正常工作,我们不需要额外的工作。

压缩文件大小

随着演示工作并连接到世界奇观网站的其他部分,仍有一个大问题需要解决。 全球陆地的 JSON 格式网格大小约为 3 兆。 不适合展示网站的首页。 好消息是使用 gzip 压缩网格可以将其降低到 350 kB。 但是,嘿,350 kB 还是有点大。 几封电子邮件之后,我们设法招募了 Won Chun(他致力于压缩巨大的 Google Body 网格)来帮助我们压缩网格。 他 将网格从以 JSON 坐标形式给出的大型平面三角形列表压缩到带有索引三角形的压缩 11 位坐标,并将文件大小压缩到 95 kB

使用压缩网格不仅可以节省带宽,而且网格的解析速度也更快。 将 3 meg 的字符串化数字转换为本机数字比解析 100 kB 的二进制数据需要更多的工作。 由此产生的 250 kB 页面大小减少非常好,并且它在 2 Mbps 连接上的初始加载时间低于一秒。 更快 小,awesomesauce!

同时,我在加载原始的自然地球形状文件,GlobeTweeter 网格是从中派生的。 我设法加载了 Shapefile,但将它们渲染为平坦的陆地需要对它们进行三角剖分(湖有洞,natch)。我使用 THREE.js 实用程序而不是洞对形状进行了三角剖分。 生成的网格有很长的边,这需要将网格分成更小的三角形。 长话短说,我没能及时让它工作,但巧妙的是,进一步压缩的 Shapefile 格式可以让你得到一个 8 kB 的陆地模型。 嗯好吧,那就下次再说吧。

未来的工作

可以使用一些额外工作的一件事是使标记动画更好。 现在,当它们越过地平线时,效果有点俗气。 此外,为标记打开有一个很酷的动画会很好。

在性能方面,缺少的两件事是优化网格分割算法和使标记更快。 除此之外,事情很花哨。 欢呼!

概括

在本文中,我描述了我们如何为 Google World Wonders 项目构建 3D 地球仪。 我希望您喜欢这些示例,并尝试构建您自己的自定义地球小部件。

参考文档

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

不乱于心

暂无简介

0 文章
0 评论
679 人气
更多

推荐作者

qq_Yqvrrd

文章 0 评论 0

2503248646

文章 0 评论 0

浮生未歇

文章 0 评论 0

养猫人

文章 0 评论 0

第七度阳光i

文章 0 评论 0

新雨望断虹

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文