- +1
手写动态 3D 蛛网图 | THREE.js
原创 服老思 P话 收录于话题#3D1#可视化1#算法2#数据2
��雷达图容易误用,所以我一般不用。从数据可视化的最佳实践来说,不推荐 3D,因为透视关系导致无法准确比较。同时,不推荐动态图,人老了后看起来眼花,当然某些场景下引入时间维度后,可以增强信息密度,如��动态柱状图和��动态折线图。
我们做人有原则,要么严格遵守,要么三条一起破坏。
所以,今天做一个 3D 的、动态的雷达图。最后选择用 THREE.js 实现主体,用 DOM 实现控件和交互。
Hacking THREE
—
之前 ��解析《自然》 杂志 150 年论文可视化作品 中,提到过这个库。THREE.js 基于 WebGL,前身是广泛用于 3D 游戏的 OpenGL。随着移动硬件性能的提升,OpenGL 这套标准接口,在 web 环境中,也普及开来。而 THREE 对 WebGL 进行了更友好的封装,使得没有计算机图形学的开发者,也能够很快地搭建自己的 3D 场景。

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

基本概念:
•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,不停翻转。

如果是初学,建议在这里多花一点时间,调整各项参数,观察结果,以掌握基本概念。否则,很容易花了大把功夫后,怎么也调不出自己要的图形。
我遇到的几个常见问题有:
•相机方向问题,物件不在视野内
•视域的长度不够(far)
•没有加光源
•光源没有对准物体
•座标系错误。注意右手法则。x 向右(拇指)、y 向上(食指)、z 从屏幕指向自己(中指)。
•角度错误。注意 THREE 里面的旋转(rotation)用弧度(radian),但相机的设置中,fov 用角度(degree)。
•面的方向(法向量)
有了 Hello Cube 后,可以进一步尝试光照、相机、材质等不同设置。
接下来,就可以探索 THREE 里面现成的形状,把它们拼接起来就可以组成复杂的图形。原网页可以扫码查看。
https://threejsfundamentals.org/threejs/lessons/threejs-primitives.html
可以看到,有许多现成的图形。
比如,下面演示了如何画一个 3D 的心形。这段代码的功能其实更广,它示范了如何通过一系列 2D 的点,通过贝塞尔曲线连接起来,再生成一个 3D 空间中的面。

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

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

可是它有一个问题:所有的棱都是一样的长度。
我们的数据可视化中,需要根据数据值的不同,来设置棱的长度。
于是,这里要做一点探索。首选的方案,是非侵入式的。即如果能通过 THREE 已经有的接口,从外部改变这个多面柱的特性,达到我们的可视化效果,则为上策。实在不行,可以选择魔改一把 THREE,再硬嵌入到项目中。
这就回到补充基本概念的时候。

在 THREE 中,模型是通过 Gemoetry 设定的。Geometry 的设定有两部分:
•设置一系列的顶点。每个顶点由 (x, y, z) 这样一个三元组声明,代表空间中的座标。
•设置一系列的三角形。每个顶点由 (id1, id2, id3) 这样的三元组声明,代表点集中下标。
所以,如果要程序化地改变一个形状,只需要改变顶点的位置,不用改变三角形的设置,与该顶点相连的线和面就会自动地改变。不过注意要把 verticesNeedUpdate 和 elementsNeedUpdate 两个标记打上,这样在下次渲染的时候,引擎会做相应的更新。
在 Hello Cube 的基础上,做如下改变测试。

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

不过,从外部实现的话,要修改的顶点比较多。其中涉及到极座标的公式,核心计算部分和普通多面柱(Cylinder)是重复的。另外,修改完顶点,还要重新计算 UV 和法向量,才能正确响应光照和纹理的设置。
考虑到这些,觉得先看一下源码比较好,也许直接改库更方便。
源码:https://github.com/mrdoob/three.js/blob/master/src/geometries/CylinderGeometry.js
将这个 CylinderGeometry 拷贝一份,做成我们自己的 VariableCylinderGeometry。事实证明,只需要修改 7 行,就达到目的了……

想起了福特和斯坦门茨的那个广为流传的段子:画一根线,只要一美元;知道在哪里画这根线,值 9999 美元。
比较好的方法,可能是从模组级别集成 THREE,然后自己派生出这个可变多面柱的类。由于对前端工程不熟悉,一看到近年的各种框架和 import 就头大。于是选择了魔改路线,直接侵入式地做了需要的修改,然后重新 build 一个 THREE 嵌在项目里面。
导入魔改版的THREE,将 HelloCube 改一下,得到了多面柱:

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

其实更直接的原因是,在 3D 的世界中,一切本来就是黑的。因为有了光、有了物体,而光照到物体上,我们才看到不同的颜色。哪怕是背景,也是如此。如果用户看到的不是黑色,说明有东西在那,它反光。
所以,我们需要在所有的物件下面(-y 的区域),加一个白色的面,作为衬底。
把以上物件组合在一起,就得到第一个原型了。



它比较适合数据点在各个维度都单调递增的场景。比如,用来描述一个同学的成绩,每个轴代表一个学科,每个多面柱代表一个年份。随着努力学习,他成绩越来越好,就是这样的感觉。如果不满足这个单调递增,则效果是很奇怪的,下层的柱子,可能有一部分 “陷入” 上层的柱子中。
覆盘
—
时间开销:
•调研:10%
•入门 THREE:20%
•Hacking prototype:30%
•调整细节:40%
其中,调研阶段考虑了 aframe 和 THREE 两个库。aframe 是基于 THREE 的一套 declarative 语言。通过 HTML 元素,来生成 3D 的物件。如果是简单的图形,用 aframe 确实很方便。经过几年的发展,它有了一个强大的社区,其中有不少参考项目。另外,还内置一个所见即所得(WYSIWYG)的编辑器,对于调整细节非常方便。

对于稍微复杂的项目,可以通过建模软件,如 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。





- 报料热线: 021-962866
- 报料邮箱: news@thepaper.cn
互联网新闻信息服务许可证:31120170006
增值电信业务经营许可证:沪B2-2017116
© 2014-2025 上海东方报业有限公司




