three.js粒⼦效果(分别基于CPUGPU实现)
前段时间做了⼀个基于CPU和GPU对⽐的粒⼦效果丢在学习WebGL的⾥,技术上没有多作讲解,有同学反馈看不太懂GPU版本,⼲脆开⼀篇⽂章,重点讲解基于GPU开发的版本。
⼀、概况
废话不多说,先丢上demo,⽤移动设备更能明显感觉性能差异。
维护粒⼦位移、颜⾊、尺⼨:
维护粒⼦位移:
结论:
同时需要维护多种粒⼦特征变化时,GPU有明显优势。
只是维护粒⼦位移时,GPU版本稍流畅,但优势并不明显。
当然,这还得具体到设备,⼀些中低端Android机器,GPU太渣,不如CPU计算。
⼆、技术实现
three.js中,粒⼦效果的实现⽅式⼤概分为三种:
1、Javascript直接计算粒⼦的状态变化,即基于CPU实现;
2、Javascript通知顶点着⾊器粒⼦的⽣命周期,由顶点着⾊器运⾏,即基于GPU实现;
3、粒⼦⽣成与状态维护全部由⽚元着⾊器负责,即屏幕特效,同样是基于GPU中实现。
第3种⽅式本⽂暂不介绍。
2.1、基于CPU实现
步骤1&2:
⾸先加载由三维软件制作的⼏何体,然后⽣成粒⼦系统。
var material = new THREE.PointsMaterial({size:4, color:0xff0000});
var particleSystem = new THREE.Points(geometry , material);
从代码中可以看出,材质是针对整介粒⼦系统设置的,所以只能维护粒⼦位移。
如果要维护粒⼦颜⾊、尺⼨呢?
我们必须为每个粒⼦设置不同的材质,由此也造成不⼩的性能损耗。
步骤3:
使⽤Tween修改所有顶点位置。
var 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 = ry.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);
}
}
从代码中可以看出,粒⼦的状态都是通过Javascript,由CPU来计算。
2.2、基于GPU实现
对⽐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:  ElementById('vertexshader').textContent,
fragmentShader: ElementById('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() {
// 计算粒⼦位置
代码运行js特效
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,即点的尺⼨这个值。