I needed a t-shirt preview that turns in 3D, from nothing but two photos: a front and a back, each cut out on a transparent background. No 3D model, no modelling step. Here's the approach that ended up working, in four parts.
1. Get the outline from alpha
A cut-out PNG already knows where the object ends. Sample the alpha channel on a grid (I use 240 points across the image) and trace the contour where alpha crosses 0.5. Marching squares does the job. The shape now ends exactly where the photo ends, so the texture always lines up with the geometry.
2. Turn distance into thickness
A flat outline extruded straight looks like a cookie. What made it read as fabric was a distance field: for each point inside the outline, the distance to the nearest seam. I compute it once with an exact Euclidean distance transform (Felzenszwalb and Huttenlocher's separable one), then blur the inside a little so the surface doesn't crease along the middle. The outline itself stays at zero.
Then thickness is two terms added together: a rounded rim near the seam and the body filling out further in.
// d: distance to the nearest seam, as a fraction of the image width
const depthAt = (d) => {
const st = Math.min(d / tubeRadius, 1); // the rim: a quarter circle, vertical at the seam
const sb = Math.min(d / bodyRadius, 1); // the body: a gentle curve towards the middle
return tube * Math.sqrt(1 - (1 - st) ** 2) + body * (1 - (1 - sb) ** 2);
};
At the seam the surface leaves vertically, like cloth rounding over a fold. Towards the middle it barely grows. For a t-shirt it tops out at about 3.2% of the image width per side. A nice side effect: narrow parts like sleeves have small distances everywhere, so they come out slim without any special handling.
3. Build the mesh on the same grid
Keep the grid points inside the outline, and add a point wherever the outline crosses a grid line so the silhouette follows the photo instead of stair-stepping. Triangulate each cell from the points it kept.
For the back, reuse the exact same points, push them the other way, and map the back photo mirrored left to right. Because both surfaces share their edge vertices, there's no seam to see through when the object turns edge-on.
If the object has real openings (sleeve ends, a hem), mark those edge segments and don't join front to back there. Everywhere else the outline is a closed seam.
4. Shade the print from the photo
If you lay a design on top, don't bake it into the texture. In the fragment shader, take the cloth's own lighting from the photo, meaning how much brighter or darker each pixel is than the fabric around it, clamp it to a sensible range (I use 0.55 to 1.12), and multiply the ink by it. Keep the ink premultiplied, or you get dark fringes where it ends:
color.rgb = color.rgb * (1.0 - ink.a) + ink.rgb * light; // ink is premultiplied
Folds now run through the print, and recolouring the cloth never touches the design.
Keeping it cheap
- Load the 3D code only when someone asks for it.
- Render on demand: only while the object moves, never while it's scrolled out of view.
- Dispose of the geometry and textures when the view closes.
- Skip the 3D option entirely on browsers without WebGL 2 or a GPU.
Where it falls short
It isn't a scan. Everything comes from the silhouette, so it works for things whose shape the outline explains, like a shirt or a bag, and the sides between front and back are inferred, not seen. Photos matter more than code: a back photo shot from a different distance shows up as a sliver along the edge, and harsh shadows make the print look blotchy.
If you've done "3D from 2D" for product previews another way, I'd love to hear what worked.
If you want to have this with one click in your e-commerce store you can check it here: PrintBlob














