A nonphotorealistic renderer

Antonio Haro

Introduction

Basic nonphotorealistic rendering is used all of the time in CAD systems, as well as in animation. Photorealistic rendering is usually much more computationally expensive and can sometimes leave out important visual cues in a scene. With nonphotorealistic rendering, a drawing of a house with brick walls would only need some representation of brick on one corner of the home; with just that visual cue, humans know that the rest of the walls of the home are made of brick as well.

A renderer for producing pictures that look like sketches was the project that was implemented this quarter. It is based on the work of Markosian, et. al [1]. The main goal of the renderer's design was that it should render multiple brush styles in a relatively fast manner. The different brush styles that were to be implemented were:

Implementation

The system originated from a current research project with Jarek Rossignac. In that project, the aim is to be able to provide higher level of detail in a 3d scene only when those higher levels are needed. Special datastructures and model file formats were designed for that project, and those are the ones used in this project. (The low level geometry file I/O and setting up of the datastructures in tables was coded by David Cardoze; The file format is very similar to the Open Inventor file format).

There are several steps involved in creating a rendering:

  1. Find the (visible) silhouette edges
  2. Parameterize these
  3. Using a parameterized brush function to offset the silhouette edges' tangent and normal vectors, draw the 'sketched' silhouette edges
Let's go through each of the steps in greater detail.

Find the (visible) silhouette edges

To find these, first we jump-start our Z-buffer. To do this, we first render (in white) the entire mesh with all polygons shaded in. Jump-starting the Z-buffer will allow us to later get rid of non-visible silhouette edges (because they shouldn't come up in the drawing.) Once the Z-buffer is jump-started, we can then proeceed to actually finding the silhouette edges in the mesh. A silhouette edge is defined in[1] as:

A polygon is front-facing if the dot product of its outward normal and a vector from a point on the polygon to the camera position is positive. Otherwise the polygon is back-facing. A silhouette edge is an edge adjacent to one front-facing and one back-facing polygon.

Our data structure has the nice property that it is very easy to figure out the normals of the two polygons (triangles for all of our models) that share an edge. Because of this property, the modified version of Appel's algorithm that is described in the paper was not implemented.

Parameterize these

All silhouette edges that are found in the previous step are then individually processed. Each silhouette edge is parameterized as a line so that we can offset all of the different points on the silhouette edge. We want to be able to do this to provide a random/human sketched look to the silhouette edge.

Use these to draw the brush strokes

With parameterized silhouette edges, we can then parameterize the tangent of the edge as well as the normal of the edge. The tangent and normal are used in conjunction with the brush function as vector offsets for the point. The equation for this is:

q(t) = p(t) + vx(t)p'(t) + vy(t)n(t)

where:

So what exactly is a 'brush function' then? A brush function is just a function that will describe what is going on in a particular brush in a very low level. For example, the charcoal stroke's brush function is just a high frequency sawtooth curve. The justification for this can be seen if one tries to draw a charcoal-like scene on paper with a pen; one ends up shaking the pen up and down on the paper in order to achieve similar effects. The same applies here.

Table of brush functions:

BrushBrush function
CharcoalHigh frequency sawtooth curves
LazyLow frequency parabolic curves
HandLow magnitude noise applied along normal and tangent
RoughHigh magnitude noise applied along tangent

Problems/Lessons learned

The major problems that arose during this project were:

Both of these took considerable time, but in retrospect, were easy to fix once the problem was located. The hidden silhouette edges were initially to be removed by using the Z-buffer, but this didn't work, so a myriad of other methods were tried. After much frustration I talked to Greg Turk who informed me that I was initially on the right track. I later found that the problem was with the order matrices were being appliedin OpenGL. While a simple problem to fix, it took a while to find.

(It also didn't help that the silhouette edges were being computed incorrectly due to a bug. Once the bug was fixed, however, everything seemed to fall into place perfectly.)

While not a bug, one annoying thing about the system is that depending on the model, some brushes might appear highly similar. A method of solving this problem while still rendering correct pictures for the rest of the test cases was not devised.

Conclusions

Nonphotorealistic rendering is very interesting. Nice results were gathered from the unoptimized system that was implemented, which could definitely be sped up. It would be very interesting to see a game or some kind of virtual environment that uses nonphotorealistic rendering for all rendering.

Nonphotorealistic rendering also allows some of us with little or no artistic talent to create masterpieces (or, at least nicer drawings than we would be able to draw ourselves!)

Gallery of results

References

[1] Lee Markosian, Kowalski Michael A., Trychin Samuel J., et. al, Real-Time Nonphotorealistic Rendering, Brown Univeristy, SIGGRAPH 1997


Back to my main page