7
$\begingroup$

Is there a way to create a flat mesh from an image that has transparency, so that the mesh matches shape and does not include the transparency in the geometry?

I have tried converting my image in an .svg with inkscape and adding a bitmap, but the results get very messy.

Here is my image:


enter image description here


Thanks.

$\endgroup$
10
$\begingroup$

I wrote a script to do exactly this a while back, here it is: enter image description here

Tips on usage, if you really are using pixel art, then avoid representing the pixels using 4 pixels. You don't have to.. but it makes the resulting mesh unnecessarily vertex heavy before doing any operations like 'limited dissolve' (which joins all adjacent geometry when possible)

Script, is a little long but I'll include here anyway.

from collections import defaultdict

import bpy

scene = bpy.context.scene

color_map = defaultdict(list)
filename = "aYlTzbV.png"
img = bpy.data.images[filename]
scale = 0.3


def idx_to_co(idx, width):
    r = int(idx / width)
    c = idx % width
    return r, c

def co_to_idx(r, c, width):
    return r * width + c


def rgba_from_index(idx, pxs):
    start_raw_index = idx * 4
    return pxs[start_raw_index:start_raw_index+4]


def sv_main():

    def is_fully_opaque(rgba):
        return rgba[3] == 1.0

    pixels = img.pixels
    pxs = list(pixels)

    w = width = img.size[0]
    h = height = img.size[1]

    num_pixels = len(pxs)
    gl = grouped_list = [pxs[i:i+4] for i in range(num_pixels)[::4]]

    for c in range(w):
        for r in range(h):
            idx = co_to_idx(r, c, w)
            rgba = rgba_from_index(idx, pxs)
            if is_fully_opaque(rgba):
                color_map[tuple(rgba[:3])].append([r*scale, -c*scale, 0.0])


def create_repr_plane(obj_name, mesh_name, vlist):
    s = scale/2
    verts = []
    v_add = verts.extend
    faces = []
    f_add = faces.append

    for i, v in enumerate(vlist):
        x, y = v[:2]
        v_add([[-s+x, s+y, 0], [-s+x, -s+y, 0], [s+x, -s+y, 0], [s+x, s+y, 0]])

        offset = i*4
        f_add([0 + offset, 1 + offset, 2 + offset, 3 + offset])

    profile_mesh = bpy.data.meshes.new(obj_name)
    profile_mesh.from_pydata(verts, [], faces)
    profile_mesh.update()

    profile_object = bpy.data.objects.new(obj_name, profile_mesh)

    scene.objects.link(profile_object)
    return profile_object


sv_main()

for i, (k, v) in enumerate(color_map.items()):

    obj = create_repr_plane("dupli_object_" + str(i), "dupli_mesh_" + str(i), v)

    repr_of_color = "{0:.4f} {1:.4f} {2:.4f}".format(*k)
    mat = bpy.data.materials.new('sv_material_' + repr_of_color)
    mat.use_nodes = True
    mat.use_fake_user = True  # usually handy
    obj.active_material = mat
    nodes = mat.node_tree.nodes
    nodes["Diffuse BSDF"].inputs[0].default_value = list(k) + [1]

It does a few things:

  • rages through the image (if doesn't really care if it's low or high res, but high res will slow it down)
  • Indexes the coordinates of all similar coloured pixels
  • generates a quad based mesh to represent each pixel, and joins them all in one mesh object for one colour
  • makes a cycles material node for each object and sets it to the indexed color.

doesn't do
Several post processing steps

  • doesn't join the resulting objects into one multi-material object
  • doesn't perform remove doubles / limited dissolve

Warning:

This script assumes your low res image doesn't have thousands of different colours. It will create as many different objects as colors found in the image. Tip is to first Quantize the image or let some tool index the colors for you. This will perform terrible with lots of gradients, if you need gradients this script is not for you. It's writen in the spirit of low res low dynamic range images.

$\endgroup$
  • $\begingroup$ Thanks a lot! I hope you don't mind if I keep it as a file and use it on other projects. Thanks again! $\endgroup$ – TrakJohnson Jul 9 '15 at 9:03
  • $\begingroup$ You can use it any way you wish, hopefully it shows how useful it can be to learn a programming language. $\endgroup$ – zeffii Jul 9 '15 at 9:15
  • $\begingroup$ i made a similar (probably less elegant) add-on version of the same problem for 2.79 github.com/benkl/pixeler $\endgroup$ – BK. Feb 22 '18 at 1:43
3
$\begingroup$

This method may be not efficient, but this is how I did it :)

  • Add a plane
  • Edit Mode, press U and choose Unwrap
  • UVeditor panel, open the picture and fit the lines in the picture
  • Properties panel, modifier section, use Subdivision Surface modifier, choose the 'Simple' option and set the 'View' value to 5, for example.
  • Then use the Displace modifier, press 'New' and set 'Texture Coordinates' to 'UV', and set strength value to 0.1
  • Textures section, press the 'Open' button and choose your picture. Now you should see the picture applied to the plane in 3D view
  • Apply the modifiers and apply the upper modifier first.
  • Edit Mode, delete the vertices you don't like.
  • Use the decimate modifier, choose 'unsubdivide' and increase the number of iterations
  • Apply modifier.
$\endgroup$
  • $\begingroup$ The first three steps you can skip it you create the plane with the import images as planes adon: It will create a plane with the same aspect ratio as your image, UV unwrap it and apply the material automatically... $\endgroup$ – cegaton Aug 17 '15 at 15:04
  • $\begingroup$ This was helpful, and when performing this action with a higher resolution image, I simply increased the Subdivision->View to 11 to increase the resolution of the new mesh. Thanks! (Yes, that means I did...crank it up to 11)... $\endgroup$ – ryancdotnet Dec 20 '16 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.