之前简单玩了玩Zdog,用它制作了一个非常简单的3D版小宇宙图标,把过程中遇到的一些趣事分享给大家。
Zdog是一个绘制伪3D模型的库,可以输出成Canvas或者svg。模型的geometry是在3D空间中进行计算的,渲染出来的是想被拍平的2D图像。
基于这种特性,Zdog有着一些自己的特点,例如可以更好的画出「圆」的东西。3D中球体的geometry可能是由很多个三角形拼起来的,而Zdog实际渲染出的球体其实是一个平面的圆形,相比真正的球体简单了很多。
对比threejs之类专门绘制3D图像的库,Zdog没有诸如mesh, texture, material, geometry之类的概念,只需要实例化自带的图形然后将他们添加到画布上就能看到图像出现在显示器上了。但也是由于一切都被简化了,像z-fighting之类的问题非常容易出现,下文的例子中就遇到了。
使用Zdog非常简单,只需要创建一个<canvas>,实例化一个Illustration 和任意的图形,执行一下render方法就可以获得画面了。
// create illo
let illo = new Zdog.Illustration({
// set canvas with selector
element: '.zdog-canvas',
});
// add circle
new Zdog.Ellipse({
addTo: illo,
diameter: 80,
stroke: 20,
color: '#636',
});
// update & render
illo.updateRenderGraph();
当然,这样得到的画面是静止的,想要让画面动起来我们就需要不断的修改画面里的内容并且渲染,在web里最常用的就是requestAnimationFrame 函数了。我们只需要将上面只调用了一次的updateRenderGraph 函数的代码修改为如下代码,就能得到不停绕y轴旋转的图形了。
function animate() {
// rotate illo each frame
illo.rotate.y += 0.03;
illo.updateRenderGraph();
// animate next frame
requestAnimationFrame( animate );
}
// start animation
animate();
开始编写代码之前,我们可以简单观察一下小宇宙的App图标,它能被简单的抽象为三部分:
中心的蓝色球体
周围的黑色环带
颜色为#fbf4f3的背景
其中我们只需要给1和2制作3D模型即可。我们依次创建了Illustration , Shape和Ellipse 之后,将他们添加到画面中就能得到如下图形(图1)了。
此时我们将环带稍加旋转就会,就会出现前面提到的z-fighting问题(如图2所示),这是由于Zdog的计算比较简单,此时Shape和Ellipse 其实都是位于原点出,Zdog并不能真正知道二者谁在谁的前面。我这里采用了一个取巧的方法,即不用一个完整的Ellipse ,而是使用两个半环拼凑在一起,这样当环带和球体的位置关系如图2所示时,就能算出前半环,球体以及后半环三者之间的层叠关系了(如图3)。
实际代码中我们创建了一半环之后可以调用copyGraph 方法再原处生成一个一样的半环然后将其旋转半圈即可凑成一个环,为了让2个半环更像一个整体,我们可以将他们添加到同一个Anchor中(类似于group的概念)。
接下来我们为图像添加旋转效果(虽然实际上环带并不是以这种方式围绕行星转动的)。由于3D中的旋转涉及到一些图形学的知识,这里我会直接给出一种简单好理解的方式。感兴趣的同学这里可以了解一下图形学中使用四元组和欧拉角来描述旋转的方式以及万向锁问题。
首先我们让整个Illustration 在z方向上旋转Zdog.TAU / 8 (TAU是Zdog中的常数,等于2π),之后在每一帧都去修改圆环所在Anchor 在y方向上的rotation。这样对两个不同的object做旋转就能避免同时修改一个物体上z和y方向上的rotation。最后我们可以使用Zdog自带的easeInOut 函数让旋转看起来更加自然一些。
https://codepen.io/SunskyXH/pen/zYKOjGV
Zdog简单方便易上手,但经过上面的编码之后你应该也发现了:了解学习相关的基础知识才能让我们很好得解决开发中具体遇到的问题。最近也在学习blender,用它做了一个和上面类似的模型(怎么感觉没有zdog画的好看呢),希望有机会和同事们多多交流。
