Show HN: I made a 3D SVG Renderer that projects textures without rasterization

Jun 5, 2025 - 03:30
 0  0
Show HN: I made a 3D SVG Renderer that projects textures without rasterization

I’ve been building a vanilla 3D object to SVG renderer in Typescript to help render circuit boards that are made in React and discovered an interesting trick to keep the SVGs small while getting approximately-correct looking perspective transformations with image textures.

An example circuit board rendered with our vanilla Typescript 3D Renderer. Great for checking sizing! You can see we were able to project the “texture” containing the PCB traces!

SVGs don’t support perspective transforms like CSS (or at least they’re not guaranteed to work in image viewers), so we need a way to simulate this perspective transformation without creating a massive SVG. It’s easy to draw the box below, you can just project the face of each side of the cube into a polygon, but mapping the texture to that perspective transform isn’t natively possible!

Perspective transforms in CSS using the transform attribute (from MDN)

So SVGs don’t support perspective transforms, what do they support? SVGs support this nice little transform called an affine transform. This 6 number transform is what you get when you do transform: matrix(a,b,c,d,e,f) in CSS. They are super useful for 2D transformations, like panning/scaling/dragging, but can’t really project into 3d.

Possible affine transforms from wikipedia
This projection isn’t possible with a single affine transform. You also can’t combine affine transformation matrices together to create this shape.

How can we approximate the transform? Here are some ideas I mulled over that could achieve a good result:

  • Redraw the image with the distortion. This is potentially expensive and means that we can’t use SVGs as the images without converting them to bitmaps. It also means that things might look “fuzzy”

  • Ray trace everything! By projecting a ray to compute each pixel for the image, I could get a very conventional 3d renderer. This doesn’t achieve my goal of lightweight SVGs though

  • Subdivide the image and project each subdivision in the most locally-correct affine transformation. Use projected polygon clip paths to cut off the edges of regions.

I was really curious how the last bullet point could work, and I could think of no other ideas that didn’t require rasterization. So with OpenAI O3’s help I implemented it into the vanilla Typescript 3D renderer. To test it, we’re going to project a checkerboard pattern onto a cube.

Cube with no texture

Ok here’s our starting point, 2 subdivisions, the 2 checkboard images with affine transformations.

Two subdivisions with a different affine transformation applied to each image

Not too bad, let’s see it with 4 subdivisions!

Four images with different affine transformations

Looks a bit rough (literally, it looks like it is not a flat surface). Let’s keep going!

Eight images, eight clip paths, still not very smooth!!
16 images, 16 clip paths

Alright let’s go to the end, we want it to look flat!!

512 Images & Clip Paths

At around 512 images, it’s really hard to tell the difference. Awesome! We did image projection without any rasterization!

Now for the exciting part: The SVG isn’t that big! Because we can use the `defs` of the SVG to avoid repeating the image, we only need to define each clip path!

This is the full file for the subdivision-2 cube. The polygons are the sides of the cube. The groups and clip paths are the only things that you need for each subregion.

Here’s a table of the file size as you increase subdivisions. I _think_ some differences in the matrix calculation account for the somewhat weird scaling.

The math is fun but in the age of AI, below our pay-grade! You can check out the full source code here!

Relevant snippet from the source where we subdivide and use bilinear interpolation to find the relevant quads

I’m excited to flesh out this 3D renderer because 3D SVGs can make great artifacts on GitHub, we want to make it so that people can easily review changes to circuit boards made with tscircuit in pull requests.

A visual snapshot test with a 3D SVG

I hope you enjoyed this neat little 3D trick! Back to coding…

I’m building an open-source electronics kernel! Also other things!

What's Your Reaction?

Like Like 0
Dislike Dislike 0
Love Love 0
Funny Funny 0
Angry Angry 0
Sad Sad 0
Wow Wow 0