Generating a single smoke cloud using ThreeJS

Overview of SingleCloud.js

SingleCloud.js uses Three.js’s PointsMaterial and BufferGeometry to render a single smoke cloud as a set of particles with variations in position, size, and color. The goal is to create a standalone, soft-looking smoke cloud with an organic, billowing effect.

1. Initializing the Smoke Cloud

The SingleCloud class initializes by loading a texture for the smoke particles, then creating a patch of particles based on random placements around a center point.

classSingleCloud{ 
  constructor(scene, textureUrl, posX, posY, posZ) {
    this.scene = scene; 
    this.cloudParticles = []; 
    this.textureUrl = textureUrl; 
    this.position = newTHREE.Vector3(posX, posY, posZ); 
    this.noise = newNoise(); // Initialize Perlin noise
    this.initSingleCloud(); 
  } 
  initSingleCloud() { 
    const loader = newTHREE.TextureLoader(); 
    loader.load(this.textureUrl, (texture) => { 
      this.createCloudPatch(texture); 
    }); 
  } 
}

The initSingleCloud function loads a texture for each particle and then calls createCloudPatch, which organizes the particles into a smoky cluster.

2. Setting Particle Positions with Noise and Random Variations

The createCloudPatch function uses generateCloudParticles to organize the smoke cloud structure. Inside generateCloudParticles, Perlin noise provides natural-looking variations in particle positions. This approach results in a smoky, billowing effect.

a. Configuring Positions with Perlin Noise: For each particle, Perlin noise values and slight random variations in x, y, and z positions make the smoke appear organic.

for (let i = 0; i < density; i++) {
 const angle = Math.random() * Math.PI * 2;
 const radius = Math.random() * size;
 const heightVariation = (Math.random() - 0.5) * 1.5;

 const x = center.x + radius * Math.cos(angle);
 const y = center.y + heightVariation;
 const z = center.z + radius * Math.sin(angle);

 const noiseValue = this.noise.simplex2(x * noiseScale, z * noiseScale);
 const position = new THREE.Vector3(x, y + noiseValue * 0.2, z);

 cloudPositions.push(position.x, position.y, position.z);
}

b. Adding Color Gradients for Realistic Smoke: Two colors are blended randomly to create a gradient effect across particles, making the smoke look dynamic and varied.

const color1 = new THREE.Color(`hsl(210, 10%, 35%)`);
const color2 = new THREE.Color(`hsl(0, 0%, 50%)`);

const mixFactor = Math.random();
const color = new THREE.Color(
 color1.r * mixFactor + color2.r * (1 - mixFactor),
 color1.g * mixFactor + color2.g * (1 - mixFactor),
 color1.b * mixFactor + color2.b * (1 - mixFactor)
);

Each particle’s color is blended based on a mixFactor, simulating subtle color shifts within the smoke for added realism.

3. Displaying the Smoke Cloud with Particle Material

A PointsMaterial is used for the particles, allowing properties like transparency and blending, giving the smoke cloud a soft, semi-transparent look.

const cloudMaterial = new THREE.PointsMaterial({
 map: texture,
 size: 2,
 blending: THREE.NormalBlending,
 depthTest: true,
 depthWrite: false,
 transparent: true,
 opacity: 0.4,
 vertexColors: true,
});

Using vertexColors enables color variations for each particle, and adjusting opacity and blending modes controls the softness and density of the smoke effect.

4. Updating the Cloud’s Position

The updatePosition function lets us move the smoke cloud along the y axis dynamically, which can be useful for animations or to simulate drifting.

updatePosition(newPos) {
 this.position.y = newPos;
 this.cloudParticles.forEach((particle) => {
  particle.position.y = newPos;
 });
}

Leave a comment

Your email address will not be published. Required fields are marked *