function createSphere(total = 10) {
const points = []
let lat, lon, x, y, z
for (let i = 0; i <= total; ++i) {
lat = i * Math.PI / total // 纬度
for (let j = 0; j <= total; ++j) {
lon = j * 2 * Math.PI / total // 经度
x = Math.sin(lat) * Math.cos(lon)
y = Math.sin(lat) * Math.sin(lon)
z = Math.cos(lat)
// 为了方便对应观看,把一些可以在第一层循环计算的公式放入这层,会有点重复计算
points.push([x, y, z])
}
}
return points
}
用 total 表示经线和纬线的数量,通过公式求出网格上的每个点的位置。这里缺少了公式中的 r,是因为希望返回的是单位球,所以这里 r 等于 1 就忽略了。默认是 10 条经线和纬线,当经线和纬线越来越大时,生成的球的表面也就越光滑。
如何画个球?好像 JS 和 CSS 并没有提供这个能力,当然也不可能为了画个球引入 Threejs。这篇文章将介绍 4 种画球的方法,每种方法都有不同的特点,生成球的数据可以使用任何方式渲染,可以在 canvas 中渲染,也可以使用 DOM 来渲染来实现一些博客里面的标签球效果。文章的最后将结合前面的知识,来画出更加复杂酷炫的 3D 形状。
标准球
标准球也称为
UV Sphere
,它是最常用的画球方法。要了解它首先来看下常见的地球平面图。从这张图可以看到,经线是列,从东经
180°
到西经180°
,纬线是行从北纬90°
到南纬90°
。经线和纬线交叉形成一个个小格子,我们获取网格上的顶点,在使用 Spherical coordinate system 把它变成 3 维球体坐标就行了。打开这篇文章,可以找到下面这个公式。其中
r
是半径,Theta
是纬度,Phi
是经度。知道了上面的公式就可以来画球了。用
total
表示经线和纬线的数量,通过公式求出网格上的每个点的位置。这里缺少了公式中的r
,是因为希望返回的是单位球,所以这里r
等于1
就忽略了。默认是 10 条经线和纬线,当经线和纬线越来越大时,生成的球的表面也就越光滑。有了这些数据我们就可以使用各种方式渲染。
用 canvas 渲染
通过上图可以发现是球的两极对准我们,但是我们希望两极在 Y 轴上。
通过更换
x
,y
,z
的位置将两极放到了 Y 轴上。不过现在还没有透视效果,看不出来是个球体,下面来添加透视效果。透视效果是近大远小,所以这里用
x
和y
除z
。因为是单位圆z
的值是-1
到1
。2 - z
将z
的值变为1
到3
。这里的2
其实是我们具体球的距离,当这个值越大时会发现球距离我们越来越远。下面让球转起来吧。怎么让球旋转呢?同样打开 Wikipedia 找到 Rotation matrix 这篇文章,我们希望球绕 Y 轴旋转,可以找到下面这个旋转矩阵。
这里增加了
total
的值,可以发现球更圆了。用 CSS3 渲染
同样我们还可以用 CSS 来渲染,利用 CSS3 中的透视和
animation
等属性可以很方便的渲染出来。仅仅只需要几行代码就渲染出来。但是可以发现球的两极有很多的点重叠,并且有两条经线重叠。
两极重复是因为第一行和最后一行的点都在两极,两条经线重复是因为上面公式中经线范围是
[0, 2PI)
它不包括2PI
。这样就可以得到一个没有点重复顶点的球。
用 webgl 渲染
用 webgl 渲染,还需要修改一下
createSphere
方法,因为 webgl 中用的是主要用的是三角形,我们需要把每 3 个相关的点连接成一个三角形。上面我们手动处理了第一和最后一行。需要注意,当是最有一列时需要将最后一列连接到第一列。
因为 webgl 代码比较冗长,所以这里省略的大量相关代码,只保留了两个 shader 和最终渲染方法,详细代码可以查看这里。
正方体
还可以通过正方体来得到球形,这个正方体类似于魔方,它的每一个面都是一个网格。
首先要生成这个魔方,然后对上面的点进行归一化,这样就可以得到单位球。
要生成一个这个正方体,我们需要 3 层循环,循环正方体的每个面,每一行和每一列。我可以定义每个面的起点,右和上的终点,然后在每次循环中乘以对应步长,就可以得到这个正方体。
上面函数中的
vec3
代表三维向量,这里使用 glMatrix 来帮助我们进行向量运算。同样可以使用 webgl 渲染,下图是细分 5 次后的结果。
正四面体细分
还可以通过对正四面体细分来近似球形。正四面体一共有 4 个面,每个面都是三角形。
通过细分每个面的三角形。通过获取一个三角形的每条边的中点,并连线,就可以生成 4 个小三角形,然后不断的重复这个过程不断的细分,最终将细分出来的点进行归一化就可以得到一个单位球。
可以通过这篇文章 Tetrahedron 知道正四面体的 4 个顶点的坐标。
下图是细分 3 次后的结果。
正二十面体细分
正二十面体细分和正四面体细分非常相似。正四面体我们找出它的 4 个顶点,然后对它的 4 个面进行细分,正二十面体我们需要找到 12 个顶点,并对它 20 个面进行细分。既然和正四面体这么相似为什么不直接用正四面体多细分几次呢?这是因为正二十面体细分可以得到大小都一样的三角形球形。
通过这篇文章 Regular Icosahedron 可以了解正二十面体。
下图是细分 2 次后的结果。可以发现所有的三角形都一样大。
SuperShapes
了解了这么多种方法,其实我们能画的不止球体,只需要在第一种画球方法上做一些修改就可以画出非常多的酷炫的形状!
我们首先要了解
supershape
的公式,打开 这个网站 可以看到这个公式。把这个公式变成一个函数。
然后修改一下第一种画球的方法。
下图是在 webgl 中渲染的结果。可以通过改变
superShape
的参数来得到各种各样的形状,当然也可以在动态的将一个形状过渡到另一个。总结
这篇文章一共介绍了 4 种画球的方法,每个球体有不同的特点和不同的应用场景,标准球两极的三角形小,靠近赤道的三角形大。正方体细分和正四面体细分的球体,面与面拼接的地方的三角形小。正二十面体细分的球体各个三角形都一样大。
参考