AI‑Driven 3D Firefly Animation with Three.js and Simplex Noise
This article demonstrates how to combine AI algorithms, specifically Simplex Noise, with Three.js to create a dynamic 3D firefly animation, covering project setup, particle system creation, bloom post‑processing, AI‑driven motion, scene optimization, and provides complete runnable code.
With the rise of Web technologies, Three.js has become a mainstream tool for building 3D graphics and animations, while AI shows strong capabilities in image processing and motion generation. Combining AI with Three.js enables more intelligent, dynamic, and personalized 3D animation experiences.
The core goal is to generate a firefly effect using a particle system in Three.js and drive its motion with an AI algorithm (Simplex Noise). Required knowledge includes basic Three.js usage; readers unfamiliar with Three.js are advised to review prerequisite material.
Technical solution :
Project setup using Vite + Vue, installing three and other dependencies.
Creating a basic Three.js scene, camera, and renderer.
Building a particle system where each particle is a small emissive sphere representing a firefly.
Applying post‑processing with EffectComposer , RenderPass , and UnrealBloomPass to achieve a glowing effect.
Integrating simplex-noise (via createNoise2D ) to generate smooth, natural motion trajectories for each particle.
Updating particle positions each frame using noise values multiplied by a factor, with a time variable driving continuous changes.
Optimizing the scene by adding a skybox background, orbit controls, and handling window resize events.
Below is the complete runnable code (Vue component) that puts all the pieces together:
npm i three --save
npm i simplex-noise <template>
<div ref="threeContainer" class="three-container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as THREE from 'three';
import { createNoise2D } from 'simplex-noise';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
const threeContainer = ref(null);
let scene, camera, renderer, composer, particles, animationFrameId;
const noise = createNoise2D();
let time = 0;
const initScene = () => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 20, 1000);
camera.position.set(0, 150, 500);
camera.lookAt(0, 0, 0);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
threeContainer.value.appendChild(renderer.domElement);
// particles
particles = new THREE.Group();
const particlesCount = 200;
for (let i = 0; i < particlesCount; i++) {
const geometry = new THREE.SphereGeometry(5, 5, 5);
const material = new THREE.MeshStandardMaterial({
emissive: new THREE.Color('#CCFF00'),
emissiveIntensity: 1.5,
});
const particle = new THREE.Mesh(geometry, material);
particle.position.set(Math.random() * 1000 - 500, Math.random() * 1000 - 500, Math.random() * 1000 - 500);
particles.add(particle);
}
scene.add(particles);
// lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const pointLight = new THREE.PointLight(0xffffff, 1.5, 1000);
pointLight.position.set(200, 300, 400);
scene.add(pointLight);
// controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
initPostProcessing();
window.addEventListener('resize', onWindowResize);
};
const initPostProcessing = () => {
composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.3, 0.25);
composer.addPass(bloomPass);
};
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
};
const updateParticles = () => {
particles.children.forEach(particle => {
const { x, y, z } = particle.position;
particle.position.x += noise(x, time) * 3;
particle.position.y += noise(y, time) * 3;
particle.position.z += noise(z, time) * 3;
});
time += 0.1;
};
const animate = () => {
animationFrameId = requestAnimationFrame(animate);
updateParticles();
composer.render();
};
const destroyScene = () => {
cancelAnimationFrame(animationFrameId);
composer.dispose();
renderer.dispose();
window.removeEventListener('resize', onWindowResize);
};
onMounted(() => { initScene(); animate(); });
onUnmounted(() => { destroyScene(); });
</script>
<style>
.three-container { position: fixed; left: 0; right: 0; top: 0; bottom: 0; overflow: hidden; }
</style>The final section summarizes the workflow: create a Three.js scene, generate emissive sphere particles, apply bloom post‑processing, drive particle motion with Simplex Noise, and add scene optimizations such as background textures and interactive controls.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.