three.js 粒子效果(分别基于 CPU & GPU 实现)

前段时间做了一个基于 CPU 和 GPU 对比的粒子效果丢在学习 WebGL 的 RTX 群里,技术上没有多作讲解,有同学反馈看不太懂 GPU 版本,干脆开一篇文章,重点讲解基于 GPU 开发的版本。

一、概况

废话不多说,先丢上demo,用移动设备更能明显感觉性能差异。

维护粒子位移、颜色、尺寸

维护粒子位移

结论:

同时需要维护多种粒子特征变化时,GPU有明显优势。 只是维护粒子位移时,GPU版本稍流畅,但优势并不明显。 当然,这还得具体到设备,一些中低端Android机器,GPU太渣,不如CPU计算。

二、技术实现

three.js中,粒子效果的实现方式大概分为三种:

1、Javascript直接计算粒子的状态变化,即基于CPU实现; 2、Javascript通知顶点着色器粒子的生命周期,由顶点着色器运行,即基于GPU实现; 3、粒子生成与状态维护全部由片元着色器负责,即屏幕特效,同样是基于GPU中实现。 第3种方式本文暂不介绍。

2.1、基于CPU实现

护位移、颜色、尺寸: http://tgideas.qq.com/2017/three/shader/particle-gpu/cpu.html

维护位移: http://tgideas.qq.com/2017/three/shader/particle-gpu/gpu-position.html

步骤1&2:

首先加载由三维软件制作的几何体,然后生成粒子系统 。

var material = new THREE.PointsMaterial({size:4, color:0xff0000});
var particleSystem = new THREE.Points(geometry , material);

从代码中可以看出,材质是针对整介粒子系统设置的,所以只能维护粒子位移。

如果要维护粒子颜色、尺寸呢?

我们必须为每个粒子设置不同的材质,由此也造成不小的性能损耗 。

步骤3:

使用Tween修改所有顶点位置。

tween = new TWEEN.Tween(pos).to({val: 0}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(1000).onUpdate(callback);
function callback(){
    var val = this.val;
    var particles = particleSystem.geometry.vertices;
    for(var i = 0; i < particles.length; i++) {
        var pos = particles[i];
        pos.x = position1[i].x * val + position2[i].x * (1-val);
        pos.y = position1[i].y * val + position2[i].y * (1-val);
        pos.z = position1[i].z * val + position2[i].z * (1-val);
    }
    particleSystem.geometry.verticesNeedUpdate = true;
}

从代码中可以看出,粒子的状态都是通过Javascript,由CPU来计算。

2.2、基于GPU实现

维护粒子位移、颜色、尺寸: http://tgideas.qq.com/2017/three/shader/particle-gpu/gpu.html

对比CPU实现流程图,我们会发现,Tween并不直接计算所有顶点位置,而是只通知动画运行时间,由顶点着色器来完成具体运算。

既然运算部分在顶点着色器,那么,需要我们自己书写着色器(opengl es),所以我们选用three.js中的ShaderMaterial。

步骤1:

首先生成粒子系统:

var uniforms = {
    texture:{value: new THREE.TextureLoader().load( "dot.png")},
    val: {value: 1.0}
};
var shaderMaterial = new THREE.ShaderMaterial({
    uniforms:     uniforms,
    vertexShader:   document.getElementById('vertexshader').textContent,
    fragmentShader: document.getElementById('fragmentshader').textContent,
    blending:       THREE.AdditiveBlending,
    depthTest:      false,
    transparent:    true
});
particleSystem = new THREE.Points(moreObj, shaderMaterial);

uniforms是连接javascript与着色器的通道。

uniforms.val 即由tween来维护的动画运行值。

vertexShader和fragmentShader,即我们要定义的顶点着色器,和片元着色器,它们负责具体的粒子状态的运算,我们定义在网页中。

步骤2:

定义顶点着色器:

attribute float size; // 粒子尺寸
attribute vec3 position2; // 目标顶点位置
uniform float val; // 动画运行时间
varying vec3 vPos; // 将顶点位置传输给片元着色器

void main() {
    // 计算粒子位置
    vPos.x = position.x * val + position2.x * (1.-val);
    vPos.y = position.y* val + position2.y * (1.-val);
    vPos.z = position.z* val + position2.z * (1.-val);
    // 坐标转换
    vec4 mvPosition = modelViewMatrix * vec4( vPos, 1.0 );
    gl_PointSize = size * ( 300.0 / -mvPosition.z );
    gl_Position = projectionMatrix * mvPosition;

}

three.js内置,自动传递给顶点着色器的变量: attribute position - 顶点坐标 mat4 modelViewMatrix - 模型+视图矩阵 mat4 projectionMatrix - 投影矩阵

定义片元着色器:

uniform sampler2D texture;
varying vec3 vPos;

void main() {
    // 计算粒子颜色,通过位置
    vec3 vColor = vec3(1.0, 0., 0.);
    vColor.r = vPos.z/50.;
    vColor.g = vPos.y/50.;
    vColor.b = vPos.x/50.;

    gl_FragColor = vec4(vColor, 1.0 );
    // 顶点贴图
    gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );

}

步骤3:

负责维护粒子运行时间:

tween = new TWEEN.Tween(pos).to({val: 0}, 2000).onUpdate(callback);
function callback(){
    particleSystem.material.uniforms.val.value = this.val;
}

三、延伸阅读

  • 类THREE.Points做了什么?

其实真没干什么,主要是申明它的type是Points。 当我们执行渲染时,WebGL会绘制Point,即调用gl.drawArrays(gl.POINTS… 而通常,比如type为Mesh时,three.js会调用gl.drawArrays(gl.TRIANGLES…

  • 类THREE.PointsMaterial做了什么?

同样,点材质也是three.js最简单的类之一,相对于基类Material,它多做的事情只是传递了size,即点的尺寸这个值。

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

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

编辑于

我来说两句

3 条评论
登录 后参与评论

相关文章

来自专栏Java技术栈

MySQL数据库开发的 36 条军规!

来自一线的实战经验,主要针对DBA和后端开发人员,总是在灾难发生后,才想起容灾的重要性;总是在吃过亏后,才记得曾经有人提醒过。文末是详细的视频讲解和PDF下载。...

785
来自专栏函数式编程语言及工具

FunDA(9)- Stream Source:reactive data streams

    上篇我们讨论了静态数据源(Static Source, snapshot)。这种方式只能在预知数据规模有限的情况下使用,对于超大型的数据库表也可以说是不...

18810
来自专栏liuchengxu

用 Python 写一个 NoSQL 数据库

本文译自 What is a NoSQL Database? Learn By Writing One In Python.

593
来自专栏杨建荣的学习笔记

MySQL里面的JSON特性

在我们梳理的开发规范里面,明确规定对于lob类型的使用原则只有一个,那就是尽量不要使用。但是很明显,开发同学走到了我们前面,如果你碰到开发同学使用JSON数据类...

950
来自专栏杨建荣的学习笔记

关于MySQL极限值的初步验证纠错(二)

之前写了一篇自己的简单测试总结:关于MySQL极限值的初步验证纠错 今天在这个基础上继续做一些分析,如果说最权威,最全面的材料,那应该非官方文档莫属了...

32811
来自专栏函数式编程语言及工具

Akka(14): 持久化模式:PersistentActor

    Akka程序的特点之一就是高弹性或者强韧性(resilient)的,因为Actor具有自我修复的能力。当Actor模式的程序出现中断情况如:系统崩溃、人...

2158
来自专栏搜云库

Spring Boot 中使用 Java API 调用 Elasticsearch

ElasticSearch 是一个高可用开源全文检索和分析组件。提供存储服务,搜索服务,大数据准实时分析等。一般用于提供一些提供复杂搜索的应用。 Elastic...

94810
来自专栏文渊之博

SQLServer图数据库一些优点

上一篇简要介绍了图数据库的一些基本内容(初识SQL Server2017 图数据库(一)),本篇通过对比关系型一些语法来体现图数据库模式的一些优点,比如查询方...

1856
来自专栏进击的程序猿

orm 系列 之 常用设计模式 The Repository Pattern

本文是orm系列的第一篇,内容来自github上的一个Markdown,清晰的讲述了一些数据库设计上常用的设计模式,并且阐述了orm是什么?

733
来自专栏SDNLAB

ONOS集群选举分析

首先简单介绍下自己,之前是做 floodlight 控制器开发的,鉴于 ODL 和 onos 的如火如荼的发展,如果不对了解点就感觉自己 OUT 了,因此忙里偷...

3306

扫码关注云+社区