Metaball droplet refraction
Five drops of water, made from one field and one marching-cubes mesh.
They merge, stretch a neck and split, and the background bends through them.
The problem
A drop of water isn't a sphere you can fade out. Make it see-through with plain alpha and it looks like tinted plastic: the background shows through unchanged. Real water bends what's behind it, flips it inside small drops and gets brighter at the edges. Drops also join and pull apart, which a fixed mesh can't do.
How it works
- Shape. Each drop is a point with a strength. Their fields are added up on a 72 × 72 × 72 grid, and marching cubes builds a surface wherever the sum crosses a threshold (80 here). When two drops come close, their fields add up between them, so the surface merges and stretches a neck before it splits.
- Refraction. The opaque scene is first rendered into a texture. For each pixel of a drop, the view ray is bent by the surface normal with an index of 1.33 (water), pushed through the drop by a set thickness, and projected back to the screen. The pixel then reads the background from that spot.
- Fresnel. The surface reflects more at grazing angles, so the edges catch the room reflection and the light, and less of the background comes through there.
What I hit
To show refraction on its own, I had to turn the specular reflection off entirely. The reflected part also dims what comes through, so leaving it on would mix both steps.
The first slow-motion shot never split: the drops' own motion kept them merged for the whole segment. The last shot now drives the gap between the two big drops directly: one drop stretches, splits, and merges again.
On a busy background with small drops, the bending was barely visible. Larger drops, fewer stripes and big rings made it readable.
My build
three.js (dev branch, r187): MarchingCubes from the examples, and MeshPhysicalMaterial with transmission 1, IOR 1.33, thickness 0.7, roughness 0, lit by RoomEnvironment for reflections. Both used unchanged. The backdrop, the five drops' motion and the timeline are mine. The mesh is rebuilt every frame on the CPU, about 4,200 triangles in the first frame.
The left half is the same mesh with a standard material at 38% opacity.
Trade-offs
- Merging and splitting come free from the field
- Refraction is one extra texture read per pixel
- One bend only: the exit side of the drop is guessed from a fixed thickness
- Drops only refract the opaque scene, not each other
- A 72³ grid shows facets up close; finer grids cost more CPU every frame
When to use it
Rain on a window, potions, slime, droplets in UI or title screens: anything where a few liquid blobs move, join and split in front of a scene.
Source and credits
Repo: github.com/mrdoob/three.js (MIT license), branch dev, commit fa01dd6
Code: MarchingCubes (examples/jsm/objects, set up like examples/webgl_marchingcubes.html), MeshPhysicalMaterial transmission and RoomEnvironment are used unchanged. The backdrop, the drop motion, the timeline and the overlays are my own code. three.js by @mrdoob and contributors.
Topic credit: @akella's real-time water drops in three.js, https://x.com/akella/status/2108469951455736248. None of their code, composition or colours is used.