澎湃Logo
下载客户端

登录

  • +1

手写动态 3D 蛛网图 | THREE.js

2020-10-05 09:07
来源:澎湃新闻·澎湃号·湃客
字号

原创 服老思 P话 收录于话题#3D1#可视化1#算法2#数据2

��雷达图容易误用,所以我一般不用。从数据可视化的最佳实践来说,不推荐 3D,因为透视关系导致无法准确比较。同时,不推荐动态图,人老了后看起来眼花,当然某些场景下引入时间维度后,可以增强信息密度,如��动态柱状图和��动态折线图。

我们做人有原则,要么严格遵守,要么三条一起破坏。

所以,今天做一个 3D 的、动态的雷达图。最后选择用 THREE.js 实现主体,用 DOM 实现控件和交互。

Hacking THREE

之前 ��解析《自然》 杂志 150 年论文可视化作品 中,提到过这个库。THREE.js 基于 WebGL,前身是广泛用于 3D 游戏的 OpenGL。随着移动硬件性能的提升,OpenGL 这套标准接口,在 web 环境中,也普及开来。而 THREE 对 WebGL 进行了更友好的封装,使得没有计算机图形学的开发者,也能够很快地搭建自己的 3D 场景。

推荐网站:https://threejsfundamentals.org/

如果追求速度的话,看过 Fundamentals、Responsive Design、Primitives 这三章,就可以开始 hack 自己的项目了。如果赶时间的话, Responsive Design 一节可以后面来看。不过推荐放在前面,这样可以让自己的设计从一开始避免锯齿、拉伸等问题,之后可以专注在图形上。阅读加练习大概 2-3 小时。

https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html

基本概念:

•Renderer:渲染引擎,它的输入是 scene 和 camera

•Object:一个基本单元

•Scene/ scene graph:类似文件夹,scene 和包含 object,scene 可以嵌套

•Geometry:几何形状,具体包括点(vector3)、面(face3)

•Material:材质,应用在面上

•Texture:纹理,应用在材质上

•Mesh:Geometry 和 Material 的组合;Geometry 决定了画什么、Material 决定了怎么画

•Light:光源

•Camera:镜头

THREE 的世界中,hello world 就是一个 “Hello Cube”,即画一个方块。跟教程的第一个 milestone 大概是上面这样,有几个 cube,不停翻转。

https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html

如果是初学,建议在这里多花一点时间,调整各项参数,观察结果,以掌握基本概念。否则,很容易花了大把功夫后,怎么也调不出自己要的图形。

我遇到的几个常见问题有:

•相机方向问题,物件不在视野内

•视域的长度不够(far)

•没有加光源

•光源没有对准物体

•座标系错误。注意右手法则。x 向右(拇指)、y 向上(食指)、z 从屏幕指向自己(中指)。

•角度错误。注意 THREE 里面的旋转(rotation)用弧度(radian),但相机的设置中,fov 用角度(degree)。

•面的方向(法向量)

有了 Hello Cube 后,可以进一步尝试光照、相机、材质等不同设置。

接下来,就可以探索 THREE 里面现成的形状,把它们拼接起来就可以组成复杂的图形。原网页可以扫码查看。

https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html

可以看到,有许多现成的图形。

比如,下面演示了如何画一个 3D 的心形。这段代码的功能其实更广,它示范了如何通过一系列 2D 的点,通过贝塞尔曲线连接起来,再生成一个 3D 空间中的面。

https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html

又比如,下面演示了如何通过矢量字体,生成三体空间中的形状。

https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html

我们的目标是蛛网图,每一个数据点是一个多面柱,它们通过颜色和高度来彼此区分,多面柱的每个棱角,就是一个维度。所以,基本的单元是多面柱。我们在 THREE 的 primitive 里面寻找,最接近的是下面这个形状。

https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html

可是它有一个问题:所有的棱都是一样的长度。

我们的数据可视化中,需要根据数据值的不同,来设置棱的长度。

于是,这里要做一点探索。首选的方案,是非侵入式的。即如果能通过 THREE 已经有的接口,从外部改变这个多面柱的特性,达到我们的可视化效果,则为上策。实在不行,可以选择魔改一把 THREE,再硬嵌入到项目中。

这就回到补充基本概念的时候。

https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html

在 THREE 中,模型是通过 Gemoetry 设定的。Geometry 的设定有两部分:

•设置一系列的顶点。每个顶点由 (x, y, z) 这样一个三元组声明,代表空间中的座标。

•设置一系列的三角形。每个顶点由 (id1, id2, id3) 这样的三元组声明,代表点集中下标。

所以,如果要程序化地改变一个形状,只需要改变顶点的位置,不用改变三角形的设置,与该顶点相连的线和面就会自动地改变。不过注意要把 verticesNeedUpdate 和 elementsNeedUpdate 两个标记打上,这样在下次渲染的时候,引擎会做相应的更新。

在 Hello Cube 的基础上,做如下改变测试。

通过改变顶点,实现形状变化

其中一个顶点,会以 8 秒为周期,伸长后又缩短。可以看到,邻接的面都配合变化,非常平滑。

经过这个实验,我们就有信心,一定能通过一些 primitive,以及修改顶点的方法,来实现可变尺寸的多面柱(VariableCylinder)。

不过,从外部实现的话,要修改的顶点比较多。其中涉及到极座标的公式,核心计算部分和普通多面柱(Cylinder)是重复的。另外,修改完顶点,还要重新计算 UV 和法向量,才能正确响应光照和纹理的设置。

考虑到这些,觉得先看一下源码比较好,也许直接改库更方便。

源码:https://github.com/mrdoob/three.js/blob/master/src/geometries/CylinderGeometry.js

将这个 CylinderGeometry 拷贝一份,做成我们自己的 VariableCylinderGeometry。事实证明,只需要修改 7 行,就达到目的了……

当然,为了修改这 7 行,需要花点时间研究 THREE 这个项目的结构。另外,由于是可变的多面柱,数据接口发生了一些变化。之前只需要输入棱的个数,而现在要输入的是每个棱的相对长度。

想起了福特和斯坦门茨的那个广为流传的段子:画一根线,只要一美元;知道在哪里画这根线,值 9999 美元。

比较好的方法,可能是从模组级别集成 THREE,然后自己派生出这个可变多面柱的类。由于对前端工程不熟悉,一看到近年的各种框架和 import 就头大。于是选择了魔改路线,直接侵入式地做了需要的修改,然后重新 build 一个 THREE 嵌在项目里面。

导入魔改版的THREE,将 HelloCube 改一下,得到了多面柱:

可变多面柱

接下来比较重要的部分就是蛛网了。计算核心还是极座标变换。得到关键点后,放入一个 Geometry,再通过 THREE.Line 这个辅助工具,转化成可渲染的图形。注意 THREE.Line 的地位和 THREE.Mesh 是类似的。而 Geometry 中,有点和面,但是没有线。

蛛网的大致效果如下。

大家可能发现,前面的模型,都是用黑色作为背景。以前我也觉得奇怪,是不是设计师喜欢黑色背景,看起来更高端?

其实更直接的原因是,在 3D 的世界中,一切本来就是黑的。因为有了光、有了物体,而光照到物体上,我们才看到不同的颜色。哪怕是背景,也是如此。如果用户看到的不是黑色,说明有东西在那,它反光。

所以,我们需要在所有的物件下面(-y 的区域),加一个白色的面,作为衬底。

把以上物件组合在一起,就得到第一个原型了。

接着,在 canvas 的上面,加一个浮层,放入座标标签和相对应的值。再给 legend 加上事件,当用户选择的时候,可以高亮相应的多面柱。最后,再调一下颜色。这部分和平时写的前端没什么差别,就略过细节了。
一个 3D 动态蛛网图,就做成了。
这个图有什么用呢?

它比较适合数据点在各个维度都单调递增的场景。比如,用来描述一个同学的成绩,每个轴代表一个学科,每个多面柱代表一个年份。随着努力学习,他成绩越来越好,就是这样的感觉。如果不满足这个单调递增,则效果是很奇怪的,下层的柱子,可能有一部分 “陷入” 上层的柱子中。

覆盘

时间开销:

•调研:10%

•入门 THREE:20%

•Hacking prototype:30%

•调整细节:40%

其中,调研阶段考虑了 aframe 和 THREE 两个库。aframe 是基于 THREE 的一套 declarative 语言。通过 HTML 元素,来生成 3D 的物件。如果是简单的图形,用 aframe 确实很方便。经过几年的发展,它有了一个强大的社区,其中有不少参考项目。另外,还内置一个所见即所得(WYSIWYG)的编辑器,对于调整细节非常方便。

Aframe 内置编辑器 (ctrl+alt+i)

对于稍微复杂的项目,可以通过建模软件,如 Maya 建立模型,再导入 aframe,利用前端技术做交互。这样开发者不需要有关于 3D 图形的知识,只需要复用 DOM 交互的经验即可,可以满足快速开发的需求。

不过,当图形是数据驱动生成的时候,发现 aframe 就有点差强人意了。程序化地修改,可以比较方便完成的是:

•平移

•缩放

•旋转

•组合

如果要改变一个形状本身,比如这次我们需要的可变多面体,aframe 就需要一个 custom model。而 custom model 是用 THREE 写的…… 所以结果是,不如直接学习 THREE 好了。

这是前期调研走的一点弯路。该来的迟早会来,怎么也挡不住。不过好在 THREE 的封装已经极其方便,相比十年前学习OpenGL,已经快了很多。由于都是在前端,可以综合使用 WebGL 和 DOM 两种技术,利用浮层实现假 3D 的效果,也能省下不少时间。如果放在古时候,到这会可能才把 Hello Cube 搞定……

CREDIT

排版:Bee

数据科学 | 数字广告 | 未来主义

原标题:《手写动态 3D 蛛网图 | THREE.js》

阅读原文

    本文为澎湃号作者或机构在澎湃新闻上传并发布,仅代表该作者或机构观点,不代表澎湃新闻的观点或立场,澎湃新闻仅提供信息发布平台。申请澎湃号请用电脑访问http://renzheng.thepaper.cn。

    +1
    收藏
    我要举报
            查看更多

            扫码下载澎湃新闻客户端

            沪ICP备14003370号

            沪公网安备31010602000299号

            互联网新闻信息服务许可证:31120170006

            增值电信业务经营许可证:沪B2-2017116

            © 2014-2025 上海东方报业有限公司

            反馈