叶落为重生每片落下的叶子都是为了下一次的涅槃...^_^

事情没有想象中那么难--JX官网首页3D粒子效果

上周为AlloyTeam/JX做了个简单的官网http://alloyteam.github.com/JX/,当时文档,demo以及其他的附属工具都还没完善,地址就流了出去...

独立的粒子特效demo 可以看这里 http://hongru.github.com/proj/laro/examples/jxhome/

JX 作为webqq的底层,框架本身怎么样,我这里暂时不作评论,很多同学对JX官网home页的opening动画的实现很感兴趣,我这里就简单说一下实现的思路。应该没有大家想象中麻烦。

【关于canvas的使用】

home页的粒子效果 其实是受启发于 Google 2011年的I/O 大会的opening。只是实现思路可能稍有不同。我知道I/O大会上他们的opening用的是Three.js 来做3D渲染,但是具体的细节我也没有深究。我这里只是说一下我自己的实现思路。

目前这个例子效果是基于canvas的,其实用dom也是可以实现的。只是担心大量的dom操作和appendChild,removeChild之类的,对性能和内存的影响蛮大,就没去做兼容。

实现思路就是:基于canvas的 3D 粒子 旋转算法 + 依赖imageData 生成 模型数组。

【3D粒子旋转】

这个部分说麻烦也麻烦,说简单也很简单。其实就是两三个数学公式的事儿。我之前有好几篇随笔都是讲它的,所以我这里也就不重复赘述了。具体可以参考:

【怎么让粒子组成指定形状?】

最开始想到的思路,是自己拼数组出来,类似现在很多html5小游戏的地图数据 一样,通过数组中特定的值来表示某一个 具体的位置,比如这个demo(http://hongru.github.com/test/google-clock.html)里面的 由粒子组成的数字,其实最开始就是一组特定的数组,如下:

    var NUM = [
        '####   ##########  #####################    ',
        '#  #   #   #   ##  ##   #      ##  ##  #    ',
        '#  #   #   #   ##  ##   #      ##  ##  #  # ',
        '#  #   #####################   #########    ',
        '#  #   ##      #   #   ##  #   ##  #   #  # ',
        '#  #   ##      #   #   ##  #   ##  #   #    ',
        '####   #########   #########   #########    '
    ]

由左向右分别是0-9以及冒号。 通过这种数组, # 就代表一个粒子。 这种人工生成 模型 数组的方式还可以处理一些简单的模型,例如这种 数字,或者少数字母等等。

但是如果想要让粒子组成一些稍微复杂一些的图案,人工排列的方式就变得不太靠谱了。

那有什么办法可以让它自动生成这种类似的模型数组呢?

canvas的imageData就有用了。我们知道canvas支持像素级别的操作,可以把一个canvas画布里面每一个像素点都拿出来独立处理。
假如 ,我们把一个canvas画布上不透明或者有颜色的所有像素点都找出来,把每一个像素点都当成一个粒子的话,那么岂不是我们在canvas上写的字,draw的image等等,都可以由很多粒子组成?

比如这个demo(http://hongru.github.com/test/my-text-particle.html):

大家注意这一段代码:

        getImageData: function () {
            var imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
            for (var x = 0; x < imageData.width; x ++) {
                for (var y = 0; y < imageData.height; y ++) {
                    //var i = 4*(x * imageData.height + y);
                    var i = 4*(y * imageData.width + x);
                    if (imageData.data[i + 3] > 128) {
						this.place ++;
                        (this.place%4 == 0) && this.particles.push(new Particle(x, y, this.canvas));
                    }
                }
            }
        },

我们遍历canvas的指定区域像素点,把透明度大于 0.5 的点都找出来,当成一个particle来处理。 当然,为了减少并行的粒子数造成的性能问题,我这里 把符合 条件的粒子 又除以了4.

看到这里,我想应该好多同学都明白了怎么让粒子组成想要的图形或者字符了吧。没错,就是借助图片,利用imageData取出 想要的点。 然后 记录下它的位置和颜色,利用这些信息生成自定义的粒子。  

当然,这里还有个小技巧,就是关于图片大小的控制,因为imageData是像素级别的,所以太大图片,必然导致过多的粒子,我们做这种效果并不需要多精确的形状,而且太多的粒子在后续渲染上 是个 极大的消耗,所以 通常 控制在 20*20 大小,算比较合适,也就是保持在 400 个粒子左右变化, 在图形组成和 变换性能上 取一个权衡点。

【最后,关于进程的控制】  

 这也是一个关键点,通常,一个稍有经验的开发者,有了上面说的几点知识后,应该就能大致写出类似的效果了,但是如果涉及到像这种连续几个不同的动画 来 转换的时候, 进程的控制 会让人觉得头疼, 因为从一个动画 进入到下一个动画的时候,为了降低性能消耗 ,需要把上一次 使用的粒子 请出循环数组, 而是用下一个 动画需要的 模型数组来变化。另外 我们还需要在 动画 连续帧变化 的loop 中控制每个动画过程的时间, 以便到了某个节点 可以进入到下一个 动画进程。

为了很好的控制进程,简化编码思路,我这里引入了一个 【有限状态机 FSM】的概念。引入这个概念之后,基本上相似的动画,每个动画进程可以独立成 一个 status来处理,互相基本没有耦合,只用关心自己即可。而且代码都具有相似的结构

进入状态-update更新-重绘draw-状态转换条件transition- 离开状态leave

 这很好的帮助了我们简化思路。便利的控制整个进程。

关于有限状态机 更多的信息和 源码以及使用方法, 可以参照源码:
https://github.com/hongru/Laro/blob/master/src/game/fsm.js
https://github.com/hongru/Laro/blob/master/src/game/state.js

【后记】

思路大致就是这样,当然,细节方面还有很多需要注意的地方,如果感兴趣的同学不妨一点点尝试,从3D 旋转算法开始, 完成一个类似的特效。 对编码思路 以及 简易的图形学算法 应该会有些帮助。

大家晚安 : ) 

posted on 2012-03-28 00:43 岑安 阅读(...) 评论(...) 编辑 收藏

导航

统计信息

News