要完成這個效果有三個問題要解決
- 自然的粒子分布
- 粒子的拋物線移動
- 粒子的擴散效果
我採用簡單的方式達成
- 用亂數產生分段的粒子團
- 用二次貝茲曲線建立拋物線
- 用粒子團的放大產生擴散效果
因為我用簡單的亂數分布粒子,粒子團會有立方體現象,球體分布會更好,但多個區段連起來就不明顯了。
二次貝茲曲線最大的困難在計算出結尾座標的位置,透過轉換到世界空間去定位出平面高度的座標,再轉換回物體空間來計算出結尾座標。
動畫用 AnimationMixer 去控制,只要定出每段粒子團的開始播放時間,粒子團就會接續的移動。




程式:
- /* 初始化渲染器 */
- const renderer = new THREE.WebGLRenderer({ antialias: true });
- document.getElementById('Container').appendChild(renderer.domElement);
- renderer.setPixelRatio(window.devicePixelRatio);
- renderer.setSize(window.innerWidth, window.innerHeight);
- /* 初始化場景 */
- const scene = new THREE.Scene();
- scene.background = new THREE.Color('#111'); /* 背景顏色 */
- scene.add(new THREE.AmbientLight('#FFF', 0.5)); /* 加入環境光 */
- /* 地面 */
- const floorMesh = new THREE.Mesh(
- new THREE.PlaneGeometry(200, 200),
- new THREE.MeshBasicMaterial({ color: '#DDD', depthWrite: false })
- );
- floorMesh.rotation.x = THREE.MathUtils.degToRad(-90);
- scene.add(floorMesh);
- /* 初始化鏡頭 */
- const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 300);
- camera.position.set(0, 10, 22);
- /* 初始化軌道控制,鏡頭的移動 */
- const orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
- orbitControls.update();
- /* 動畫刷新器 */
- const mixers = [];
- function newMixer(target) {
- const mixer = new THREE.AnimationMixer(target);
- mixers.push(mixer);
- return mixer;
- }
- /* 動畫計時器 */
- const clock = new THREE.Clock();
- /* 渲染週期 */
- function renderCycle() {
- const delta = clock.getDelta();
- mixers.forEach(x => x.update(delta));
- renderer.render(scene, camera);
- requestAnimationFrame(renderCycle);
- }
- renderCycle();
- /*---------------------------------------------------------------*/
- /* 水管物件,水滴粒子群組會附加在這裡 */
- const pipeMesh = new THREE.Mesh(
- new THREE.CylinderGeometry(0.5, 0.5, 9, 16),
- new THREE.MeshBasicMaterial( {color: '#eea236'} )
- );
- pipeMesh.rotation.x = THREE.MathUtils.degToRad(0);
- pipeMesh.position.set(0, 4, 0);
- scene.add(pipeMesh);
- const dripGroup = new THREE.Group();
- dripGroup.position.set(0, 4, 0);
- pipeMesh.add(dripGroup);
- /* 水滴材質 */
- const dripMaterial = new THREE.PointsMaterial({
- map: new THREE.TextureLoader().load('circle.png'),
- color: '#0aa',
- size: 1,
- opacity: 0.7,
- depthWrite: false,
- transparent: true,
- });
- /* 建立水滴,用亂數建立粒子點 */
- for (let i = 0; i < 60; i++) {
- const vertices = [];
- for (let j = 0; j < 40; j++) {
- const x = Math.random() - 0.5;
- const y = Math.random() - 0.5;
- const z = Math.random() - 0.5;
- vertices.push(x, y, z);
- }
- const geometry = new THREE.BufferGeometry();
- geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
- const particles = new THREE.Points(geometry, dripMaterial);
- dripGroup.add(particles);
- newMixer(particles); /* 水滴動畫 */
- }
- /*---------------------------------------------------------------*/
- const cycle = 2; /* 循環週期,會影響水流速度 */
- const scale = 8; /* 擴散大小 */
- const length = 14; /* 水流長度 */
- /* 二次貝茲曲線,用來取得拋物線的座標點 */
- const curve = new THREE.QuadraticBezierCurve3();
- curve.v1.set(0, length, 0); /* 曲線控制點座標 */
- /* 計算結尾座標 */
- const rootPos = dripGroup.getWorldPosition(new THREE.Vector3());
- const quaternion = dripGroup.getWorldQuaternion(new THREE.Quaternion());
- const toPos = curve.v1.clone();
- toPos.applyQuaternion(quaternion); /* 轉換到世界空間 */
- /* 當水流是向上時,增加平面位置 */
- if (toPos.y > (length / 3)) {
- toPos.x *= 1.8;
- toPos.z *= 1.8;
- }
- toPos.y = Math.min(-rootPos.y, toPos.y * 1.5); /* 將結尾拉回平面高度 */
- toPos.applyQuaternion(quaternion.conjugate()); /* 轉換回物體空間 */
- curve.v2.copy(toPos); /* 曲線結尾點座標 */
- /* 建立拋物線及擴散動畫 */
- const points = curve.getPoints(10); /* 曲线分段 */
- const curveTime = [];
- const curvePos = [];
- points.forEach((v, i) => {
- curveTime.push(cycle * i / points.length);
- curvePos.push(v.x, v.y, v.z);
- });
- const posTrack = new THREE.VectorKeyframeTrack('.position', curveTime, curvePos);
- const scaleTrack = new THREE.VectorKeyframeTrack('.scale', [0, cycle], [0, 0, 0, scale, scale, scale]);
- const clip = new THREE.AnimationClip('scale', cycle, [posTrack, scaleTrack]);
- mixers.forEach((mixer, i) => {
- const action = mixer.clipAction(clip);
- action.time = cycle * i / mixers.length;
- action.play();
- });
1 回應:
Here all content so useful and helpful for beginner and experience both.This site is so amazing,This sites gives good knowledge of Threejs.This is very helpful for me.
張貼留言