├── LICENSE ├── README.md ├── FireFlyMaterial.ts └── FireFly.ts /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [your name or organization] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | 1. The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | 2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Three.js Fireflies Material Component and 2 | Creating fireflies in three.js. FireFly Shader. FireFly Material. 3 | 4 | [⚡️Demo Link](https://fireflies-demo.vercel.app/) 5 | 6 | This project contains two main components for creating a firefly effect in a Three.js scene: `FireFlyMaterial` and `FireFlies`. You can use these components independently to customize your firefly effects. 7 | 8 | ## 1. FireFlyMaterial 9 | 10 | The `FireFlyMaterial` class extends `ShaderMaterial` and allows you to create a custom shader material specifically designed for rendering fireflies. 11 | 12 | ### How to Use `FireFlyMaterial` 13 | 14 | 1. **Import the Material**: 15 | ```javascript 16 | import { FireFlyMaterial } from './FireFlyMaterial'; 17 | ``` 18 | 19 | 2. **Instantiate the Material**: 20 | ```javascript 21 | const fireflyMaterial = new FireFlyMaterial(); 22 | ``` 23 | 24 | 3. **Set Uniforms**: 25 | You can set various uniforms for the material, such as color and size. 26 | ```javascript 27 | fireflyMaterial.setColor(new Color('#ffffff')); // Set the firefly color 28 | fireflyMaterial.setRadius(0.1); // Set the firefly radius 29 | ``` 30 | 31 | 4. **Apply to Geometry**: 32 | You can apply this material to any geometry (like `PlaneGeometry`). 33 | ```javascript 34 | const fireflyGeometry = new PlaneGeometry(0.2, 0.2); 35 | const fireflyMesh = new Mesh(fireflyGeometry, fireflyMaterial); 36 | scene.add(fireflyMesh); 37 | ``` 38 | 39 | ## 2. FireFlies 40 | 41 | The `FireFlies` class handles instancing and positioning of multiple fireflies in a scene, allowing for dynamic updates. 42 | 43 | ### How to Use `FireFlies` 44 | 45 | 1. **Import the Class**: 46 | ```javascript 47 | import { FireFlies } from './FireFlies'; 48 | ``` 49 | 50 | 2. **Create an Instance**: 51 | You can create an instance of the `FireFlies` class with desired properties. 52 | ```javascript 53 | const fireflies = new FireFlies({ 54 | groupCount: 3, 55 | firefliesPerGroup: 100, 56 | groupRadius: 10, 57 | noiseTexture: yourNoiseTexture // Optional 58 | }); 59 | ``` 60 | 61 | 3. **Add to Scene**: 62 | The fireflies are automatically added to the scene upon instantiation. 63 | 64 | 4. **Update Loop**: 65 | Call the `update` method within your animation loop to update firefly animations. 66 | ```javascript 67 | function animate() { 68 | requestAnimationFrame(animate); 69 | fireflies.update(); // Update fireflies 70 | renderer.render(scene, camera); 71 | } 72 | animate(); 73 | ``` 74 | 75 | ## Example Usage 76 | 77 | Here is a brief example of how both components can be used together: 78 | 79 | ```javascript 80 | import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'; 81 | import { FireFlyMaterial } from './FireFlyMaterial'; 82 | import { FireFlies } from './FireFlies'; 83 | 84 | // Initialize scene, camera, and renderer 85 | const scene = new Scene(); 86 | const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); 87 | const renderer = new WebGLRenderer(); 88 | renderer.setSize(window.innerWidth, window.innerHeight); 89 | document.body.appendChild(renderer.domElement); 90 | 91 | // Create fireflies 92 | const fireflies = new FireFlies({ 93 | groupCount: 3, 94 | firefliesPerGroup: 100, 95 | groupRadius: 10, 96 | }); 97 | 98 | // Animation loop 99 | function animate() { 100 | requestAnimationFrame(animate); 101 | fireflies.update(); 102 | renderer.render(scene, camera); 103 | } 104 | animate(); 105 | ``` 106 | 107 | ## Conclusion 108 | 109 | With `FireFlyMaterial` and `FireFlies`, you can easily create dynamic and visually appealing firefly effects in your Three.js scenes. Feel free to customize the parameters and integrate them as needed in your projects! 110 | 111 | -------------------------------------------------------------------------------- /FireFlyMaterial.ts: -------------------------------------------------------------------------------- 1 | import { ShaderMaterial, AdditiveBlending, Color } from 'three'; 2 | 3 | /** 4 | * Options for the FireFlyMaterial constructor. 5 | */ 6 | interface FireFlyMaterialOptions { 7 | uTime?: number; // Time for animation 8 | uFireFlyRadius?: number; // Radius for fireflies 9 | uColor?: Color; // Color for fireflies 10 | } 11 | 12 | /** 13 | * FireFlyMaterial class rendering firefly particles with customizable properties. 14 | */ 15 | export class FireFlyMaterial extends ShaderMaterial { 16 | constructor(options: FireFlyMaterialOptions = {}) { 17 | // Destructure options with default values 18 | const { uTime = 0, uFireFlyRadius = 0.1, uColor = new Color('#ffffff') } = options; 19 | 20 | // Call the parent constructor 21 | super({ 22 | transparent: true, 23 | blending: AdditiveBlending, 24 | uniforms: { 25 | uTime: { value: uTime }, 26 | uFireFlyRadius: { value: uFireFlyRadius }, 27 | uColor: { value: uColor } 28 | }, 29 | vertexShader: `uniform float uTime; 30 | varying vec2 vUv; 31 | varying float vOffset; 32 | 33 | void main() { 34 | // Apply noise to the particle motion 35 | float displacementX = sin(uTime + float(gl_InstanceID) * 0.10) * 0.5; 36 | float displacementY = sin(uTime + float(gl_InstanceID) * 0.15) * 0.5; 37 | float displacementZ = sin(uTime + float(gl_InstanceID) * 0.13) * 0.5; 38 | 39 | // Make the object face the camera like a pointMaterial. 40 | float rotation = 0.0; 41 | vec2 rotatedPosition = vec2( 42 | cos(rotation) * position.x - sin(rotation) * position.y, 43 | sin(rotation) * position.x + cos(rotation) * position.y 44 | ); 45 | 46 | vec4 finalPosition = viewMatrix * modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0); 47 | finalPosition.xy += rotatedPosition; 48 | 49 | // Make the particles move 50 | finalPosition.x += displacementX; 51 | finalPosition.y += displacementY; 52 | finalPosition.z += displacementZ; 53 | 54 | gl_Position = projectionMatrix * finalPosition; 55 | 56 | vUv = uv; 57 | vOffset = float(gl_InstanceID); 58 | }`, 59 | fragmentShader: `varying vec2 vUv; 60 | uniform float uTime; 61 | uniform float uFireFlyRadius; 62 | uniform vec3 uColor; 63 | varying float vOffset; 64 | 65 | void main() { 66 | float distance = length(vUv - 0.5); 67 | float glow = smoothstep(0.50, uFireFlyRadius, distance); 68 | float disk = smoothstep(uFireFlyRadius, uFireFlyRadius - 0.01, distance); 69 | 70 | // Add a flashing effect using the time uniform 71 | float flash = sin(uTime * 3.0 + vOffset * 0.12) * 0.5 + 0.5; // Adjust the frequency and amplitude as desired 72 | float alpha = clamp((glow + disk) * flash, 0.0, 1.0); 73 | 74 | vec3 glowColor = uColor * 3. * flash; 75 | vec3 fireFlyColor = uColor * 3.; 76 | 77 | vec3 finalColor = mix(glowColor, fireFlyColor, disk); 78 | 79 | gl_FragColor = vec4(finalColor, alpha); 80 | }` 81 | }); 82 | } 83 | 84 | /** 85 | * Update time uniform for animation. 86 | * @param {number} time - The time to update the uniform with. 87 | */ 88 | updateTime(time: number): void { 89 | this.uniforms.uTime.value = time; 90 | } 91 | 92 | /** 93 | * Set the firefly color uniform. 94 | * @param {Color} color - The color for the fireflies. 95 | */ 96 | setColor(color: Color): void { 97 | this.uniforms.uColor.value.copy(color); 98 | } 99 | 100 | /** 101 | * Set the firefly radius uniform. 102 | * @param {number} radius - The radius for fireflies. 103 | */ 104 | setFireFlyRadius(radius: number): void { 105 | this.uniforms.uFireFlyRadius.value = radius; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /FireFly.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AdditiveBlending, 3 | Color, 4 | DynamicDrawUsage, 5 | InstancedMesh, 6 | Matrix4, 7 | Object3D, 8 | PlaneGeometry, 9 | Scene, 10 | ShaderMaterial, 11 | Texture, 12 | Vector3 13 | } from 'three'; 14 | 15 | import Gl from '@/Gl'; 16 | import FireFlyMaterial from './FireFlyMaterial'; 17 | 18 | /** 19 | * This FireFlies class generates firefly particles using instanced rendering. 20 | * You can customize the number of firefly groups, how many fireflies per group, 21 | * their radius, and a noise texture for additional effects. 22 | * 23 | * Usage: 24 | * const fireflies = new FireFlies(scene, { 25 | * groupCount: 5, 26 | * firefliesPerGroup: 100, 27 | * groupRadius: 10, 28 | * noiseTexture: yourNoiseTexture 29 | * }); 30 | * 31 | * In your render loop, call the update method: 32 | * fireflies.update(deltaTime); 33 | */ 34 | 35 | type Props = { 36 | groupCount: number; // Number of groups of fireflies 37 | firefliesPerGroup: number; // Number of fireflies in each group 38 | groupRadius: number; // Radius of each group 39 | noiseTexture: Texture | null; // Optional texture for noise effects 40 | }; 41 | 42 | const defaultProps = { 43 | groupCount: 1, 44 | firefliesPerGroup: 50, 45 | groupRadius: 5, 46 | noiseTexture: null 47 | }; 48 | 49 | export class FireFlies { 50 | private gl: Gl; // Instance of the Gl class for WebGL context 51 | private scene: Scene; // The Three.js scene to which the fireflies will be added 52 | private fireflyParticles: InstancedMesh; // Instanced mesh for rendering fireflies 53 | private fireflyCount: number; // Total number of fireflies 54 | private Uniforms = { 55 | uTime: { value: 0 }, // Uniform for time variable to animate fireflies 56 | uFireFlyRadius: { value: 0.1 }, // Uniform for firefly radius 57 | uPlayerPosition: { value: new Vector3(0, 0, 0) }, // Uniform for player position 58 | uNoiseTexture: { value: new Texture() }, // Uniform for noise texture 59 | uColor: { value: new Color('#ffffff') } // Uniform for firefly color 60 | }; 61 | private groupCount: number; // Number of groups of fireflies 62 | private firefliesPerGroup: number; // Number of fireflies in each group 63 | private groupRadius: number; // Radius for grouping fireflies 64 | private fireflyMaterial: FireFlyMaterial; 65 | 66 | /** 67 | * Constructs the FireFlies instance. 68 | * @param _scene The Three.js scene where the fireflies will be rendered. 69 | * @param props Configuration options for the fireflies. 70 | */ 71 | constructor(_scene: Scene, props: Props = defaultProps) { 72 | this.gl = Gl.getInstance(); // Get the WebGL context 73 | this.scene = _scene; // Assign the scene 74 | this.groupCount = props.groupCount; // Set group count 75 | this.groupRadius = props.groupRadius; // Set group radius 76 | this.firefliesPerGroup = props.firefliesPerGroup; // Set fireflies per group 77 | if (props.noiseTexture) { 78 | this.Uniforms.uNoiseTexture.value = props.noiseTexture; // Assign noise texture if provided 79 | } 80 | 81 | // Create a firefly geometry 82 | const fireflyGeometry = new PlaneGeometry(0.2, 0.2); // Adjust the size of the firefly as desired 83 | 84 | // Create a firefly material 85 | this.fireflyMaterial = new FireFlyMaterial(); 86 | 87 | // Create a firefly object using instanced rendering 88 | this.fireflyCount = this.groupCount * this.firefliesPerGroup; // Calculate total firefly count 89 | this.fireflyParticles = new InstancedMesh( 90 | fireflyGeometry, 91 | this.fireflyMaterial, 92 | this.fireflyCount 93 | ); 94 | 95 | // Set initial positions for the fireflies 96 | this.setInitialPositions(this.groupCount, this.firefliesPerGroup); 97 | this.scene.add(this.fireflyParticles); // Add fireflies to the scene 98 | } 99 | 100 | /** 101 | * Sets the initial positions of the fireflies in their groups. 102 | * @param groupCount The number of groups of fireflies. 103 | * @param firefliesPerGroup The number of fireflies in each group. 104 | */ 105 | setInitialPositions(groupCount: number, firefliesPerGroup: number) { 106 | this.fireflyParticles.instanceMatrix.setUsage(DynamicDrawUsage); // Set usage to DynamicDraw 107 | 108 | const position = new Vector3(); // Vector to hold position 109 | const matrix = new Matrix4(); // Matrix to transform each firefly 110 | 111 | for (let i = 0; i < groupCount; i++) { 112 | // Calculate a random center position for each group 113 | const groupCenter = new Vector3(0, 0, 0); // Can be modified for dynamic positioning 114 | 115 | // Set positions for fireflies within the group 116 | for (let j = 0; j < firefliesPerGroup; j++) { 117 | // Calculate a random offset within the group 118 | const offset = new Vector3( 119 | this.randomGaussian() * this.groupRadius, 120 | this.randomGaussian() * this.groupRadius, 121 | this.randomGaussian() * this.groupRadius 122 | ); 123 | 124 | // Calculate the final position by adding the group center and the offset 125 | position.copy(groupCenter).add(offset); 126 | 127 | // Set the matrix position for the firefly 128 | matrix.setPosition(position); 129 | const index = i * firefliesPerGroup + j; // Calculate the index within the instanced mesh 130 | this.fireflyParticles.setMatrixAt(index, matrix); 131 | } 132 | } 133 | this.fireflyParticles.renderOrder = 1; // Set render order to ensure proper layering 134 | this.fireflyParticles.instanceMatrix.needsUpdate = true; // Mark the instance matrix for update 135 | } 136 | 137 | /** 138 | * Updates the uniforms each frame. 139 | * @param uTime The elapsed time since the last frame. 140 | */ 141 | update(delta: number) { 142 | this.Uniforms.uTime.value += delta; 143 | this.fireflyMaterial.updateTime(this.Uniforms.uTime.value); 144 | } 145 | 146 | /** 147 | * Generates a random number based on a Gaussian distribution. 148 | * @returns A random number from the Gaussian distribution. 149 | */ 150 | private randomGaussian() { 151 | let u = 0, 152 | v = 0; 153 | while (u === 0) u = Math.random(); // Convert [0,1) to (0,1) 154 | while (v === 0) v = Math.random(); 155 | const num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); // Box-Muller transform 156 | return num; // Return the random number 157 | } 158 | } 159 | --------------------------------------------------------------------------------