@wolffo/three-fire
Version:
Modern TypeScript volumetric fire effect for Three.js and React Three Fiber
289 lines (284 loc) • 11.8 kB
TypeScript
import { Texture, Color, Matrix4, Vector3, Vector4, Mesh, ShaderMaterial } from 'three';
import React from 'react';
import { ReactThreeFiber } from '@react-three/fiber';
/**
* Uniforms interface for the fire shader
*/
interface FireShaderUniforms {
/** Fire texture (grayscale mask) */
fireTex: {
value: Texture | null;
};
/** Fire color tint */
color: {
value: Color;
};
/** Current time for animation */
time: {
value: number;
};
/** Random seed for fire variation */
seed: {
value: number;
};
/** Inverse model matrix for ray marching */
invModelMatrix: {
value: Matrix4;
};
/** Scale of the fire object */
scale: {
value: Vector3;
};
/** Noise scaling parameters [x, y, z, time] */
noiseScale: {
value: Vector4;
};
/** Fire shape intensity */
magnitude: {
value: number;
};
/** Noise lacunarity (frequency multiplier) */
lacunarity: {
value: number;
};
/** Noise gain (amplitude multiplier) */
gain: {
value: number;
};
}
/**
* Volumetric fire shader using ray marching and simplex noise
*
* Based on "Real-Time procedural volumetric fire" by Alfred et al.
* Uses simplex noise for turbulence and ray marching for volume rendering.
*
* @example
* ```ts
* const material = new ShaderMaterial({
* defines: FireShader.defines,
* uniforms: FireShader.uniforms,
* vertexShader: FireShader.vertexShader,
* fragmentShader: FireShader.fragmentShader,
* transparent: true
* })
* ```
*/
declare const FireShader: {
readonly defines: {
readonly ITERATIONS: "20";
readonly OCTAVES: "3";
};
readonly uniforms: FireShaderUniforms;
readonly vertexShader: "\n varying vec3 vWorldPos;\n\n void main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;\n }\n ";
readonly fragmentShader: "\n uniform vec3 color;\n uniform float time;\n uniform float seed;\n uniform mat4 invModelMatrix;\n uniform vec3 scale;\n uniform vec4 noiseScale;\n uniform float magnitude;\n uniform float lacunarity;\n uniform float gain;\n uniform sampler2D fireTex;\n\n varying vec3 vWorldPos;\n\n // GLSL simplex noise function by ashima\n vec3 mod289(vec3 x) {\n return x - floor(x * (1.0 / 289.0)) * 289.0;\n }\n\n vec4 mod289(vec4 x) {\n return x - floor(x * (1.0 / 289.0)) * 289.0;\n }\n\n vec4 permute(vec4 x) {\n return mod289(((x * 34.0) + 1.0) * x);\n }\n\n vec4 taylorInvSqrt(vec4 r) {\n return 1.79284291400159 - 0.85373472095314 * r;\n }\n\n float snoise(vec3 v) {\n const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);\n const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n\n vec3 i = floor(v + dot(v, C.yyy));\n vec3 x0 = v - i + dot(i, C.xxx);\n\n vec3 g = step(x0.yzx, x0.xyz);\n vec3 l = 1.0 - g;\n vec3 i1 = min(g.xyz, l.zxy);\n vec3 i2 = max(g.xyz, l.zxy);\n\n vec3 x1 = x0 - i1 + C.xxx;\n vec3 x2 = x0 - i2 + C.yyy;\n vec3 x3 = x0 - D.yyy;\n\n i = mod289(i);\n vec4 p = permute(permute(permute(\n i.z + vec4(0.0, i1.z, i2.z, 1.0))\n + i.y + vec4(0.0, i1.y, i2.y, 1.0))\n + i.x + vec4(0.0, i1.x, i2.x, 1.0));\n\n float n_ = 0.142857142857;\n vec3 ns = n_ * D.wyz - D.xzx;\n\n vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n\n vec4 x_ = floor(j * ns.z);\n vec4 y_ = floor(j - 7.0 * x_);\n\n vec4 x = x_ * ns.x + ns.yyyy;\n vec4 y = y_ * ns.x + ns.yyyy;\n vec4 h = 1.0 - abs(x) - abs(y);\n\n vec4 b0 = vec4(x.xy, y.xy);\n vec4 b1 = vec4(x.zw, y.zw);\n\n vec4 s0 = floor(b0) * 2.0 + 1.0;\n vec4 s1 = floor(b1) * 2.0 + 1.0;\n vec4 sh = -step(h, vec4(0.0));\n\n vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n\n vec3 p0 = vec3(a0.xy, h.x);\n vec3 p1 = vec3(a0.zw, h.y);\n vec3 p2 = vec3(a1.xy, h.z);\n vec3 p3 = vec3(a1.zw, h.w);\n\n vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));\n p0 *= norm.x;\n p1 *= norm.y;\n p2 *= norm.z;\n p3 *= norm.w;\n\n vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);\n m = m * m;\n return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));\n }\n\n float turbulence(vec3 p) {\n float sum = 0.0;\n float freq = 1.0;\n float amp = 1.0;\n\n for(int i = 0; i < OCTAVES; i++) {\n sum += abs(snoise(p * freq)) * amp;\n freq *= lacunarity;\n amp *= gain;\n }\n\n return sum;\n }\n\n vec4 samplerFire(vec3 p, vec4 scale) {\n vec2 st = vec2(sqrt(dot(p.xz, p.xz)), p.y);\n\n if(st.x <= 0.0 || st.x >= 1.0 || st.y <= 0.0 || st.y >= 1.0) {\n return vec4(0.0);\n }\n\n p.y -= (seed + time) * scale.w;\n p *= scale.xyz;\n\n st.y += sqrt(st.y) * magnitude * turbulence(p);\n\n if(st.y <= 0.0 || st.y >= 1.0) {\n return vec4(0.0);\n }\n\n return texture2D(fireTex, st);\n }\n\n vec3 localize(vec3 p) {\n return (invModelMatrix * vec4(p, 1.0)).xyz;\n }\n\n void main() {\n vec3 rayPos = vWorldPos;\n vec3 rayDir = normalize(rayPos - cameraPosition);\n float rayLen = 0.0288 * length(scale.xyz);\n\n vec4 col = vec4(0.0);\n\n for(int i = 0; i < ITERATIONS; i++) {\n rayPos += rayDir * rayLen;\n vec3 lp = localize(rayPos);\n lp.y += 0.5;\n lp.xz *= 2.0;\n col += samplerFire(lp, noiseScale);\n }\n\n // Apply color tint to the fire\n col.rgb *= color;\n col.a = col.r;\n gl_FragColor = col;\n }\n ";
};
/**
* Properties for creating a Fire instance
*/
interface FireProps$1 {
/** Fire texture (grayscale mask defining fire shape) */
fireTex: Texture;
/** Fire color tint (default: 0xeeeeee) */
color?: Color | string | number;
/** Ray marching iterations - higher = better quality, lower performance (default: 20) */
iterations?: number;
/** Noise octaves for turbulence (default: 3) */
octaves?: number;
/** Noise scaling parameters [x, y, z, time] (default: [1, 2, 1, 0.3]) */
noiseScale?: [number, number, number, number];
/** Fire shape intensity (default: 1.3) */
magnitude?: number;
/** Noise lacunarity - frequency multiplier (default: 2.0) */
lacunarity?: number;
/** Noise gain - amplitude multiplier (default: 0.5) */
gain?: number;
}
/**
* Volumetric fire effect using ray marching shaders
*
* Creates a procedural fire effect that renders as a translucent volume.
* The fire shape is defined by a grayscale texture, with white areas being
* the most dense part of the fire.
*
* @example
* ```ts
* const texture = textureLoader.load('fire.png')
* const fire = new Fire({
* fireTex: texture,
* color: 0xff4400,
* magnitude: 1.5
* })
* scene.add(fire)
*
* // In animation loop
* fire.update(time)
* ```
*/
declare class Fire extends Mesh {
material: ShaderMaterial & {
uniforms: FireShaderUniforms;
};
private _time;
/**
* Creates a new Fire instance
*
* @param props - Configuration options for the fire effect
*/
constructor({ fireTex, color, iterations, octaves, noiseScale, magnitude, lacunarity, gain, }: FireProps$1);
/**
* Updates the fire animation and matrix uniforms
*
* Call this method in your animation loop to animate the fire effect.
*
* @param time - Current time in seconds (optional)
*
* @example
* ```ts
* function animate() {
* fire.update(performance.now() / 1000)
* renderer.render(scene, camera)
* requestAnimationFrame(animate)
* }
* ```
*/
update(time?: number): void;
/**
* Current animation time in seconds
*/
get time(): number;
set time(value: number);
/**
* Fire color tint
*
* @example
* ```ts
* fire.fireColor = 'orange'
* fire.fireColor = 0xff4400
* fire.fireColor = new Color(1, 0.5, 0)
* ```
*/
get fireColor(): Color;
set fireColor(color: Color | string | number);
/**
* Fire shape intensity
*
* Higher values create more dramatic fire shapes.
* Range: 0.5 - 3.0, Default: 1.3
*/
get magnitude(): number;
set magnitude(value: number);
/**
* Noise lacunarity (frequency multiplier)
*
* Controls how much the frequency increases for each noise octave.
* Range: 1.0 - 4.0, Default: 2.0
*/
get lacunarity(): number;
set lacunarity(value: number);
/**
* Noise gain (amplitude multiplier)
*
* Controls how much the amplitude decreases for each noise octave.
* Range: 0.1 - 1.0, Default: 0.5
*/
get gain(): number;
set gain(value: number);
}
declare module '-three/fiber' {
interface ThreeElements {
fire: ReactThreeFiber.Object3DNode<Fire, typeof Fire>;
}
}
/**
* Props for the Fire React component
*/
interface FireProps extends Omit<FireProps$1, 'fireTex'> {
/** Fire texture URL or Three.js Texture object */
texture: string | Texture;
/** Auto-update time from useFrame (default: true) */
autoUpdate?: boolean;
/** Custom update function called each frame */
onUpdate?: (fire: Fire, time: number) => void;
/** Child components */
children?: React.ReactNode;
/** Position in 3D space */
position?: [number, number, number];
/** Rotation in radians */
rotation?: [number, number, number];
/** Scale factor (uniform or per-axis) */
scale?: [number, number, number] | number;
}
/**
* Ref interface for imperative fire control
*/
interface FireRef {
/** Fire mesh instance */
fire: Fire | null;
/** Update fire animation manually */
update: (time?: number) => void;
}
/**
* React Three Fiber component for volumetric fire effect
*
* Creates a procedural fire effect that can be easily integrated into R3F scenes.
* The component automatically handles texture loading, animation updates, and
* provides props for all fire parameters.
*
* @example
* ```tsx
* <Canvas>
* <Fire
* texture="/fire.png"
* color="orange"
* magnitude={1.5}
* scale={[2, 3, 2]}
* position={[0, 0, 0]}
* />
* </Canvas>
* ```
*
* @example With custom animation
* ```tsx
* <Fire
* texture="/fire.png"
* onUpdate={(fire, time) => {
* fire.fireColor.setHSL((time * 0.1) % 1, 1, 0.5)
* }}
* />
* ```
*/
declare const FireComponent: React.ForwardRefExoticComponent<FireProps & React.RefAttributes<FireRef>>;
/**
* Hook for easier access to fire instance and controls
*
* Provides a ref and helper methods for controlling fire imperatively.
*
* @returns Object with ref, fire instance, and update method
*
* @example
* ```tsx
* function MyComponent() {
* const fireRef = useFire()
*
* const handleClick = () => {
* if (fireRef.fire) {
* fireRef.fire.magnitude = 2.0
* }
* }
*
* return (
* <Fire ref={fireRef.ref} texture="/fire.png" />
* )
* }
* ```
*/
declare const useFire: () => {
/** Ref to pass to Fire component */
ref: React.RefObject<FireRef>;
/** Fire mesh instance (null until mounted) */
fire: Fire | null;
/** Update fire animation manually */
update: (time?: number) => void | undefined;
};
export { FireComponent as Fire, FireComponent, Fire as FireMesh, FireShader, useFire };
export type { FireProps$1 as FireMeshProps, FireProps, FireRef, FireShaderUniforms };