Creating a Dynamic Fire Shader in Three.js

Introduction

Simulating fire in a 3D scene is a challenging yet rewarding task. This article walks you through the creation of a dynamic fire shader in Three.js, utilizing noise functions and custom shader logic to produce a realistic flame effect.

The Concept Behind Fire Shaders

Fire is characterized by:

  • Dynamic Movement: Flames flicker and change shape over time.
  • Gradient Colors: Transitioning hues from red to yellow to white.
  • Transparency: Variable opacity to simulate flame edges.

By combining vertex and fragment shaders, we can simulate these characteristics effectively.

Vertex Shader Breakdown

Purpose

The vertex shader applies subtle distortions to mimic the chaotic movement of fire.

Code Explanation

uniform float time;  // Animation time variable.
varying vec3 vPosition;  // Pass the vertex position to the fragment shader.

float noise(vec3 p) {
    return sin(p.x * 10.0 + p.y * 10.0 + p.z * 10.0) * 0.5 + 0.5;
    // Generate noise using sine waves for flame distortion.
}

void main() {
    vPosition = position;  // Pass the original vertex position.

    vec3 pos = position;
    pos.y += noise(vec3(pos.x, pos.y + time, pos.z)) * 0.5;  // Add vertical flicker.

    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);  // Transform vertex.
}

Key Features

  1. Noise for Flickering: Creates vertical displacement for a flickering effect.
  2. Dynamic Movement: The time uniform drives continuous motion.

Fragment Shader Breakdown

Purpose

The fragment shader handles the visual appearance of fire, including color gradients and transparency.

Code Explanation

uniform float time;  // Time for animation.
varying vec3 vPosition;  // Vertex position passed from vertex shader.

void main() {
    float intensity = 1.0 - smoothstep(0.0, 1.0, length(vPosition.xy));  
    // Calculate intensity based on distance from the center.

    vec3 color = mix(vec3(1.0, 0.2, 0.0), vec3(1.0, 1.0, 0.0), intensity);
    // Gradient from red to yellow based on intensity.

    color = mix(color, vec3(1.0), pow(intensity, 3.0));
    // Add a white-hot core using intensity falloff.

    gl_FragColor = vec4(color, intensity * 0.7);
    // Apply transparency based on intensity.
}

Key Features

  1. Color Gradient: Blends red, yellow, and white to simulate fire hues.
  2. Intensity Control: Adjusts brightness based on proximity to the flame’s core.
  3. Dynamic Transparency: Edges fade out naturally with lower intensity.

Integrating the Shader in Three.js

  1. Geometry: Use a cone or plane to represent the fire volume.
  2. Material: Apply the custom shader using ShaderMaterial.
  3. Animation: Continuously update the time uniform to animate the fire.
const fireMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0.0 }
    },
    vertexShader: `...`,  // Vertex shader code here
    fragmentShader: `...`,  // Fragment shader code here
    transparent: true
});

const fireGeometry = new THREE.ConeGeometry(1, 2, 32);
const fireMesh = new THREE.Mesh(fireGeometry, fireMaterial);
scene.add(fireMesh);

Customizing the Fire Shader

Use lil-gui to tweak parameters like:

  • Flame Height: Adjust vertex noise amplitude.
  • Color Gradients: Modify colors for unique flame effects.
  • Opacity: Control transparency for various levels of realism.

Applications of Fire Shaders

  • Campfire Scenes: Realistic flames for outdoor settings.
  • Torches and Candles: Subtle and controlled fire for medieval environments.
  • Fantasy Effects: Magical fire in games or animations.

Conclusion

The fire shader combines dynamic vertex deformation and color blending in the fragment shader to create a compelling visual effect. Experimenting with noise functions, color transitions, and animation speed can lead to a wide variety of fire effects, adding depth and realism to your Three.js projects!

Leave a comment

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