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 条评论
登录 后参与评论

相关文章

来自专栏人人都是极客

GPU渲染之OpenGL的GPU管线

GPU渲染流水线,是硬件真正体现渲染概念的操作过程,也是最终将图元画到2D屏幕上的阶段。GPU管线涵盖了渲染流程的几何阶段和光栅化阶段,但对开发者而言,只有对顶...

1132
来自专栏Coco的专栏

不可思议的混合模式 background-blend-mode

1503
来自专栏图形学与OpenGL

4.4.2 OpenGL几何变换编程实例

/* 三维旋转变换,参数:旋转轴(由点p1和p2定义)和旋转角度(thetaDegrees)*/

472
来自专栏進无尽的文章

UI篇-Layer几个关键点补充

强大的UIView是基于 CALayer实现的,它的重要性不言而喻,相信大家也都有自己的研究和理解,今天这片文章里的内容是几个关键点的补充。

541
来自专栏Flutter入门

Android OpenGL ES(三)-平面图形

前两章,其实我们已经明白了绘制平面图形的套路了。 接下来我们按照套路继续画其他的图形。

712
来自专栏移动开发面面观

OpenGL ES——导入.stl格式的3D模型

944
来自专栏Flutter入门

Android OpenGL ES(八) - 简单实现绿幕抠图

这里的关键是,判断颜色的范围。这里简单的认定 g>140.0 && r<128.0 && b<128.0 时为绿色。当是绿色的时候,就将其颜色换成白色。同时al...

612
来自专栏练小习的专栏

比例字体&等宽字体

我们都知道等宽字体和比例字体的区别,就在于比例字体(Monospaced Font)即每个字母宽度是按一定比例自动调整的,而等宽字体(Proportional ...

3376
来自专栏CNN

Android OpenGL添加纹理

转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】

613
来自专栏NewbieWeb

WebGL ThreeJS学习总结三

总结三主要是学习ThreeJS框架和一些数学知识等,包括相机、光线、形状、变换、视角控制、焦点控制、矩阵和弧度等;并根据所学知识实现一些稍复杂的效果。

732

扫码关注云+社区