TSL / WebGPU Minimal

Fluid Text: WebGPU / TSL Minimal walkthrough

Smallest DOM-to-CanvasTexture text layer with hero-style distortion and Art Ink overlay. This walkthrough explains what the demo is doing, which library outputs it uses, and which parameters matter.
Hero text demo

This version keeps the integration small so the moving parts are visible. It uses the WebGPU / TSL path from `three-fluid-fx/tsl`, where the effect is built with RenderPipeline output nodes, TSL factories, and WGSL compute helpers.

Live demo

Minimal TSL Examples: Fluid Text Open example

What this demo teaches

Fluid Text is the hero text trick isolated: normal HTML owns layout, wrapping, and responsive typography, then a canvas texture mirrors those pixels into the WebGPU scene so the fluid field can refract and color the words. In this variant, the relevant fluid output is TextureNode fields: velocityNode, densityNode, and dyeNode when dye is enabled.

  • Understand why the visible DOM text is used as the source of truth before being hidden.
  • Copy live typography into a CanvasTexture that can be rendered by the three.js scene.
  • Compose simple distortion and Art Ink overlay around the text without changing page layout.

Implementation path

  1. Lay out real DOM text first

    The source copy is normal HTML, so CSS controls font size, line breaks, and responsive placement. The WebGPU layer does not guess the layout.

  2. Mirror the DOM into a texture

    DomTextPlane reads computed font styles and element rectangles, draws the text into an offscreen canvas, and maps that CanvasTexture onto a full-screen plane.

  3. Hide the DOM after sync

    Once the texture is current, the original text becomes transparent. Selection and layout still come from the DOM, while the rendered pixels come from the scene pass.

  4. Run the hero-style post stack

    The scene pass is first refracted by simpleDistortion and then blended with Art Ink through fluidOverlay, matching the landing-page defaults.

  5. Minimal scope

    It avoids GUI state and preset switching. Read it first when you need the smallest reliable version of the technique.

const textPlane = new DomTextPlane(stage, [kicker, headline, lead])
scene.add(textPlane.mesh)

pipeline.outputNode = fluidOverlay(
  'artInk',
  simpleDistortion(scenePass, fluid.densityNode, 0.45),
  fluid.densityNode,
  fluid.dyeNode,
  fluid.velocityNode,
  { opacity: 0.5, vibrance: 0.5 },
)

function syncText() {
  textPlane.sync(viewport.width, viewport.height)
  domText.classList.add('is-synced')
}

Parameters to tune

Parameter What it controls How to tune it
headline + lead The DOM source strings that are mirrored into the WebGPU texture. Keep the DOM as source of truth. After changing text, resync the texture instead of editing shader state.
distortionIntensity Amount of fluid-driven UV displacement applied to the text scene. Use enough to make pointer motion visible, but keep it below the point where letters become unreadable.
overlayOpacity How much Art Ink is blended over the distorted text. Lower it for product pages. Raise it for hero captures and social clips.
splatRadius + splatForce Controls brush size and how much pointer motion enters the fluid field. Use a larger radius than small object demos because the typography fills the viewport.

Source landmarks

Start from examples/tsl/minimal/fluid-text/main.ts. These are the parts worth reading first:

  • The DOM source block created inside the stage.
  • DomTextPlane.sync(), which converts element rectangles into a CanvasTexture.
  • The pipeline output: simpleDistortion followed by fluidOverlay("artInk", ...).
  • The resize path that resizes both FluidSimulation and the text plane.

Production notes

  • Wait for document.fonts.ready before the first sync when using web fonts.
  • Keep the DOM layer in the document for layout and accessibility, then hide only its painted color.
  • Resync on ResizeObserver or font-loading events in production; this example uses the stage resize path and editable controls.