three.js粒子過度效果製作(一)
阿新 • • 發佈:2018-11-16
three.js粒子過度效果製作(一)
粒子過度效果在很多網頁中經常簡單,可以實現從物體A過度到物體B。其原理是改變頂點的位置,按照預先設計好的路徑移動。
一種簡單的實現方式是,給出在A位置的所有頂點座標,給出B位置的所有頂點座標,然後通過過度的方式實現。下面演示一下從一個平面過度到一個球體。A是一個平面,通過PlaneGeometry得到所以粒子的座標,B是一個球體通過SphereGeometry的生成所有點的座標。我們也可以通過外部載入的方式來載入一個模型,採用模型的頂點座標,就可以實現從猿--人
的變化。
這裡要求A、B兩組頂點的數量一致,如果不一致的話,想辦法處理一下,比如把數量少的一組頂點複製一部分新增到陣列後面。這樣得到了重複的頂點,在視覺上不影響。我這裡A、B直接生成了462個頂點,不需要處理頂點數量問題。
planeP是陣列物件,但是元素缺是{x:0,y:0,x:0}
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。
步驟
- 建立planegeometry–
A
和spheregeometry–B
分別獲取它們的頂點座標。為了方便,建立的兩組資料的頂點數量相同。 - 將他們的頂點座標拷貝(clone)到兩個陣列中。clone()是three.js 中vec3的內建方法,不是JavaScript的。
- 建立一個點材質。
- 再建立一個THREE.Points–
C
物件。這個物件和上面的planegeometry是一樣的頂點座標,但不是同一個資料來源。坑:js的原始型別和值引用型別。 - 建立tween迴圈動畫。讓val的值在1-0之間來回變化。
- 建立tween迴圈體中的回撥函式。改變
C
的頂點座標從A
-B
直接變化。 - 更新geometry.verticesNeedUpdate ,這個很重要。
- 更新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版本,兩者的效果一樣。
步驟
- 編寫頂點著色器
- 編寫片元著色器
- 建立一個planegeometry–
P
。獲取他的頂點座標。 - 建立points,sizes,colors Float32Array陣列,長度是
P
的三倍。 - 向三個陣列中寫入陣列,因為
P
的頂點座標書符合陣列(陣列的每個元素是一個點的三維向量),我們要將它轉換成純數字的陣列。vertex.toArray(positions, i * 3)也是three.js vec3內建的方法。是將vertex(x,y,z)的值存放在positions陣列中,3是偏移量。 - 同理建立一個SphereGeometry –
S
,主要是獲取它的頂點陣列,並轉化到一個新的一維陣列中去。 - 建立一個BufferGeometry。並把頂點資料、顏色、粒子大小、待變化的頂點資料寫到Attribute中。
- 建立ShaderMaterial,採用自定義的著色器。
- 建立bufferPoints,使用 BufferGeometry,ShaderMaterial。
- 建立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;
}