three.js粒子过度效果制作(一)

版权声明:本文为作者的原创文章,未经允许不得转载。 https://blog.csdn.net/lin5165352/article/details/83856957

three.js粒子过度效果制作(一)

粒子过度效果在很多网页中经常简单,可以实现从物体A过度到物体B。其原理是改变顶点的位置,按照预先设计好的路径移动。
一种简单的实现方式是,给出在A位置的所有顶点坐标,给出B位置的所有顶点坐标,然后通过过度的方式实现。下面演示一下从一个平面过度到一个球体。A是一个平面,通过PlaneGeometry得到所以粒子的坐标,B是一个球体通过SphereGeometry的生成所有点的坐标。我们也可以通过外部加载的方式来加载一个模型,采用模型的顶点坐标,就可以实现从猿--人的变化。
这里要求A、B两组顶点的数量一致,如果不一致的话,想办法处理一下,比如把数量少的一组顶点复制一部分添加到数组后面。这样得到了重复的顶点,在视觉上不影响。我这里A、B直接生成了462个顶点,不需要处理顶点数量问题。
planeP是数组对象,但是元素缺是{x:0,y:0,x:0}Objectd对象,属于值引用类型。所以planeGeo、planeP的顶点数据都是一样的,改变一个其中一个也会改变。因此在创建
var mesh = new THREE.Points(new THREE.PlaneGeometry(40, 40, 20, 21), pointMaterial)的时候就不要再用planeGeo了,否则动画执行一次就停止了,因为A、B的值都变成了一样。
在这里插入图片描述
粒子位置和颜色的改变可以在GPU或者CPU中完成,二者在性能方面有些差异。GPU在处理顶点数据,片元方面比cpu快速很多,cpu在逻辑判断等编程方便比较有优势。所以在处理大量数据的时候最好交给gpu来处理,代码中粒子的数量很少,在性能上没有什么差异。粒子数量多的时候在移动设备上就会有明显的差别。
下面分别实现CPU 和GPU版本。

CPU版本

cpu版本中粒子位置的变化都是在js代码中实现,过度效果采用tween.js插件。
更新粒子位置后需要设置mesh.geometry.verticesNeedUpdate = true。
步骤

  1. 创建planegeometry–A和spheregeometry–B分别获取它们的顶点坐标。为了方便,创建的两组数据的顶点数量相同。
  2. 将他们的顶点坐标拷贝(clone)到两个数组中。clone()是three.js 中vec3的内置方法,不是JavaScript的。
  3. 创建一个点材质。
  4. 再创建一个THREE.Points–C 对象。这个对象和上面的planegeometry是一样的顶点坐标,但不是同一个数据源。坑:js的原始类型和值引用类型。
  5. 创建tween循环动画。让val的值在1-0之间来回变化。
  6. 创建tween循环体中的回调函数。改变C的顶点坐标从A-B直接变化。
  7. 更新geometry.verticesNeedUpdate ,这个很重要。
  8. 更新TWEEN.update()。
var planeGeo = new THREE.PlaneGeometry(40, 40, 20, 21);
planeGeo.translate(-30, 0, 0);
var sphereGeo = new THREE.SphereGeometry(20, 23, 21);
sphereGeo.translate(30, 0, 0);
var planeP = [], sphereP = [];

planeGeo.vertices.forEach(function (p) {
    planeP.push(p);  // clone()
});
sphereGeo.vertices.forEach(function (p) {
    sphereP.push(p);
});
var pointMaterial = new THREE.PointsMaterial({
    size: 2.0,
    color: 0xffffff,
    map: new THREE.TextureLoader().load("../img/disc.png"),
    side: THREE.DoubleSide,
    alphaTest: 0.5,
    transparent: true,
});
var mesh = new THREE.Points(new THREE.PlaneGeometry(40, 40, 20, 21), pointMaterial); //planeGeo-->clone()-->new THREE.PlaneGeometry(40, 40, 20, 21)

var pos = {val: 1};
var tween = new TWEEN.Tween(pos).to({val: 0}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(1000).onUpdate(callback);
var tweenBack = new TWEEN.Tween(pos).to({val: 1}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(1000).onUpdate(callback);
tween.chain(tweenBack);
tweenBack.chain(tween);
tween.start();
function callback() {
    var val = this.val;
    for (var i = 0; i < mesh.geometry.vertices.length; i++) {
        var pos = {};
        pos.x = planeP[i].x * val + sphereP[i].x * (1 - val);
        pos.y = planeP[i].y * val + sphereP[i].y * (1 - val);
        pos.z = planeP[i].z * val + sphereP[i].z * (1 - val);
        mesh.geometry.vertices[i].set(pos.x, pos.y, pos.z);
    }
    mesh.geometry.verticesNeedUpdate = true;
}
scene.add(mesh);
// 循环更新
TWEEN.update();

GPU版本

彩色的部分是GPU版本,白色的是CPU版本,两者的效果一样。
在这里插入图片描述

步骤

  1. 编写顶点着色器
  2. 编写片元着色器
  3. 创建一个planegeometry–P。获取他的顶点坐标。
  4. 创建points,sizes,colors Float32Array数组,长度是P的三倍。
  5. 向三个数组中写入数组,因为P的顶点坐标书符合数组(数组的每个元素是一个点的三维向量),我们要将它转换成纯数字的数组。vertex.toArray(positions, i * 3)也是three.js vec3内置的方法。是将vertex(x,y,z)的值存放在positions数组中,3是偏移量。
  6. 同理创建一个SphereGeometry –S,主要是获取它的顶点数组,并转化到一个新的一维数组中去。
  7. 创建一个BufferGeometry。并把顶点数据、颜色、粒子大小、待变化的顶点数据写到Attribute中。
  8. 创建ShaderMaterial,采用自定义的着色器。
  9. 创建bufferPoints,使用 BufferGeometry,ShaderMaterial。
  10. 创建tween动画,同上。这次回调函数中只改变material.uniforms.val.value的值。
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 customColor;
attribute vec3 positionB;
uniform float val;
varying vec3 vColor;
void main() {
    vec3 vPos;
    vPos.x = position.x * val + positionB.x * (1.-val);
    vPos.y = position.y * val + positionB.y * (1.-val);
    vPos.z = position.z * val + positionB.z * (1.-val);
    vColor = customColor;
    vec4 mvPosition = modelViewMatrix * vec4( vPos, 1.0 );
    gl_PointSize = size * ( 300.0 / -mvPosition.z )*(abs(val-0.5)+0.2);
    gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
uniform sampler2D texture;
varying vec3 vColor;
void main() {
    gl_FragColor = vec4( color * vColor, 1.0 );
    gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );
    if ( gl_FragColor.a < ALPHATEST ) discard;
}
</script>

var vertices = new THREE.PlaneGeometry(40, 40, 20, 21).translate(-30,0,5).vertices;
var positions = new Float32Array(vertices.length * 3);
var colors = new Float32Array(vertices.length * 3);
var sizes = new Float32Array(vertices.length);
var vertex;
var color = new THREE.Color();
for (var i = 0, l = vertices.length; i < l; i++) {
    vertex = vertices[i];
    vertex.toArray(positions, i * 3);
    color.setHSL(0.01 + 0.5 * (i / l), 1.0, 0.5);
    color.toArray(colors, i * 3);
    sizes[i] = 5 * 0.5;
}
var verticesB =new THREE.SphereGeometry(20, 23, 21).translate(30,0,10).vertices;
var positionsB = new Float32Array(verticesB.length*3);
for (var i = 0, l = verticesB.length; i < l; i++) {
    vertex = verticesB[i];
    vertex.toArray(positionsB, i * 3);
}

var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3));
geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1));
geometry.addAttribute('positionB', new THREE.BufferAttribute(positionsB, 3));
var material = new THREE.ShaderMaterial({
    uniforms: {
        color: {value: new THREE.Color(0xffffff)},
        texture: {value: new THREE.TextureLoader().load("../img/disc.png")},
        val: {value: 1.0}
    },
    vertexShader: document.getElementById('vertexshader').textContent,
    fragmentShader: document.getElementById('fragmentshader').textContent,
    alphaTest: 0.5,
});

bufferPoints = new THREE.Points(geometry, material);
scene.add(bufferPoints);

var pos = {val: 1};
var tween = new TWEEN.Tween(pos).to({val: 0}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(1000).onUpdate(callback);
var tweenBack = new TWEEN.Tween(pos).to({val: 1}, 2000).easing(TWEEN.Easing.Quadratic.InOut).delay(1000).onUpdate(callback);
tween.chain(tweenBack);
tweenBack.chain(tween);
tween.start();

function callback() {
    bufferPoints.material.uniforms.val.value = this.val;
}

猜你喜欢

转载自blog.csdn.net/lin5165352/article/details/83856957