23、《每周一点canvas动画》——3维环境搭建

在上一篇《每周一点canvas动画》——从2D到3D中,我们讨论了要在2D的平面实现3D的效果,是一件多么复杂的事情。但是对于一些简单的3D效果,使用webGL不仅有杀鸡用牛刀的感觉,而且浏览器的兼容性也是一个很大的问题。所以,我们考虑在2D的canvas中去模拟3D的效果,将其作为我们项目中的降级方案。也许你对在2D的canvas中去模仿3D的效果保有怀疑,这里我先给一个小小的demo,让你直观的感受下,canvas模拟的3D效果到底如何?

是不是很逼真,立体效果不错吧!我记得前段时间淘宝首页有一个简单的3D效果(可是我没找到,不过有那么个印象),以为用了webGL。其实,canvas完全就可以模拟。下面我们介绍3D环境的搭建。

1.坐标系统

前面部分的动画内容之所以是2维的,是因为我们所有的动画都基于一个2维坐标系统。要实现3维的动画效果,除了x轴,y轴,我们还需要另一条坐标轴—— z轴。但是,canvas先天是不具备这条坐标轴的。所以,我们需要手工的去设定这条坐标轴。

在坐标轴的设定上,我们有两种选择,如下图所示:

第一种叫做左手坐标系(左手的食指,中指,大拇指三者垂直,大拇指指向自己),第二种叫做右手坐标系(右手的食指,中指,大拇指三者垂直,大拇指指向外面)。他们之间的区别如图所示,z轴的指向不同。以右手坐标为例,反映到物体的表现上(如果只是考虑物体的大小),当物体朝着z轴的正向运动,那么我们会看到物体变得越来越小。就如第一幅效果图中展示的那样,当物体朝着负方向运动的时候。我们可以看到物体变得越来越大,产生一种朝我们迎面飞来的感觉。另外,在本文中我们默认使用的是右手坐标系。

2.透视(perspective)

2.1 概念

不管你是叫他景深也好,透视也罢。perspective是3D场景中最重要的概念之一,如果你使用过three.js,就会发现camera中有一个参数,就是perspective。另一个你很熟悉的场景,恐怕就是css3中的perspective了吧!如果你对这其中的任何一个有了解,那么perspective的概念就很好理解了。

perspective的作用是确定物体是靠近我们,还是远离我们。因为在2维的空间中,我们只需要两个坐标就可以确定一个物体在平面上的位置。但是,在3维的环境中这是行不通的,两个物体可能具有相同的x坐标,y坐标。但是只要z坐标不相同,我们就不能判断他们两的位置是不是重合的。

那么,怎么在2维的平面去体现透视效果呢?各位看官请试想一下,当篮球从远处飞向你的时候(这里如果我们只考虑一个元素——篮球大小),篮球是不是越来越大呢,当你把篮球扔出去,它是不是越来越小呢!Ok,就是这个理。在2维的平面,我们想要让物体感觉向我们走来,就放大它,同理远离的效果就是让它变小。

除了让它的大小发生变化。另一个比较重要的点是:当物体远离直至消失的过程中,要想模拟三维的消失效果,我们必须让物体的x坐标,y坐标向消失点移动。这个消失点你可以理解为汽车向远方驶去,最终它会逐渐变成黑点消失在地平线上,这个黑点就是消失点。

所以总结下来,我们在perspective这一块要做两件事:

  1. 放大或者缩小物体
  2. 让它靠近或者远离消失点
2.2 公式

上面的概念中我们了解了要想形成透视的效果,我们需要做两件事。下图展示了一个侧面视图,人眼代表观察点,蓝色的球体代表屏幕中的物体,人眼距离屏幕的距离为fl,物体与屏幕之间有一段距离z值(这个值在成像的时候并不存在,反映到物体大小的变化上)

物体的大小与这 fl , z 两者之间满足下面的关系:

scale = fl / (fl + z) = 1/ ( 1+ z/fl)

fl的值就如 css3 中我们设定的 perspective 值。 同理,这里也是我们自己设定的一个值,200,300,500都无所谓啦。

从公式中我们可以得出:

scale的值一般情况下范围为(0,1.0),这个值之后会用在缩放物体的比例与靠近消失点的比例。

试想两种比较极端的情况:

当z轴无限大的时候(也就是朝着z轴的正向持续运动),scale的值就会趋近于0。当z的距离趋近于-fl的时候,scale的值就会变得很大,就像是戳进我们的眼里。

以我们前面使用的小球为例,在draw方法上我们定义

context.scale(this.scaleX, this.scaleY)

当缩放比例确定后,一方面我们确定了球体大小的变化,另一方面我们用物体的坐标乘以这个比例就可以得到物体的新坐标。

可能这样说比较抽象,我们假定 fl=200 ,这时物体的z坐标等于0,由公式可得 scale=1.0 ,那么物体的大小不变,物体的位置不变。如果 z=200 ,那么 scale=0.5 。物体的大小变为原来的 1/2,同时我们要让它现在的坐标乘以缩放比例 scale,得到新的位置。如果原来的为(200,300),那么新坐标就为(100,150)。具体效果如下图:

3.代码实现

先上效果图

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    ball = new Ball(40, "red"),
    mouse = utils.captureMouse(canvas);
var xpos = 0,
    //物体的 3D坐标
    ypos = 0,
    zpos = 0,
    fl = 250,
    //距离屏幕的距离(焦距) 
    vpX = canvas.width / 2,
    //消失点 
    vpY = canvas.height / 2;

window.addEventListener('keydown', function(e) {
    if (e.keyCode === 38) { //up
        zpos += 5;
    } else if (e.keyCode === 40) {
        zpos -= 5;
    }
}, false);
(function drawFrame() {
    window.requestAnimationFrame(drawFrame, canvas);
    context.clearRect(0, 0, canvas.width, canvas.height);
    if (zpos > -fl) {
        var scale = fl / (fl + zpos); //缩放比列 
        xpos = mouse.x - vpX;
        ypos = mouse.y - vpY;
        ball.scaleX = ball.scaleY = scale; //物体大小变化 
        ball.x = vpX + xpos * scale; //新坐标 
        ball.y = vpY + ypos * scale;
        ball.visible = true; //物体可见 
    } else {
        ball.visible = false
    }
    if (ball.visible) {
        ball.draw(context);
    }
}())

代码相对来说比较简单。首先,我们设定物体的3D坐标 xpos,ypos,zpos,初始默认为0。然后设定焦距fl = 250,最后设置消失点(vpX, vpY)。这里需要注意的地方是,我们设置的消失点为画布的中心。如果不这样做,物体就会向画布的左上角(0,0)处汇集,这并不是我们想要的效果。

接下来,在动画循环中我们根据公式计算缩放比例 scale,然后作用于 ball 的 scale 上,最后计算物体的新位置。这里有个值得注意的点是,我们在外层加了一个判定条件(zpos > -fl),这样做的目的是当物体太大的时候,超出了 canvas 画布我们就不再绘制它。

这一节的内容是整个3维效果的核心,后面的所有效果都是基于此。所以请务必弄明白!

阅读更多

没有更多推荐了,返回首页