TL;DR

A how-to walkthrough shows how to build a rising cigarette smoke effect in three.js using a ShaderMaterial, a Perlin noise texture, UV sampling and time-driven animation. The guide covers mapping and remapping a grayscale noise texture, fading edges, animating vertical repetition, and adding a vertex-space twist for depth.

What happened

The source outlines a shader-based approach for rendering translucent, rising smoke on a plane in three.js. It starts by applying a ShaderMaterial with simple vertex and fragment shaders and then supplies a Perlin noise texture to the fragment shader via a uniform. Fragment UV coordinates are passed from the vertex shader to sample the texture; because the noise is grayscale, the code reads the .r channel. The texture can be restricted to a sample rectangle and used as an alpha mask (white = opaque, black = transparent), which requires transparent: true on the material. Animation is achieved by setting texture.wrapT to RepeatWrapping and translating the sampled v coordinate using an elapsed time uniform and a speed scalar. Visual refinement uses smoothstep to remap grayscale levels into stronger transparencies and to fade the mask near geometry edges. The effect is improved by modifying vertex positions: a vertical 1px slice of the texture supplies a per-vertex random angle that, multiplied by a strength value and applied via a rotate2D function, turns the plane into a twisted, spiral-like volume.

Why it matters

  • Harnesses GPU shaders to generate procedurally animated smoke with fine per-pixel control.
  • Uses a single Perlin noise texture and parameterized uniforms for flexible tuning (speed, remap, edge fade, twist).
  • Combines fragment masking and vertex deformation to achieve translucent, volumetric-looking motion from a simple plane.
  • Demonstrates techniques (texture repeating, smoothstep remapping, UV sampling) that are broadly applicable to other shader-driven effects.

Key facts

  • The base rendering uses THREE.ShaderMaterial with custom vertex and fragment shaders.
  • A Perlin noise texture is loaded and passed as a uniform sampler2D (uTexture); the shader reads texture(uTexture, uv).r because the texture is grayscale.
  • UV coordinates are forwarded from the vertex shader to the fragment shader via a varying vec2 vUv.
  • To animate, texture.wrapT is set to THREE.RepeatWrapping and a uTime uniform offsets texture sampling vertically; uSpeed controls motion rate.
  • Masking is implemented by writing a constant white color and using the texture sample as the fragment alpha; the material must set transparent: true.
  • Remapping grayscale values to stronger blacks/whites uses smoothstep(uRemapLow, uRemapHigh, value) to increase apparent translucency.
  • Edge fading uses smoothstep on vUv.x and vUv.y with uniforms uEdgeX and uEdgeY to reduce the visible seam at geometry boundaries.
  • Vertex-space twisting samples a 1px-wide vertical slice of the texture (uTwistSampleX, uTwistSampleHeight) to produce per-vertical-position angles; the result rotates vertex xz coordinates by angle * uTwistStrength.
  • A rotate2D helper matrix performs the x/z rotation in the vertex shader to create a spiral-like 3D deformation.

What to watch next

  • The guide notes that default three.js depth writing handles occlusion but that semi-transparent, overlapping fragments require additional handling — details on how to resolve this are not confirmed in the source.
  • Tune uRemapLow and uRemapHigh carefully: they determine which gray levels become transparent versus opaque and strongly affect realism.
  • When animating, ensure texture.wrapT = THREE.RepeatWrapping is enabled so vertical translation fills the sampling space and avoids visible seams.

Quick glossary

  • ShaderMaterial: A three.js material type that lets you supply custom vertex and fragment GLSL shaders and uniforms for per-vertex and per-pixel control.
  • UV coordinates: 2D coordinates on a mesh that map vertices to positions in a texture image for sampling colors in a shader.
  • Perlin noise: A gradient noise function commonly used to generate natural-looking procedural textures like smoke or clouds.
  • smoothstep: A GLSL function that smoothly interpolates values between 0 and 1 over a specified input range, useful for soft thresholding and fades.
  • RepeatWrapping: A texture property in three.js that makes a texture repeat when UV coordinates go outside the 0–1 range, useful for seamless scrolling effects.

Reader FAQ

Do you need a Perlin noise texture?
The example uses a Perlin noise texture as the grayscale source for randomness and patterns; the source demonstrates loading and sampling such a texture.

How is transparency produced for the smoke?
Transparency is produced by rendering white fragments and using the noise texture sample as the alpha channel; the material must set transparent: true.

How is motion implemented?
Motion comes from setting texture.wrapT to RepeatWrapping and subtracting elapsed time multiplied by a uSpeed uniform from the sampled texture v coordinate.

How do you handle occlusion and depth with semi-transparent fragments?
The source indicates that default depth writing in three.js handles occlusion but that semi-transparent, overlapping fragments require additional handling; the specific solution is not confirmed in the source.

BROUGHT TO YOU BY: NERV – GOD'S IN HIS HEAVEN, ALL'S RIGHT WITH THE WORLD Shaders 103 – smoke Shaders 103 – smoke PLANTED: JAN 2026 HITS: 2 INTENDED AUDIENCE: CREATIVE CODERS AND…

Sources

Related posts

By

Leave a Reply

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