How the effect works

Watch a forged page and it looks like choreography: thousands of cubes flying from chaos into a photograph, each one apparently knowing its seat. There is no choreography. There is one algebraic identity, a camera on a rail, and a vertex shader that derives everything else. This guide walks through the whole mechanism, because once you see it, every control in the forge stops being a knob and starts being a consequence.

The equal-apparent-size trick

Every cube belongs to one tile of the image grid — column c, row r. At forge time each cube is also parked at a random depth z somewhere along the corridor the camera will travel. Here is the entire trick: the cube's world-space size is scaled by (ASSEMBLE_Z − z) / ASSEMBLE_Z, where ASSEMBLE_Z is the depth at which the image should read as finished.

Apparent size on screen is world size divided by distance from the camera. Multiply the two together and the random z cancels out: when the camera stands at ASSEMBLE_Z, every cube — near ones scaled small, far ones scaled large — projects to exactly the same screen rectangle. They tile edge to edge with no seams, because the seams were guaranteed by arithmetic, not tuned by hand.

At any other camera position the cancellation fails, on purpose. Near cubes loom, far cubes shrink, and the same grid reads as scattered noise. So the "assembly animation" is not an animation at all — no positions are interpolated, no tweens run. The scroll bar drives the camera down the corridor, and the picture condenses out of the noise as a side effect of perspective. Scroll back up and it dissolves again, for free.

Twelve bytes per cube

The usual way to draw many copies of a mesh is instancing with a transform matrix per instance — sixteen floats, 64 bytes. But look at what actually varies per cube here: column, row, parked depth. Three small numbers. Everything else — the world position, the compensating scale, which tile of the texture to sample — is a pure function of those three and of uniforms shared by every cube.

So that is all the studio stores: 12 bytes per instance, packed into one instanced attribute. The vertex shader rebuilds the transform from (c, r, z) on every frame. Flat faces are the one exception: they carry their colour per cube instead of sampling a shared texture, which adds a second attribute and doubles the cost to 24 bytes.

Concretely: a 144-row grid over a 3:1 panorama is 432 × 144, about 62,000 cubes and roughly 750 KB of instance data — uploaded once, never changed. Push the grid to 512 rows and you are past a quarter of a million cubes and still under 4 MB.

That single static buffer is also why there is exactly one draw call. Nothing per-cube ever crosses from JavaScript to the GPU after load. The main thread's whole job per frame is updating a couple of uniforms from the scroll position; the GPU does the rest. This is the difference between an effect that demos well and one you can ship on a phone.

Where the image lives

With the default full-image faces, the photograph is uploaded once as a texture and each cube's front face samples its own tile of it — the landed frame is as sharp as the source file, and the grid resolution only decides how many cubes fly, not how sharp the landing is. With flat faces, each tile is averaged to a single colour at forge time and the texture is discarded: the landed image resolves to exactly the grid, a deliberate mosaic. The image guide covers when each one wins.

The camera, damped

The camera does not sit rigidly at the scroll position; it chases it through critical damping, which is why a fast flick settles into place instead of snapping. The damping constant is exposed as --damp, and the track length — how much scrolling one full assembly costs — as --scroll, in viewport-heights. The engine publishes its progress (0 to 1) as a --assembly custom property on :root and as a pixelScroll event, so a surrounding page can pace its own content on the camera's clock. The embedding guide shows that in practice.

What happens when WebGL isn't there

No mechanism survives contact with every browser. When WebGL is unavailable, or neither three.js CDN can be reached, the forged page mounts the photograph as a plain still, framed as the finished mosaic would have been, publishes assembly as complete, and sets data-forge-fallback on the root element. Content keyed to the assembly lays out over a resolved image rather than waiting forever. And under prefers-reduced-motion the camera simply does not travel: scroll-coupled whole-field motion is the canonical vestibular trigger, so the picture is just there, assembled, from the first frame.

To see all of this from the other side — as controls rather than consequences — the documentation is the reference, and the Studio is the fastest way to build an intuition.