The AI Wrote the Graphics Code. I Still Had to Know Graphics.
I built textrude.io, a little browser tool that turns a word into 3D text you can spin, style, and export as a transparent PNG or a video. “Built” is generous: I described what I wanted and an AI wrote most of the three.js. It worked on the first try, in the specific sense that it ran, put text on the screen, and looked like a finished thing.
It was also, in three separate and completely invisible-until-you-look ways, rendering garbage.
This is the honest shape of vibe-coding a graphics app, and it’s more interesting than a build log. The AI is genuinely good at three.js — the setup, the boilerplate, the scene graph, the library’s whole surface. Where it fell down was the handful of places where the library’s abstractions leak into actual graphics math, and there it produced confident, plausible, idiomatic code that looked right in the editor and was wrong on the canvas. Three times, the only way to catch it was to already know the underlying thing.
Bug one: the letters with their holes filled in
To make a 3D letter you take the font’s 2D outline and extrude it into depth. Fonts store their outlines Y-up, and somewhere on the path from outline to mesh the Y axis gets flipped — a texture space that runs Y-down, or a mirror someone added to orient the text — even though three.js’s own world is Y-up. That flip does something quiet and nasty: it reverses the winding order — the direction each outline is traced — of every contour in the glyph.
Winding order is not cosmetic. It’s how you tell a letter’s outer edge from its holes: the bowl of an O, the two counters in a B, the little enclosed triangle in an A. The standard rule is “the contour with positive signed area is the outside, the negative ones are holes.” The AI used exactly that rule. But after a Y-flip, the signs are inverted — the outer contour now has negative area — so the code decided the outside of the letter was a hole and filled it in. Type a word with no enclosed shapes and everything looked perfect. Type O and you got a solid blob.
The fix is one line and one comment — after the flip, negative area is the outer contour — but you only write that line if you know what winding order is and that flipping an axis inverts it. The AI didn’t; it applied the textbook rule to a case the textbook rule doesn’t cover.
Bug two: the stripes across every bevel
After building each letter I merged them into one mesh, and the reflexive next step — the one every tutorial does — is to recompute the surface normals so the lighting looks right. The AI recomputed the normals. The text came out with faint horizontal stripes smeared across the beveled edges.
Recomputing normals averages them across shared vertices, which is exactly what you want on a smooth curved surface and exactly what you must not do on hard edges. A bevel is all hard edges; averaging the normals across those seams smears the shading that’s supposed to break sharply, and you get stripes. The correct move is counterintuitive: do nothing. The extrude step already generated correct normals; the “helpful” recompute is what broke them. Another one-line fix — don’t call the recompute — that’s only obvious if you know that averaging normals and hard edges are enemies.
Bug three: the transparent PNG I fixed seven times
The marquee feature is exporting your text as a clean transparent PNG. Getting a real alpha channel out of a WebGL canvas needs two specific flags flipped on, and the AI knew to flip them. The PNG came out with a solid black background anyway.
The flags weren’t the problem. The tool has a bloom pass — the glow that makes chrome and neon look good — and post-processing writes opaque pixels by construction: it takes your nice transparent scene and composites it onto a filled background as part of making it pretty. So every flag was set correctly and the output was still opaque, because the alpha was being destroyed one stage downstream of where anyone was looking.
The fix is a genuinely awkward dance, and you can watch me not find it: the commit history has “fix transparent PNG export” in it about seven times. What finally works is, right before you capture, tearing down everything that makes the scene look good — unmount the post-processing, null out the background, hide the floor and its shadow — then waiting a few frames for the render loop to actually draw the stripped-down version, grabbing the canvas from inside that render loop, and putting all the pretty stuff back afterward. “Disable the good-looking parts and capture on a later frame” is not a thing you guess correctly on the first try, or the fourth.
The part that’s the whole point
None of these were the AI being stupid. It wrote fluent, correct-looking three.js — the parts with a dense training corpus and a clear shape, it absolutely nailed, faster than I could have. It fell down in precisely the places where the code compiles, runs, and looks plausible, but the pixels come out wrong — because “does this render correctly” is not a question you can answer by reading the code. You have to look at the output, and you have to already know what correct looks like and why this particular wrong is happening.
Which is the same line I keep landing on: the assistant flies where there’s a check it can’t wriggle past, and stalls where the only check is a trained eye. Graphics has a check — you can literally see it’s broken — but that check only pays out if you can tell “bug” from “style,” a filled-in O from an intentional effect, and trace a black background three stages back to a bloom pass. That tracing is the part that stayed mine. It’s the same thing I’ve written about for work you can check, just rendered in pixels instead of rows.
I like that textrude exists, and I like that I mostly talked it into being. But the three bugs are the actual souvenir of the weekend. I didn’t write much of the code. I did have to know why it was wrong — and that, it turns out, was the job.