Blenderyard

November 5, 2014

Stereographic projection of line art

Filed under: Blender, Freestyle — blenderyard @ 2:54 PM

I came across an interesting blog post describing how to create lampshades using backward stereographic projection from 2D input images.  Following the detailed instructions given by the blog author, I had a few hours of fun math programming exercise in Matlab to reproduce the same visual result.  As an input 2D image I chose a variation of a Japanese traditional line art pattern called Seigaiha (青海波), here created using Freestyle in Blender.

seigaiha_tn

The input image underwent a series of raster/vector image processing including black/white component labeling, contour simplification, and 2D mesh generation to obtain 3D mesh data of a spherical lampshade as a result of backward stereographic projection described in the reference document.  The 3D geometry data was then imported to Blender and rendered with Cycles.  The screen capture below shows a view port render of the imported mesh data (with the Solidify modifier applied to give physical thickness).

seigaiha_screenshot_tn

A top-view render below with a stronger point lamp shows a clear shadow pattern of the lampshade that nicely matches the input Seigaiha texture image.

seigaiha_output_top_tn

Advertisements

November 2, 2014

Pixelating Freestyle lines

Filed under: Blender, Freestyle — blenderyard @ 3:41 PM

Thin, pixelated lines without anti-aliasing are one of line styles that Blender artists may want to achieve using Freestyle time to time.  This line style is useful for preparing low resolution game assets and imitating old-fashion computer line drawing, just to name a few examples.  Such visual results can be obtained by disabling anti-aliasing and set line width to 1.  However, the resulting quality of pixelated line art may not be satisfactory due to possible thickening of lines here and there depending on input scenes.  Setting line width to a value less than 1 could remove undesirable thick portions of lines, but also may cause broken line in other parts because of sub-pixel line thickness.  Unlike traditional line drawing in old computers, Freestyle renders line art using full-featured 3D rendering engines (namely the Blender Internal and Cycles) that are not specifically designed for producing retro style aliased lines.

An easy way to address this limitation is just to implement a desirable aliased line drawing function within the framework of Freestyle line rendering.  A couple of proof-of-concept renders based on this approach are shown below:

pixelate_suzanne_tn

The left and center panels of the image above show color and line components, while the right panel shows the composite result.  The image has been scaled 400% so as to show the pixelated lines clearly.

pixelate_saturn_tn

This one shows raw render results without scaling.

Now the fun part: these renders were generated using a custom style module written in Python.  In the style module, a traditional aliased line drawing function based on Bresenham’s algorithm has been used to create a set of pixels that comprise pixelated lines.  The computed X and Y coordinates of the pixels are then passed to an external program (the convert command of the ImageMagick graphics package) to rasterize the lines.  This last step can be done in different ways using other 2D graphics libraries (e.g., Cairo), and the use of the convert command is just for testing the idea.  The code is presented below (tested with Blender 2.72b).  This style module can be used in the Python Scripting mode of Freestyle in Blender.  If you want to try it yourself, then you need to modify the full path to ImageMagick’s convert command hard-coded in the program.

# pixelate.py

from freestyle.types import *
from freestyle.chainingiterators import *
from freestyle.predicates import *
from freestyle.shaders import *
from freestyle.utils import *
import bpy
import os

# full path to ImageMagick's "convert" command
CONVERT_COMMAND = r'C:\home\kajiyama\bin\convert.exe'

alpha = 0 # set to 1 for preview render
upred = QuantitativeInvisibilityUP1D(0)
Operators.select(upred)
Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred))
shaders_list = [
    ConstantThicknessShader(1),
    ConstantColorShader(0, 0, 0, alpha),
    ]
Operators.create(TrueUP1D(), shaders_list)

# Based on the implementation of Bresenham's line drawing algorithm
# by Steve Cunningham and Tim Worsham, October 1988, CSU Stanislaus
# http://www.cs.csustan.edu/~rsc/SDSU/Interpolation.pdf
def line(x1, y1, x2, y2):
    pixels = []
    bx = x1
    by = y1
    dx = x2 - x1
    dy = y2 - y1
    if dy == 0: # horizontal line
        pixels.append((bx, by))
        xsign = -1 if dx < 0 else 1
        while bx != x2:
            bx += xsign
            pixels.append((bx, by))
    elif dx == 0: # vertical line
        pixels.append((bx, by))
        ysign = -1 if dy < 0 else 1
        while by != y2:
            by += ysign
            pixels.append((bx, by))
    else: # Bresenham's line drawing algorithm
        pixels.append((bx, by))
        xsign = -1 if dx < 0 else 1
        ysign = -1 if dy < 0 else 1
        dx = abs(dx)
        dy = abs(dy)
        if dx < dy: # the line is more vertical than horizontal
            p = 2 * dx - dy
            const1 = 2 * dx
            const2 = 2 * (dx - dy)
            while by != y2:
                by += ysign
                if p < 0:
                    p += const1
                else:
                    p += const2
                    bx += xsign
                pixels.append((bx, by))
        else: # the line is more horizontal than vertical
            p = 2 * dy - dx
            const1 = 2 * dy
            const2 = 2 * (dy - dx)
            while bx != x2:
                bx += xsign
                if p < 0:
                    p += const1
                else:
                    p += const2
                    by += ysign
                pixels.append((bx, by))
    return pixels

scene = getCurrentScene()
w = scene.render.resolution_x * scene.render.resolution_percentage / 100
h = scene.render.resolution_y * scene.render.resolution_percentage / 100

# write pixels as a set of drawing instructions in the Magick Vector Graphics (MVG) format
# http://www.imagemagick.org/script/magick-vector-graphics.php
pixels = []
nstrokes = Operators.get_strokes_size()
for i in range(nstrokes):
    stroke = Operators.get_stroke_from_index(i)
    points = [(int(svert.point.x), (h-1)-int(svert.point.y)) for svert in stroke]
    prev = points.pop(0)
    for curr in points:
        pixels.extend(line(prev[0], prev[1], curr[0], curr[1]))
        prev = curr
mvg_filename = bpy.path.abspath(scene.render.filepath + "%04d_line.mvg" % scene.frame_current)
with open(mvg_filename, 'wt') as mvg:
    for x, y in pixels:
        mvg.write("point %d,%d\n" % (x, y))
print('Wrote', mvg_filename)

# render the pixels using the ImageMagick convert command
png_filename = bpy.path.abspath(scene.render.filepath + "%04d_line.png" % scene.frame_current)
cmd = '%s -size %dx%d -background none "%s" "%s"' % (CONVERT_COMMAND, w, h, mvg_filename,png_filename)
#print(cmd)
os.system(cmd)
print('Wrote', png_filename)

Create a free website or blog at WordPress.com.