July 4, 2015

A study of secondary feature lines in Freestyle

Filed under: Blender, Freestyle — blenderyard @ 4:29 PM

Highly detailed 3D scans of live humans and other objects have been increasingly available in the computer graphics market these days, providing an interesting venue for cost-effective generation of non-photorealistic (NPR) illustrations with a variety of artistic styles.  A follow-up of the previous case study using a 3D-scanned model, here is another Freestyle line stylization study using a full body model of a woman (RP Wom Rosy 0095) from Renderpeople.  The input mesh consists of 100K triangular faces.

rosy_sketchy_1_tn rosy_sketchy_2_tn

This time a specific focus was put on silhouettes, suggestive contours and ridges/valleys with the aim of capturing subtle surface curvatures and small bumps.  Silhouettes in Freestyle are often clean and easy to capture.  In contrast, suggestive contours and ridges/valleys tend to be jagged and noisy.  However, they are also known to be useful for highlighting minor (secondary) features of smooth surfaces.  Hence how to post-process these types of secondary feature edges is a challenge.

rosy_sketchy_3_tn rosy_sketchy_4_tn

In this study, the secondary feature edges were smoothed by a Bezier Curve geometry modifier, in combination with chain splitting conditioned by a minimum angle threshold to ease the non-linear curve fitting (prone to numerical stability that leads to unexpected results).  The smoothed secondary lines were then grouped into shorter and longer ones by a length threshold.  The shorter lines are here considered less important, so they are drawn with a light gray color.  The longer lines are instead supposed to be of more importance in delivering geometry information to the audience.  Hence they are drawn in a solid black color, with a quick color gradient toward the light gray at both ends to smoothly get connected with the shorter lines.  Silhouettes on the other hand were simply drawn in black with slight thickness variability along stroke.

rosy_sketchy_5_tn rosy_sketchy_6_tn

All the renders in this blog article are results of the same model and the documented line stylization approach, with minor case-by-case tuning of selected parameters depending on viewing geometry (i.e., camera positions).  Specifically, close-up renders often have less line density, so that the length threshold was decreased to get more secondary lines drawn in the solid black color.


May 12, 2015

Fixing undesired gaps in Freestyle lines

Filed under: Blender, Freestyle — blenderyard @ 4:08 AM

Freestyle line drawing may contain undesired gaps that show discontinuity in otherwise consistent outlines.  Undesired gaps are often observed in silhouette lines.  The problem is that silhouette lines can be partly occluded by the faces that define the silhouettes.  The first remedy of the issue is to enable the Face Smoothness option found as part of the Freestyle edge detection options.  When this option is disabled, silhouette edges are foreseen to be jaggy (as described in a separate post in the Freestyle Integration into Blender blog) and hence more prone to undesired gaps.  With the Face Smoothness option enabled, resulting silhouette lines will be much smoother and better suited for line visibility tests.

Turning the Face Smoothness option on may still fail to fix some of the line gaps.  This is a technical limitation of the current smooth silhouette line detection algorithm implemented in Freestyle.  Interested readers are addressed to an academic research paper by Bénard et al. (2014), “Computing smooth surface contours with accurate topology”, in which smooth silhouette lines computed by Freestyle are referred to as interpolated contours.  As the word “interpolation” suggests, Freestyle silhouette lines are somehow “guessed” from other existing mesh edges, so that some degree of computational inaccuracy is expected.

Inaccurate silhouette lines mostly come from coarse mesh faces.  Smooth silhouettes are computed from existing mesh edges, so that the coarser the mesh faces are, the less accurate the interpolated silhouette lines are.

Undesired gaps in smooth silhouette lines can be addressed to some extent by manually subdividing mesh faces locally around the places where the gaps are observed.  It is noted that artists usually have their own preferred polygon counts for given mesh models, so subdividing mesh faces of the entire models is unlikely a practical solution.

Below a case study on fixing undesired gaps by manual mesh face subdivision is presented.  In the following test render, a small gap (highlighted in red and enlarged) is observed in a silhouette line of the white shirt.  This is a typical example of line gaps due to inaccurate guesses of smooth silhouettes.


As shown below in the screen capture of the mesh data in Blender, the faces around the line gap (smoothed by a level 2 Subdivision Surface modifier) are indeed quite coarse (especially so when compared to a mesh resolution that would be required to represent the quick curve of the outline).  Thick orange lines in the image indicate Freestyle feature edges (so-called view map as a whole) visualized as a curve object by a custom style module (, see the post #2894 of the BA Freestyle thread).  This way the gap in the silhouette line can be seen not only from the camera view but also from an arbitrary viewing angle (as in the right and left 3D viewport, respectively), making it straightforward to identify on which mesh face the silhouette line having the gap resides.


Now the quad face causing the gap in the smooth silhouette line is manually divided into two triangles as illustrated below.


The following rendering result shows that the gap has been successfully fixed by the mesh tweak.


Obviously the applicability of the documented manual solution is limited.  Locally subdividing mesh faces may cause other visual artifacts instead of line gaps.  Model and camera movements might also make it difficult to apply manual mesh tweaks.

The upside is that the manual method gives a guideline for artists to tune input mesh data in a manner favorable to Freestyle.  The present case study illustrates how to locate the spots that need optimizing.  It is always the case that tuning mesh data while keeping Freestyle in mind gives a better line rendering result.

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.


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).


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.


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:


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.


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.


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.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred))
shaders_list = [
    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
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
                    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
                    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
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('Wrote', png_filename)

October 17, 2014

Contour lines as in maps

Filed under: Blender, Freestyle — blenderyard @ 4:40 PM


This is a quick proof-of-concept render of contour lines (as in geographical maps) drawn by Freestyle in Blender.  The contour lines are genuine edges in mesh data annotated by Freestyle edge marks.  These edges were auto-generated by a script based on the Intersect mesh-editing command (new in 2.72) to identify contour lines, i.e., cross-section lines with a plane at different heights at a regular interval.


import bpy
import math
from mathutils import Vector

def exec_slices(context, interval):
    print('interval', interval)

    ob = context.active_object

    rad = max(Vector(point).length for point in ob.bound_box)
    print('rad', rad)

    zmin = min(point[2] for point in ob.bound_box)
    zmax = max(point[2] for point in ob.bound_box)
    print('zmin', zmin, 'zmax', zmax)

    imin = int(math.ceil(zmin / interval))
    imax = int(math.floor(zmax / interval))
    zvals = [i * interval for i in range(imin, imax+1)]
    print('zvals', zvals)


    rot = Vector((0.0, 0.0, 0.0))
    for z in zvals:
        loc = Vector((0.0, 0.0, z)) + ob.location
        do_slice(rad, loc, rot)


def do_slice(rad, loc, rot):
    bpy.ops.mesh.primitive_plane_add(radius=rad, location=loc, rotation=rot)





class Slices(bpy.types.Operator):
    bl_idname = "object.slices"
    bl_label = "Slices"
    bl_options = {'REGISTER', 'UNDO'}

    interval = bpy.props.FloatProperty(name="Slice Interval", default=1.0, min=0.01, max=100)

    def poll(cls, context):
        ob = context.active_object
        return ob is not None and ob.type == 'MESH'

    def execute(self, context):
        exec_slices(context, self.interval)
        return {'FINISHED'}

def register():

def unregister():

if __name__ == "__main__":

    # test call

October 9, 2014

Under the hood: Stroke animation with Freestyle for Blender

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

During the Christmas and new year holidays at the end of 2010, I was experimenting an idea to realize animated strokes using Freestyle for Blender.  The idea was to create an animation movie clip in which a set of stylized lines is gradually drawn as time goes.  After a few attempts of coding in Python using the Freestyle Python API, I got a style module that achieved the desired visual effects.  I made several video clips to demonstrate the new stroke drawing module by the end of new year holidays.  I was quite satisfied with the results.

After a while, I came to know that I had deleted the stroke drawing module file by accident.  Relevant .blend files and output movie clips were salvaged, but the core of the coding work was permanently lost.  Since then it was a long-standing to-do item for me to re-implement the stroke drawing module from scratch.

Recently I got a bit of spare time (for the first time since a long time, after the completion of my intensive freelance work on Freestyle and Blender NPR development supported by the Blender Development Fund).  So I gave another try to stroke animation in Freestyle.  Here is the result (click the image below to jump to the finished product posted on Vimeo):


Technically speaking, the stroke animation is implemented as follows.  First a set of stylized strokes is created as usual using Freestyle, namely either through the interactive Parameter Editor mode or in the coder-oriented Python Scripting mode.  The basic strategy for animated strokes then is to make only a fraction of the entire stylized strokes visible, where the fraction is set to the ratio of the number of rendered frames (i.e., the current frame number) to the total number of frames.  To make the visual effects as general as possible, I have implemented the stroke drawing effect as a post-processing procedure as follows:


from freestyle.types import Operators
from freestyle.utils import getCurrentScene

def postprocess(frame_start=None, frame_end=None, interval=0):
    frame_start: the start frame (default Scene.frame_start)
    frame_end: the end frame (default: Scene.frame_end)
    interval: the number of frames inserted as interval between two strokes

    totlen = 0.0
    nstrokes = Operators.get_strokes_size()
    #print('#strokes', nstrokes)
    for i in range(nstrokes):
        stroke = Operators.get_stroke_from_index(i)
        totlen += stroke.length_2d
    #print('totlen', totlen)

    scene = getCurrentScene()
    sta = scene.frame_start if frame_start is None else frame_start
    end = scene.frame_end if frame_end is None else frame_end
    cur = scene.frame_current
    fac = (cur - sta) / (end - sta)
    #print('fac', fac)

    totDrawingFrames = (end - sta + 1) - interval * (nstrokes - 1)
    if totDrawingFrames < 0:
        raise RuntimeError('The number of frames is too small')

    lengthPerFrame = totlen / totDrawingFrames
    #print('lengthPerFrame', lengthPerFrame)

    thresh = (cur - sta + 1) * lengthPerFrame + 1e-6
    #print('thresh', thresh)

    curlen = 0.0
    for i in range(nstrokes):
        stroke = Operators.get_stroke_from_index(i)
        for svert in stroke:
            svert.attribute.visible = curlen + svert.curvilinear_abscissa < thresh
        curlen += stroke.length_2d + lengthPerFrame * interval

In the case of Suzanne the monkey drawn by thick brush strokes, the following style module was used (that is, a slightly modified version of


from freestyle.types import *
from freestyle.predicates import *
from freestyle.functions import *
from freestyle.chainingiterators import *
from freestyle.shaders import *

class pyProjectedYBP1D(BinaryPredicate1D):
    def __init__(self, iType=IntegrationType.MEAN):
        self.func = GetProjectedYF1D(iType)
    def __call__(self, i1, i2):
        return (self.func(i1) > self.func(i2))
Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0)))
## Splits strokes at points of highest 2D curavture
## when there are too many abrupt turns in it
func = pyInverseCurvature2DAngleF0D()
Operators.recursive_split(func, pyParameterUP0D(0.2, 0.8), NotUP1D(pyHigherNumberOfTurnsUP1D(3, 0.5)), 2)
## Keeps only long enough strokes
## Sorts strokes by the maximum projected Y coordinate value
shaders_list = [
    pyNonLinearVaryingThicknessShader(2, 16, 0.6),
    ConstantColorShader(0, 0, 0),
Operators.create(TrueUP1D(), shaders_list)

import stroke_anim; stroke_anim.postprocess(interval=10)

Notice the last line in the stylization script where the post-processing procedure is invoked.  By animation rendering from 1 to some frame count (400 in the case of brush-stroked Suzanne), a set of stylized lines created by will be transformed into a sequence of animation frames where the strokes are “drawn” over time.  The interval parameter specifies a pause between two strokes in units of frames.  Stroke animation can also be done in the Parameter Editor mode of Freestyle by manually modifying script/freestyle/module/ in your Blender installation as indicated above in the code example.

I believe the idea of having post-processing effects after the creation of Freestyle strokes can be generalized in the future in such a way that artists can specify either user-defined Python procedures or predefined Parameter Editor options to further retouch auto-generated stylized lines.  Some coding effort is required for now, however, to reproduce the documented stroke animation effects.

July 4, 2014

Freestyle renders of a 3D scanned ram skull

Filed under: Blender, Freestyle — blenderyard @ 4:20 PM

The following images are Freestyle renders of a 3D scanned ram skull model (available at the Ten24 3D Scan Store).  The rendering was done with Blender 2.71.  Short hatching-like lines are suggestive contours smoothed by the Bezier Curve geometry modifier and tapered by the Along Stroke thickness modifier.




April 14, 2014

World’s first fanzine on Blender and Freestyle (2013)

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


I finally got a copy of the world’s first fanzine on Blender and Freestyle published in February 2013 (note that the publication date was even before the first Blender release that shipped Freestyle).  Many thanks to @blekei, @mato_sus304 and @tksg8086 for the wonderful publication!  This is absolutely rewarding!

April 10, 2014

Fancy line stylization tryouts

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


The image above shows the outcome of 4 fancy line styles, all done using Freestyle in the Parameter Editor mode (without Python scripting) in Blender 2.70.

February 17, 2014

Yukicotan: A capability study of Freestyle (Blender 2.69)

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

A capability study of Freestyle (Blender 2.69) was done using a 3D model recently released at

Yukicotan_test1b_tn Yukicotan_test1a_tn Yukicotan_mesh_tn

The image to the left is a simple render composed of silhouettes (thin lines) and external contours (thick lines).  The image in the middle consists of the same silhouettes and external contours as well as suggestive contours (dotted lines).  The image to the right shows the mesh data (841K faces) imported into Blender.  No textures were included in the original 3D model (specifically prepared for 3D printing), so that only the shaded surface and auto-generated lines are shown in the Freestyle renders.

Older Posts »

Create a free website or blog at