7
$\begingroup$

I often run into a situation where I create Image Maps in Illustrator, export them as PNG, and when I apply them into my mesh and zoom in, pixelation from the PNG appears. This annoys me a lot in 3D and games (see this model imported from a Wii U game, which gets pixelated as soon as you zoom in)

enter image description here

Wouldn't it make more sense to be able to import an image map as a vector format, since Blender accepts (in some instances) SVG files?

I haven't been able to do this...

EDIT: There are a lot of great answers which are also very tedious and can't really be incorporated in a regular workflow. The question was more about mapping SVG like this one on a cup object like this one to show a client an idea quickly, etc.

$\endgroup$
  • 1
    $\begingroup$ Raster will always pixellate if you get close enough, but it's not an issue if you limit how close the camera can get. If it gets close enough to see the pixels, then modelling the geometry instead of using textures is needed anyway. $\endgroup$ – dixiepig Apr 5 '18 at 19:32
  • 1
    $\begingroup$ You'd be better off creating UVs and assigning materials directly on the geometry. If you are going to use SVGs, make sure that you convert the images to paths before saving. Paths can be imported as curves into blender. $\endgroup$ – cegaton Jun 11 '18 at 23:45
  • 2
    $\begingroup$ You can plot every graph with formulas. If you like maths, you could create a script which converts your curves to node graphs. For fun I recreated the keyboard base layout with nodes. $\endgroup$ – Leander Jun 13 '18 at 22:34
  • 1
    $\begingroup$ Upload your example svg for testing purposes, please. $\endgroup$ – Leander Jun 14 '18 at 9:14
  • 1
    $\begingroup$ For testing purposes here is a free SVG of a keyboard (click the blue arrow under the thumbnail) - you can try to reproduce the image in the original post. $\endgroup$ – MicroMachine Jun 14 '18 at 17:41
8
+25
$\begingroup$

The requested feature doesn't natively exist within Blender, hence recreating the behaviour is very complex and tedious.

Download the accompanying blend file with various node setups.

Creating shapes with material nodes

If a shape can be recreated with mathematical functions (plotted shapes) it can be created with cycles math nodes.

Let's create a procedural rounded rectangle group. (If you know Blender and math, skip to step 3.)

  1. On a large plane, add a material with an Object Texture Coordinate input.
    node setup
    We're going to focus on the X, Y positive area (marked blue) first.
  2. Let's by defining the bounds for the rectangle, separate the XYZ channels and add two greather than nodes for X an. Add them together clamped.
    enter image description here
    The black is the interiour of the rectangle in the XY positive area. Make sure your object's scale is applied as we're using local coordinates. Strangely enough, we don't need these nodes, discard them!
  3. Create a circular shape using the unit circle functions. In our case it is simple: radius^2 = x^2 + y^2
    This means, that for every point inside the circle, the following is true: `sqrt(x^2 + y^2) < radius.
    In nodes, add a Power node for each x and y, then an add node to add them together, then another Power node for the square root. Finally a greather than to determine if the point is outside of the circle (= if the radius is to great).
    radius nodes
    The 2nd power node with a value of 0.5 is missing in the image. Don't forget, the effect of the nodes is seen in material view and rendered view only.
  4. Offset the X and Y coordinate by rectangle-"radius" minus rounded corner radius.
    enter image description here
    Regardless of the value of the CornerRadius, the circles bounds are clamped to the RectangleSize bounds.
  5. Clamp the values left and below of the circle respective X and Y diameter.
    enter image description here
    These are the desired areas. To get the red area we multiply x < rectangleRadius - circleRadius with `y < rectangle radius, and for the blue area switch the inputs. Then add both results together.
    enter image description here
    This is the node graph, after multiplying this rectangle restriction onto the circle, a neatly rounded corner.
    enter image description here
  6. To make the rounded corner XY symmetrical, add an abs node each to the X and Y outputs form the separate RGB node.
    symmetrical rounded rectangle
  7. Before the abs node, add a subtract node, to be able to shift the x and y coordinates.
    enter image description here
  8. Make the end result influence a color mix node.
    enter image description here
  9. Make it a group with an input for inputColor, Vector Coordinates, offsetX, offsetY, Size and CornerSize.
    enter image description here
    Nodegraph before grouping
  10. But wait, add support for a different height and width. Figure it out yourself or download the blend.

Go wild, this is rather slow.

enter image description here

Creating shapes based on functions with nodes

Let's recreate this easter egg.

  1. The first function will be converted as we did with the first example. I will interpret
    (x-10)^2 / 300 + y^2 / 600 = 1
    as
    easter egg nodes
  2. I added some arbitrary sine functions.
    enter image description here

Creating a shape with OSL

The open shading language enable us to use a custom script node. Knowlegde of C and Python is required for the following part. (Since I don't really know any C or Python, the code samples are messy.)

You might want to head to BlenderSushi and complete their OSL series.

We'll create a stop sign polygon octagon.

  1. I'll hard code some coordinates. Store the points in a string and convert them to a c style array with this python snippet. I just manually copied and pasted the coordinates separated by spaces and commas and the manually copied and pasted the generated code. Execute the snippet in any python interpreter.
pts = "-1,2 1,2 2,1 2,-1 1,-2 -1,-2 -2,-1 -2,1"
pts = [[float(n) for n in m.split(",")] for m in pts.split(" ")]
pts.append(pts[0])

pt_array = "point pts[" + str(len(pts)) + "] = {"
for pt in pts:
    pt_array += " point(" + str(pt[0]) + ", " + str(pt[1]) + ", 0),"
pt_array = pt_array[:-1] + "};"

print(pt_array)

This generates the following output.

point pts[9] = { point(-1.0, 2.0, 0), point(1.0, 2.0, 0), point(2.0, 1.0, 0), point(2.0, -1.0, 0), point(1.0, -2.0, 0), point(-1.0, -2.0, 0), point(-2.0, -1.0, 0), point(-2.0, 1.0, 0), point(-1.0, 2.0, 0)};
  1. In Blender, create a new text block and start off with a basic osl shader

    shader simpleColor( point Vector = P, output color col = color(0.0)) { // insert our custom point array here }

  2. Select the textblock in a script-node in the material nodes. Activate Open Shading Language in the Render Settings.
    script node cycles

  3. Now we got our polygon in our osl shader. To detect if a point lies in the polygon, we are going to implement a simple version of the Ray Casting algorithm. There are plenty of resources on point-in-polgyon topics on stackoverflow. I'll cast a ray from the y minimum and check with how many polylines it intersects. If the number is a multiple of two, the point must lie outside the polygon.
  4. Create a simple function, which will take two points of a linear mathematical function and an x value and return the corresponding y value.
float linear_function(float x, point p1, point p2) {
    float x1 = p1[0];
    float y1 = p1[1];
    float x2 = p2[0];
    float y2 = p2[1];
    if (x1 == x2)
        return 0.0;

    float m = ((y2 - y1) / (x2 - x1));
    float n = y1 - m * x1;

    return (m*x+n);
}

7. Now, we'll add another function to detect if a point lies above a polyline. First we'll check if the x coordinate is inbetween the two points of the line, then we'll compare the y value of the function between the two lines with the y value of our point.

int hit(point p, point p1, point p2) {
    float x = p[0];
    if (p1[0] == p2[0])
        return 0;
    if (p1[0] < p2[0])
        if (p1[0] > x || p2[0] < x)
            return 0;
    if (p1[0] > p2[0])
        if (p1[0] < x || p2[0] > x)
            return 0;

    if (linear_function(x, p1, p2) > p[1])
        return 1;
    return 0;
}
  1. With these two functions added, the shader can be tested with 2 points. Note, that osl shaders are only visible in rendered preview mode. enter image description here
  2. We can also use our stop sign coordinates. enter image description here

Notes on svgs

With these tools you are equipped to start writing your svg plugin. The hit function has to be implemented for curveto, smooth curveto, quadratic Bézier curve, smooth quadratic Bézier curveto and elliptical Arc and the python script has to convert all types of the svg path. It's actually not that hard to compute further intersections, but parsing the svg is going to be a lot of work.

The final goal is to write a python script, which parses an svg (with python's xml parser) and converts is to a osl shader script. The points and curves would be hardcoded in the shader.
Unfortunately, we have to first implement the most basic collision detection for beziers (see the previous link), because most svgs will use them, and possibly for all other svg elements (see the previous2 link).

$\endgroup$
  • $\begingroup$ I like the answer a lot but I don't think it could be incorporated in a regular workflow. The question was more about mapping SVG like this one on a cup object like this one to show a client an idea, etc. $\endgroup$ – MicroMachine Jun 20 '18 at 21:42
  • $\begingroup$ @MicroMachine Did you actually go through the answer, do all steps and read all links? No, because the blend file is broken (I just noticed and will fix it: OSL is disabled and the code is wrong). You've also supplied three different svgs. My answer works well with the images in the question. If I extend my answer to supply an easy way with the next sample svg, will you change it again? (Read the last two links! This answer isn't the solution, but the path to the solution: your own addon to import svgs.) $\endgroup$ – Leander Jun 21 '18 at 6:32
  • $\begingroup$ @Leander no prob, I'll go through it in detail again and get back to you! $\endgroup$ – MicroMachine Jun 21 '18 at 6:37
  • 1
    $\begingroup$ @MicroMachine I have recreated this svg and will start writing the converter myself, as soon as I find time. The script mentioned in the edit at the end, will still be easier to create than altering the source code and creating your own build with svg support. $\endgroup$ – Leander Jun 21 '18 at 7:50
  • $\begingroup$ @Leander thanks for the time and effort you put into this, it's way beyond my skills but I'm excited to see the converter you make! Keep me posted. I accepted your answer based on your dedication and the potential of that converter $\endgroup$ – MicroMachine Jun 21 '18 at 17:29
3
$\begingroup$

One option is to import your SVG using the SVG import addon. SVG Addon We'll use this test image. SVG Logo Select File> Import > Scalable Vector Graphic SVG Import

In my case, not a lot appears to have changed, but I have some curves now in the outliner. SVG Imported Curves

If I zoom in, I can see the image I imported, but there are some problems. Note that blender has automatically created a set of basic diffuse materials (called "SVGMat.001...") for each color in the image. Editing these can be used to change the look of the final result. Directly After Import

I'll start by increasing the scale of all of the curves by 100, then applying that scale with ctrl + a. Increase the scale

Right now all of the curves are intersecting with each other, because they all have the same Z position. I'll go through each curve, and move it up or down by 0.001 or so, to keep them from clipping. In my case, all the layers were imported in order, so it was pretty easy to tell which should be on top, because it showed up last in the outliner. Added Z depth to each layer

To keep all of these curves together, first I'm going to add an empty, then parent all the curves to that empty. Layers parented to empty

There's still one more major problem that needs to be fixed: Cartman's eyes and mouth are not filled in. This is because Blender automatically filled in his feet, but not the other shapes on the same layer. We can fix this by entering edit mode and adding polyfaces for each shape that isn't filled. press tab to enter edit mode with the eyes/mouth/feet layer selected. Curve edit mode

Select and single vertex. Single vertex selected

Press ctrl + L to select all the vertices on the same curve. Select connected vertices

Then press F to fill the shape. Repeat for all of the shapes in the layer. Fill Face

This now gives us a final image that can be moved around and scaled normally by the empty, that has no pixel aliasing. You might notice that some of the larger curves (such as Cartman's face) seem a bit low resolution. For these curves, we can adjust the curve resolution to make bezier curves and arcs render more smoothly. Cartman's Chin

To do this, select the curve you want to change, and increase the resolution value (when Render U Resolution is 0 it copies from the Preview U Resolution, so I changed that value, but pick whichever you want). enter image description here

One final note: the lines on Cartman's chin and jacket will not show up in a render without some more tuning, or using freestyle rendering. This is because they aren't actually faces, just lines, which have no thickness, and therefore don't show up when rendering.

$\endgroup$
  • $\begingroup$ This is great, thanks! Would the elements stay in place when you use it against a mesh? Can you detail the mapping process? It seems this technique would work best with some manageable vector images (no gradients, limited nb of elements...) $\endgroup$ – MicroMachine Jun 13 '18 at 21:54
  • $\begingroup$ Yeah, this is not sustainable for anything particularly complex. There is no (UV) mapping technique because the imported objects are curves, not meshes (curves can't have UV data). It's basically the same as creating objects and applying materials to the entire thing, rather than creating UV maps and applying textures. The elements are all children of the empty, so as long as you just move/rotate/scale the empty around, all the layers should stay in place. $\endgroup$ – Tavi Kohn Jun 13 '18 at 22:12
  • $\begingroup$ I'm just wondering why it's possible to interpret/import SVG data into blender as editable objects, but not possible to import them as a 2d image in the UV mapping tools (without editing the data). Would make more sense for UV Mapping than limited bitmap data. Something similar is how InDesign handles an Illustrator image that you place into it. $\endgroup$ – MicroMachine Jun 13 '18 at 22:34
  • $\begingroup$ UV maps are for mapping pixels to mesh faces. Because Vector graphics don't have pixels, the UV mapping workflow doesn't really apply. You could consider this a limitation of Blender, but I don't really blame them for not wanting to implement this functionality; it sounds difficult. $\endgroup$ – Tavi Kohn Jun 14 '18 at 1:29
  • 1
    $\begingroup$ Curves are converted to flat polygons for render in Blender anyways. Keeping these as curves may be the more memory-efficient way to store some data, and may be easier to edit than textures, but there will still be pixellation until you edit the curve to have sufficient resolution for your render-- just like there will be pixellation from a texture until you change the texture to have sufficient resolution for your render. To eliminate pixellation fully, you either need 4 vertices per render pixel or 4 texels per render pixel, your choice. $\endgroup$ – Nathan Jun 17 '18 at 18:02
0
$\begingroup$

Make curves in the keyboard in shape of all keys, modifiers>solidify. Move slightly above all the base layer textures. Done. Crisp and detailed because it will be a mesh. To make it a "constant" color, use an emmissive node directly to surface on the shading tree, and check intensity. You´re ready to go.

$\endgroup$
  • $\begingroup$ Your answer only works for the keyboard situation, not to put eyes on a character in a videogame for example $\endgroup$ – MicroMachine Dec 19 '18 at 18:36

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.