In this tutorial we will see three tricks to convert a 2D image to 3D. We will use Blender for the first and Processing with ToxicLibs for the second and third.
These techniques can be useful to generate an STL file that we can later use for 3D printing or CNC milling. I will use as an example this image taken from here, TSZ Digital Art. What I will show is meant for pure raster images, not ones that have previously been converted to vector, like is shown here, for example.
2D image to 3D with Blender
This first technique is quite known and standard. We start by creating a plane and by subdividing it many times. The more subdivisions you do, the better, but beware not to crash your computer.
Then, in the Texture panel, you import the image that you want to convert to 3D.
Finally, you need to apply to your plane a Displace modifier using as a texture the one that you have just created.
You will probably have to adjust the strength and, most likely, you will have to go back to the the Texture panel and adjust the Image Mapping settings, so that the image fits the plane. Try to change the Extension to clip and play with the Crop minimum and Crop maximum parameters. I also recommend to change the origin of the plane to its bottom left corner and to move it at the origin of the axes, it will be easier to deal with it.
As last touch, you can add a Subdivision Surface modifier to smooth even further you result.
2D image to 3D with Processing and ToxicLibs – 1
In this second approach we will use Processing and ToxicLibs instead. The idea is to use a voxel-space and to use the brightness values of the image as SDF values for the voxels. We will use ToxicLibs Volume utils for this. We have already talked about these topics in the Painting noise with ToxicLibs Volumetric brush in Processing tutorial. Here is the code:
import toxi.geom.*; import toxi.geom.mesh.*; import toxi.volume.*; import toxi.processing.*; import peasy.*; PeasyCam cam; ToxiclibsSupport gfx; VolumetricSpaceArray volume; IsoSurface surface; TriangleMesh mesh; void setup() { size(1024, 768, P3D); noStroke(); fill(255); cam = new PeasyCam(this, 50); PImage img = loadImage("ca.png"); img.filter(BLUR, 2); // Blurring a bit the image avoids strange results gfx=new ToxiclibsSupport(this); volume=new VolumetricSpaceArray(new Vec3D(50, 50, 5), img.width, img.height, 4); // Change the Z axes value to modify the amount of extrusion surface=new ArrayIsoSurface(volume); mesh=new TriangleMesh(); img.loadPixels(); for (int x=0; x<volume.resX; x++) { for (int y=0; y<volume.resY; y++) { for (int z=0; z<volume.resZ; z++) { if (z >= volume.resZ / 2 ) { float val = brightness(img.pixels[y*img.width+x]) / 255.f; volume.setVoxelAt(x, y, z, val); } else { volume.setVoxelAt(x, y, z, 1); } } } } volume.closeSides(); surface.reset(); mesh = (TriangleMesh)surface.computeSurfaceMesh(mesh, 0.5); println("Ready"); } void draw() { background(0); myLights(); myPerspective(); gfx.mesh(mesh); } void exportSimpleMesh() { mesh.saveAsSTL(sketchPath("scribble"+(System.currentTimeMillis()/1000)+".stl")); } void myPerspective() { // Avoid clipping the view float fov = PI/3.0; float cameraZ = (height/2.0) / tan(fov/2.0); perspective(fov, float(width)/float(height), cameraZ/500.0, cameraZ*10.0); } void myLights() { directionalLight(240, 240, 240, 0.25, 0.25, 1); directionalLight(240, 240, 240, 0, 0, -1); lightSpecular(240, 240, 240); shininess(1); } void keyPressed() { if (key=='s') { exportSimpleMesh(); } }
2D image to 3D with Processing and ToxiLibs – 2
The last approach is similar to the previous one and it produces similar results, but not exactly the same. It also uses voxels and is taken directly from one of the example sketches of ToxicLibs (img_to_voxels.pde), only slightly modified. We will use Volumetric Brush. We will use the brightness of the pixels to determine how many “strokes” of the brush we need to apply on the Z axis of the voxel-space.
import toxi.geom.*; import toxi.geom.mesh.*; import toxi.volume.*; import toxi.processing.*; import peasy.*; PeasyCam cam; ToxiclibsSupport gfx; VolumetricSpace volume; IsoSurface surface; TriangleMesh mesh; VolumetricBrush brush; int SCALE=8; int DEPTH=10; void setup() { size(1024, 768, P3D); noStroke(); fill(255); cam = new PeasyCam(this, 50); PImage img = loadImage("ca.png"); img.filter(BLUR, 2); // Blurring a bit the image avoids strange results gfx=new ToxiclibsSupport(this); // setup volumetric space Vec3D worldSize = new Vec3D(img.width, img.height, DEPTH).scale(SCALE); volume = new VolumetricHashMap(worldSize, img.width, img.height, DEPTH, 0.33); brush = new RoundBrush(volume, SCALE); // parse the image for (int y = 0; y < img.height; y ++) { for (int x = 0; x < img.width; x ++) { int col=img.pixels[y * img.width + x] & 0xff; //if (col > THRESH) { for (int z = 0, d = (int)(col/255.0*DEPTH); z < d; z++) { brush.drawAtGridPos(x, y, z, 1); } //} } } // make volume watertight volume.closeSides(); // compute mesh mesh = new WETriangleMesh(); new HashIsoSurface(volume).computeSurfaceMesh(mesh, 1f); println("Ready"); } void draw() { background(0); myLights(); myPerspective(); gfx.mesh(mesh); } void exportSimpleMesh() { mesh.saveAsSTL(sketchPath("scribble"+(System.currentTimeMillis()/1000)+".stl")); } void myPerspective() { // Avoid clipping the view float fov = PI/3.0; float cameraZ = (height/2.0) / tan(fov/2.0); perspective(fov, float(width)/float(height), cameraZ/500.0, cameraZ*10.0); } void myLights() { directionalLight(240, 240, 240, 0.25, 0.25, 1); directionalLight(240, 240, 240, 0, 0, -1); lightSpecular(240, 240, 240); shininess(1); } void keyPressed() { if (key=='s') { exportSimpleMesh(); } }
What do you think? Which approach do you prefer? Or do you have other methods? Let me know!