UNPKG

@wolffo/three-fire

Version:

Modern TypeScript volumetric fire effect for Three.js and React Three Fiber

190 lines (186 loc) 9.12 kB
import { Texture, Color, Matrix4, Vector3, Vector4, Mesh, ShaderMaterial } from 'three'; /** * 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 { /** 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); /** * 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); } export { Fire as FireMesh, FireShader }; export type { FireProps as FireMeshProps, FireShaderUniforms };