Painterly Rendering


By Benjamin Cooley

CS 572 Project from Spring 2010 at Cal Poly. A Painterly Renderer for implicit functions.


Painterly Rendering is a form of Non-Photorealistic used to render 3D objects as if an artist was 'painting' them.  My implementation is baised off of a paper by Barbara J. Meier, "Painterly Rendering for Animation".  When using painterly rendering (or most forms of NPR), you must use a certain amount of randomness to give the pictures a 'hand-drawn' look.  This can cause problems with animation, as random changes from screen to screen stand out.  Meier's process added the randomness into the underlying model and used the same seed for each frame render.   A set of particles are randomly generated and used as locations for brush strokes, and those particles contain the information necessary to render, including the 'randomness'.

Meiers Algorithm:

create particles to represent geometry
   for each frame of animation
       create reference pictures using geometry, surface
           attributes, and lighting
       transform particles based on animation parameters
       sort particles by distance from viewpoint
       for each particle, starting with furthest from viewpoint
           transform particle to screen space
           determine brush stroke attributes from
               reference pictures or particles and randomly
               perturb them based on user-selected parameters
           composite brush stroke into paint buffer
       end (for each particle)
   end (for each frame)

Images from the Meier's paper


My project takes in a set of implicit equations and renders them out in a painterly style.  The internal model was to be as interactive as possible considering render times. 

This project plugs into an early Marching Cubes project to generate 3D geometry from implicit equations.  To take advantage of the marching cubes algorithm, my program baises its 'units' on cubes, instead of particles.  So instead of each particle having its own random seed, each cube has a set of particles (spread out along the triangles in the cube) and a random seed.


My implementation uses a simple MVC design concept, 1 Thread for display (view) of the monitor, 1 for display of the console, one for input from the console.  The model is shared between all Views/Controllers. It contains the location of the camera, a set of Marching Cubes objects (equations and output data from Marching Cubes), and synchornization data.

Monitor View (Painterly Render Algorithm)

When an object is added or updated in the model, it generates a set of particles along its surface.  Particle Density is determined per object and is initally set to 3/cube.  The render renders these particles as brush-strokes with the following attributes:
length: A per-object variable, initially set to three, of how many brush images are drawn in a row.
brush size: A factor of how far away the object is from the camera. Brushes range from 50% of brush image to 200% of brush image. (Brush image should be in same directory as program).
Orientation: A per-object variable, set to (0, 1) initially.
Color: Determined from a flat shade of the model

Particles are rendered in order of distance from the camera.

Console Controller (Commands)

Here are the commands that work in the program:
Add Object
"AddObject", "addobject", "addObject", "add", "Add" AddObject name ImplicitFunction
AddObject ImplicitFunction
Adds an object as described by the function. objects automaticly use a marching cubes algorithm (128 sided grid).
Delete Object
"DeleteObject", "deleteObject", "deleteobject", "delete", "Delete" DeleteObject name
Deletes the given object from the screen
"Move", "move" Move name x y z
Moves name to be centered at x y z
List Objects"ListObject", "listObject", "listobject", "list", "List" List
Lists objects in the scene
Move Camera"MoveCamera", "moveCamera", "movecamera", "movec" MoveCamera x y z
Moves the camera to x y z
Move Camera Target
"MoveCameraTarget", "moveCameraTarget", "movecameratarget", "movet" MoveCameraTarget x y z
Moves the camera target (where the camera is facing) to x y z
Move Relative"RMoveCameraTonight", "rmoveCameraTarget", "rmovecameratarget", "rmove" rmove name x y z
Moves an object realtiviely (oldPoints += newPoint)
Move Camera Target Relative"rMoveCameraTarget", "rmoveCameraTarget", "rmovecameratarget", "rmovet" rmovet x y z
Camera target Relative
Set Color "color", "setColor", "setcolor", "SetColor", "Color" Color num num num
Set color (0 <= num <= 1)
ShowGL"showgl", "showGL", "gl" "GL" ShowGL
Displays OpenGL Render output
Show Paint
"showPaint", "showpaint" "Paint", "paint" ShowPaint
Displays Paint
Show Points "showPoints", "showpoints", "points", "Points" ShowPoints
Displays Points
Points Per Cube
"PointsPerCube", "pointsPerCube", "PPC", "ppc", "pointspercube" PPC num
Changes how many particles are generated per cube <0
"Orientation", "orientation", "o" Orientation num num
1 0
Changes what direction brush strokes are drawn in
abs(x) + abs(y) must equal 1. further, 0 <= x <= 1
Per Stroke
"PerStroke", "perStroke", "perstroke", "ps" PerStroke num
Changes how many brush images per stroke <0


Although randomness was stored in the model, it is not currently being used in the render process.  Particle Placement is random, but stroke attributes are not randomly changed.  Further, Orientation and Stroke Lenght are currently object-level qualities, while they should be baised off of the underlying geometry.

It was hard to get additional data from the underlieing geometry. Since we were working off Marching Cubes, all triangles were close to being equal in size.  While this allowed us to consolidate parts of the program, it no longer makes sense to use the model for certain calculations.

The most annoying problem to deal with was namespaces. Most thigns are currently only object-level accessable, even though the model allows for modification of individual cubes, because there is no easy way to name them.  Commands would have to take in an object name, and an index into the objects internal array of cubes to change a proprety that low.

More Pictures

How to Run and Links

My example brush:

You can access an executable of the program here.
In order to work, you have to have a brush image (Black, Grey, and Transparent only) in the same directory as the executable, named "brush.gif".  Once started, give the program a set of commands like :
movec 30 30 30
AddObject Sphere1 ((x^2)+(y^2)+(z^2)-16)
Move Sphere1 9 9 0
AddObject Sphere2 ((x^2)+(y^2)+(z^2)-16)
Color Sphere1 1.0 0.0 0.0
Color Sphere2 0.0 1.0 0.0