Created
December 5, 2024 11:23
-
-
Save sp90/3926e53871b9425f4725444168ec99a3 to your computer and use it in GitHub Desktop.
Angular component for adding fog and dust particles to your images
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, ElementRef, viewChild } from '@angular/core'; | |
import * as THREE from 'three'; | |
const SMOKE_PARTICLE_COUNT = 150; | |
const SMOKE_SPREAD = 1600; | |
const SMOKE_OPACITY = 0.5; | |
const SMOKE_COLOR = 0xcdc8bb; | |
const SMOKE_ROTATION_SPEED = 0.5; | |
const SMOKE_SIZE_FACTOR = 300; | |
const SMOKE_SIZE_VARIATION = 0.1; | |
const DUST_PARTICLE_COUNT = 1200; | |
const DUST_SPREAD = 1600; | |
const DUST_SIZE = 2; | |
const DUST_COLOR = 0xcdc8bb; | |
const DUST_MOVEMENT_SPEED = 200; | |
const DUST_MOVEMENT_LEVEL = 2; | |
const DUST_VERTICAL_DIRECTION = 1; | |
const DUST_OPACITY_LEVEL = 0.5; | |
@Component({ | |
selector: 'app-smoke', | |
template: ` | |
<canvas #canvas></canvas> | |
`, | |
styles: [ | |
` | |
canvas { | |
display: block; /* Prevents scrollbar */ | |
} | |
`, | |
], | |
}) | |
export class EffectsComponent { | |
canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas'); | |
#renderer!: THREE.WebGLRenderer; | |
#scene!: THREE.Scene; | |
#camera!: THREE.PerspectiveCamera; | |
#smokeParticles: THREE.Mesh[] = []; | |
#dustParticles: THREE.Points[] = []; | |
#clock = new THREE.Clock(); | |
ngAfterViewInit(): void { | |
this.init(); | |
this.animate(); | |
} | |
private init(): void { | |
const canvas = this.canvasRef().nativeElement; | |
this.#renderer = new THREE.WebGLRenderer({ canvas, alpha: true }); | |
this.#renderer.setSize(window.innerWidth, window.innerHeight); | |
this.#scene = new THREE.Scene(); | |
this.addCamera(); | |
this.addLights(); | |
this.addParticles(); | |
this.addDustParticles(); | |
window.addEventListener('resize', this.onResize.bind(this)); | |
} | |
private animate(): void { | |
requestAnimationFrame(this.animate.bind(this)); | |
this.evolveSmoke(this.#clock.getDelta()); | |
this.evolveDust(this.#clock.getDelta()); | |
this.#renderer.render(this.#scene, this.#camera); | |
} | |
private addCamera(): void { | |
this.#camera = new THREE.PerspectiveCamera( | |
75, | |
window.innerWidth / window.innerHeight, | |
1, | |
10000 | |
); | |
this.#camera.position.z = 1000; | |
this.#scene.add(this.#camera); | |
} | |
private addLights(): void { | |
const light = new THREE.DirectionalLight(0xffffff, 0.95); | |
light.position.set(-1, 0, 1); | |
this.#scene.add(light); | |
} | |
private addParticles(): void { | |
const textureLoader = new THREE.TextureLoader(); | |
textureLoader.load('/others/clouds.png', (texture: any) => { | |
let smokeMaterial = new THREE.MeshLambertMaterial({ | |
color: SMOKE_COLOR, | |
map: texture, | |
transparent: true, | |
opacity: SMOKE_OPACITY, | |
}); | |
(smokeMaterial.map as any).minFilter = THREE.LinearFilter; | |
for (let i = 0; i < SMOKE_PARTICLE_COUNT; i++) { | |
const sizeVariation = 1 + Math.random() * SMOKE_SIZE_VARIATION; | |
const size = SMOKE_SIZE_FACTOR * sizeVariation; | |
const smokeGeometry = new THREE.PlaneGeometry(size, size); | |
const smokeMesh = new THREE.Mesh(smokeGeometry, smokeMaterial); | |
smokeMesh.position.set( | |
Math.random() * SMOKE_SPREAD - SMOKE_SPREAD / 2, | |
Math.random() * SMOKE_SPREAD - SMOKE_SPREAD / 2, | |
Math.random() * 1000 - 100 | |
); | |
smokeMesh.rotation.z = Math.random() * 360; | |
this.#smokeParticles.push(smokeMesh); | |
this.#scene.add(smokeMesh); | |
} | |
}); | |
} | |
private evolveSmoke(delta: number): void { | |
for (let i = 0; i < this.#smokeParticles.length; i++) { | |
this.#smokeParticles[i].rotation.z += delta * SMOKE_ROTATION_SPEED; | |
} | |
} | |
private onResize(): void { | |
this.#camera.aspect = window.innerWidth / window.innerHeight; | |
this.#camera.updateProjectionMatrix(); | |
this.#renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
// New function to add dust particles: | |
private addDustParticles(): void { | |
const dustGeometry = new THREE.BufferGeometry(); | |
const positions = new Float32Array(DUST_PARTICLE_COUNT * 3); | |
for (let i = 0; i < DUST_PARTICLE_COUNT; i++) { | |
const i3 = i * 3; | |
positions[i3] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2; | |
positions[i3 + 1] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2; | |
positions[i3 + 2] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2; | |
} | |
dustGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
const dustMaterial = new THREE.PointsMaterial({ | |
color: DUST_COLOR, | |
size: DUST_SIZE, | |
transparent: true, | |
opacity: DUST_OPACITY_LEVEL, | |
}); | |
const dustParticles = new THREE.Points(dustGeometry, dustMaterial); | |
this.#dustParticles.push(dustParticles); | |
this.#scene.add(dustParticles); | |
} | |
// New function to animate dust particles: | |
private evolveDust(delta: number): void { | |
const movementSpeed = DUST_MOVEMENT_SPEED * DUST_MOVEMENT_LEVEL; | |
for (let i = 0; i < this.#dustParticles.length; i++) { | |
const positions = this.#dustParticles[i].geometry.attributes['position'] | |
.array as Float32Array; | |
for (let j = 0; j < positions.length; j += 3) { | |
// Mostly upwards movement with slight randomness | |
positions[j] += (Math.random() - 0.5) * delta * movementSpeed * 0.1; // Reduced horizontal movement | |
positions[j + 1] += | |
DUST_VERTICAL_DIRECTION * delta * movementSpeed + | |
(Math.random() - 0.5) * delta * movementSpeed * 0.1; // Mostly vertical | |
positions[j + 2] += (Math.random() - 0.5) * delta * movementSpeed * 0.1; // Reduced depth movement | |
// Fade out and reset position if particle goes too high/low | |
if (positions[j + 1] > DUST_SPREAD / 2 || positions[j + 1] < -DUST_SPREAD / 2) { | |
positions[j] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2; | |
positions[j + 1] = -DUST_SPREAD / 2; // Reset at the bottom | |
positions[j + 2] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2; | |
} | |
} | |
this.#dustParticles[i].geometry.attributes['position'].needsUpdate = true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment