Three.js 粒子系统学习小记:礼花效果实现

背景知识

在3D建模过程中,当我们需要创建很多细小的物体时,并不会一个个地创建这些物体,而是通过创建粒子,粒子可以模拟很多效果,例如烟花、火焰、雨滴、雪花、云朵等等。Three.js提供了各种的粒子系统创建方式。从官网例子的demo来看,可以总结分为两类,分别是Points和Sprite。

Points粒子系统的创建

首先看看threejs官网对Points的解释:

A class for displaying points. The points are rendered by the WebGLRenderer using gl.POINTS.

也就是说,points必须要通过WebGLRenderer渲染方式来创建(重点强调这个是因为官网的demo中粒子的创建一般通过两种方式WebGLRendererCanvasRenderer

renderer = new THREE.WebGLRenderer( );

points粒子系统的创建过程一般可以总结为三步:

1.创建一个几何对象Geometry(也可以是外部导入的模型),然后基于几何体自身的顶点集合geometry.vertices创建粒子(即 将网格转化为粒子),每个顶点将代表粒子系统中的每个粒子。

var geometry = new THREE.SphereGeometry( 500, 50, 50 );

2.创建粒子材质,Points对应PointsMaterial,来实现每个粒子的图案。实现的方式可以是加载图片纹理(demo地址)或者canvas纹理,又或者不采用纹理直接创建正方体粒子(demo地址)。

var material = new THREE.PointsMaterial( { ... } ) ;

3.通过以上两步的创建一个Points类的物体,这个物体则代表了整个粒子系统。

var mesh = new THREE.Points( geometry, material );

下图展示了将一个球形网格模型转化成一个球形粒子系统:

另外,也可以创建一个自定义的几何体为其添加顶点集合。

Sprite粒子系统的创建

A sprite is a plane that always faces towards the camera, generally with a partially transparent texture applied.

threejs官网如是说,sprite是一直面向camera的平面,并且我们可以用其创建基于屏幕坐标移动、定位和缩放的对象,而不影响三维场景中的其他物体(做到互不影响必须单独创建一个用于sprite对象的camera和render)。另外sprite对象还有一个特点是不能生成阴影。

创建过程:

1.创建粒子材质,如果渲染器是canvasRender则可以直接引用canvas画布,另外也可以加载图片纹理和canvas纹理。

var spriteMaterial = new THREE.SpriteMaterial( { ... } );

2.创建粒子

var sprite = new THREE.Sprite( spriteMaterial );

3.另外还可以为粒子设置position(如果将每个粒子设置为一个几何体的每个顶点,则效果和point粒子系统相似)。

sprite.position.x = 0;
sprite.position.y = 0;
sprite.position.z = 0;

4.为了方便控制,还可以将粒子加进同一个组内,变成一个粒子系统。

for ( var i = 0; i < len; i++ ) { //len表示粒子数目
    ...
    group.add( sprite );
}

粒子材质

先说说每个粒子材质图形的创建,一般是通过canvas描绘或通过加载图片的方式来格式化粒子:

1.直接引用画布,当通过CanvasRenderer渲染时:

renderer = new THREE.CanvasRenderer();

你可以直接在每个粒子的材质对象里直接引用HTML5画布。例如:

//画点
var PI2 = Math.PI * 2;
var program = function ( context ) {
    context.beginPath();
    context.arc( 0, 0, 0.5, 0, PI2, true );
    context.fill();
};
//为每个点附上材质
var material = new THREE.SpriteCanvasMaterial( {
    color: Math.random() * 0x808008 + 0x808080,
    program: program
} );

上文提到,points对象只能通过WebGLRender进行渲染,所以pointsmaterial和这种方式是无缘了。WebGLRender渲染时的粒子如果需要用canvas实现,则必须加多一步将canvas转化为纹理,在通过map属性加载进来。

2.加载图片纹理:

var textureLoader = new THREE.TextureLoader();
var sprite = textureLoader.load( "textures/sprites/snowflake.png" );
var material = new THREE.PointsMaterial( { size: size, map: sprite, blending: THREE.AdditiveBlending, depthTest: false, transparent : true } );

从上面的代码可以看到,粒子材质的属性还有很多,详情点击:pointsMaterial spriteMaterial

礼花效果实现

应用上面的知识点,小编做了一个礼花的小demo,礼花的展示效果大致分为三步:

  1. 绽放前,飞线动画向上运动。
  2. 绽放出球形礼花。
  3. 往下往外坠落礼花消失。

实现过程如下:

  1. 创建粒子,在这个例子中小编应用了canvasRender结合spriteMaterial,用canvas画出每个粒子(圆点),然后为它们设置初始位置都在同一点。
var PI2 = Math.PI * 2;
//画点
var program = function ( context ) {

    context.beginPath();
    context.arc( 0, 0, 0.5, 0, PI2, true );
    context.fill();

};
group = new THREE.Group();
scene.add( group );

for ( var i = 0; i < vl; i++ ) {
    //为每个点附上材质
    var material = new THREE.SpriteCanvasMaterial( {
        color: Math.random() * 0x808008 + 0x808080,
        program: program
    } );

    particle = new THREE.Sprite( material );
    particle.position.x = 0;
    particle.position.y = -500;
    particle.position.z = 0;
    particle.scale.x = particle.scale.y = Math.random() * 4 + 2;
    ...
    group.add(particle);
}
  1. 飞线动画实现

在每一帧的render中,判断每个粒子的y坐标小于一定值时,以不同的速度按照正弦曲线的轨迹向上运动,形成飞线动画的效果。

function fsin(x){     //正弦函数
    return 50*Math.sin(0.8*x*Math.PI/180);
}
delta = 10 * clock.getDelta();
var speed = 80;
delta = delta < 2 ? delta : 2;
var dur = new Date().getTime() - t1; 
if (dur < 1800) {     //控制飞线动画时间
    var k = 0;
    group.traverse(function(child) {
        if (child.position.y < 0) {
            child.position.y += delta * speed * Math.random();
            child.position.x = fsin(child.position.y);    
        }            
    });
}
renderer.render( scene, camera );
  1. 绽放效果是结合tweenMax实现的,在粒子初始化的时候,为了实现绽放时的球形效果,定义了一个球体几何体,得到球体的总顶点数作为粒子的总数,用tweenMax设置了每个粒子在绽放到最大时的位置,即了相应的球体的顶点位置再增减一些随机数,并设置随机的绽放时间,来达到错落有致的效果。
//创建一个球型用作最后的形状
var geometry = new THREE.SphereGeometry( 500, 50, 50 );
var vl = geometry.vertices.length;

for ( var i = 0; i < vl; i++ ) {

    ...
    particle = new THREE.Sprite( material );
    ...

    var timerandom = 1*Math.random();
    //为每个点加动画
    TweenMax.to(
        particle.position,
        timerandom,
        {x:geometry.vertices[i].x+(0.5-Math.random())*100,y:geometry.vertices[i].y+(0.5-Math.random())*100,z:geometry.vertices[i].z+Math.random()*100,delay:1.8,} 
    );

    group.add( particle );
}

4.礼花消失,同样是使用tweenMax

TweenMax.to(
          particle.position,
           2*timerandom,
          {y:'-600',z:'300',delay:1.8+timerandom,} 
 );

demo展示:地址

整个花的形状:地址

花的形状是用极坐标函数写的:传送门

项目代码地址:

https://github.com/kiroroyoyo/threejsexample/tree/master/particle

学习心得

  1. 在threejs的粒子系统中,每个粒子其实是一张图片或者一个canvas而不是3D的物体。
  2. 当粒子量级非常大时,可以用BufferGeometry来代替Geometry的顶点,因为它可以将数据存储在缓冲区中,减少数据传递到GPU的成本,同时因为在缓冲区,所以更适合静态的物体。
  3. threejs版本更新了很多次,粒子系统的创建也改了很多次名字,从THREE.ParticleSystem到THREE.PointCloud到THREE.Points,在学习实例时应注意。本文例子应用的是目前最新的r84。

参考文章:

  1. https://threejs.org/docs/
  2. https://www.solutiondesign.com/blog/-/blogs/webgl-and-three-js-particles
  3. 《Three.js开发指南》

谢谢阅读,如有问题请各位大大指正!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏落影的专栏

OpenGL ES实践教程(九)OpenGL与视频混合

前言 前面的实践教程: OpenGL ES实践教程1-Demo01-AVPlayer OpenGL ES实践教程2-Demo02-摄像头采集数据和渲染 O...

4455
来自专栏IT派

用Python画中国地图(下)

在上一篇文章《用Python画一个中国地图》中,我们简单描述了一下如何用Python快速画出一个中国地图的轮廓,似乎没有什么实用价值,这一次我们用实际数据填充它...

1043
来自专栏UML

维恩图 (Venn Diagram) 示例:UML vs. SysML

维恩图是集合之间以及共享某些共同点的对象组之间关系的图示。有时,维恩图被用作视觉头脑风暴工具,用于比较和对比两种(有时是三种或更多种)不同的东西。比较是看事物的...

1686
来自专栏数据小魔方

超强脑洞第五弹——ggplot 构造连环饼图

今天这篇之前曾有涉略过,就是利用ggplot的辅助插件工具——scatterpie制作基于气泡图的饼图,之前曾在地图图层上演示过此种类似图表,不过这里我将其与折...

3525
来自专栏菩提树下的杨过

silverlight中"制作逐帧动画"/"播放gif"收集

“逐帧动画”与“播放GIF”貌似二个风马牛不相干的问题,其实不然! 因为silverlight中的image控件不支持直接把gif动画做为source,所以象做...

1827
来自专栏Alan's Lab

swift 写 iOS 空心字描边动画

http://oleb.net/blog/2010/12/animating-drawing-of-cgpath-with-cashapelayer/ http...

793
来自专栏落影的专栏

OpenGL ES实践教程(二)摄像头采集数据和渲染

教程 这一篇教程是摄像头采集数据和渲染,包括了三部分内容,渲染部分-OpenGL ES,摄像头采集图像部分-AVFoundation和图像数据创建纹理部分-G...

3205
来自专栏前端说吧

css笔记 - transition学习笔记(二)

transition过渡 :用于当元素 从一种样式变换为另一种样式 时为元素添加效果。

663
来自专栏null的专栏

《数学之美》拾遗——TF-IDF

开篇序     在学习机器学习的过程中,我写了简单易学的机器学习算法的专题,依然还有很多的算法会陆续写出来。网上已经有很多人分享过类似的材料,我只是通过自己的理...

28410
来自专栏点滴积累

geotrellis使用(三十四)矢量瓦片技术研究——矢栅一体化

前言 本文所涉及技术与Geotrellis并无太大关系,仅是矢量瓦片前端渲染和加载技术,但是其实我这是在为Geotrellis的矢量瓦片做铺垫。很多人可能会说,...

48610

扫码关注云+社区