1. 程式人生 > >three.js粒子過度效果製作(一)

three.js粒子過度效果製作(一)

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;
}