Palette Based Renderer:

A Non-Photorealistic Rendering Tool

Project Outline

Progress Update 1, March 7, 2001

Progress Update 2, April 11, 2001


Project Final Report

Project Description

The goal for this project was to provide a general system for users to create non-photorealistic rendering effects. The way that the user specifies the type of effect they want is to provide the renderer with a palette of textures. For example the user could provide a palette of 20 wood grain textures ranging in color from a light colored pine to a dark cherry wood. The system would then be able to render geometry, using the various wood textures instead of lighting. The result would be a model rendered with light colored wood mapped to triangles that are brightly lit and dark colored wood mapped to triangles that are not heavily lit.

The major novelty and benefit of such a system over most standard non-photorealistic rendering techniques is that it allows users to create a wide variety of effects, ranging from artistic styles like crosshatching to the more bizarre effects you will see demonstrated below, all by simply changing the palette of textures . The obvious drawback is that it doesn't have the ability to do some of the things available in  any custom implementation of single NPR techniques.  For example, some non-photorealistic rendering system that implement sketching techniques like crosshatching could align the direction of the crosshatches to the curvature of  the model. This is not possible within our system. We see the benefits of allowing users  to express their creativity and invent new and exciting NPR rendering styles greatly outweighs this drawback.

Using The System

1)    The first step in using the system is to initialize the renderer. This is done by reading in a text file with the names of the .tga files that make up the texture palette and, optionally, the name of a background image. The renderer then reads in the textures from the files and does some preprocessing on the images to extract their average intensity value (taking into account any alpha blending with the background).

2)    Once the renderer in initialized the render state can be set. This is done with calls to functions similar to the OpenGL render state functions. For example the function:
 

void Lightfv(int LightID, int ParamName, float *Value);


allows a user to set a lighting parameter just like in OpenGL. The important parameters that the renderer supports are for setting up the light and material properties.

The system also has a few custom state variables which can be set. These are used to specify how the system chooses the textures to map to the object.

  • ColorMatchMode - controls how the method used to match textures to colors.
  • - In MATCH_COLORS mode it tries to match over all color channels, so that a blue light triangle will be matched to a blue texture.
    - In MATCH_INTENSITY mode it only looks at the overall brightness of the triangle and matches it to a texture of similar brightness.
    - MATCH_ORDER mode is useful for texture palettes that do not vary in average intensity. In this mode it chooses textures based on the order they appear in the input text file, with brighter target triangles mapping to textures further down on the list.
  • IntensityRangeScale - allows scaling of the range covered by the texture palette. If the textures in the texture palette only hit a small range of intensities, the IntensityRangeScale can be used to spread them out.

  • 3) Once the render state is set. The system behaves just like OpenGL. To render some triangles (the only supported primitives) call Begin(), then RenderTriangle(Tri) to send the triangles and finally End() to complete rendering.

    Implementation

    The most significant portion of the implementation is dealing with the call to render triangles. The code for state management was adapted from code in the SoftGL system written by Kenneth E. Hoff III for the COMP 236 class. So assuming that we have a way to manage the render state, let's walk through the rendering pipeline and examine how it is implemented.

    1)  The Begin() call, to begin rendering,  just clears the some internal data structure in the renderer to prepare for the next rendering frame.

     2) Each RenderTriangle(Tri) call sends one triangle to be rendered. In our renderer all rendering is done in a deferred fashion. When a triangle is sent to be rendered the first step is to determine what the lighting color of that triangle should be in the OpenGL lighting model. We choose the center of mass of the triangle and use a software implementation of the OpenGL lighting model to compute the lighting intensity at that point, given the state of the lights and the material properties. Once this is done we must decide which texture best matches that lighting color.

    Texture matching is done in several ways. Keep in mind that as a preprocess we have already computed the average intensity of all of the textures in the texture palette. If the color matching mode is MATCH_COLORS we treat the lighting color of the triangle and the average color of each texture as vector in R3 . We then apply a norm to compute the distance between the textures color and the triangle's color and choose the texture that is closest. If the color matching mode is MATCH_INTENSITY we take the maximum value of the three channels of the triangle's color, multiply it by 3 and use that as it's intensity value. For the texture average color we take the the sum of the three color channels as its intensity. We look for the texture with the smallest difference. This may seem like a strange method of matching intensity, but in practice it works quite well. The result is that any triangle that is brightly lit with any color light is mapped to the bright textures in the texture palette. Finally, if the color mode is MATCH_ORDER we simple compute the sum of the three color channels for the triangle lighting and index that number into the texture set so that brighter triangles get assigned textures further down the list.

    Once the a triangle is associated with one of the textures in the palette by the above steps we simply bucket the triangle into one of a series of buckets maintained by the renderer. There is one bucked for each palette texture. Once the last RenderTriangle(Tri) call is done every triangle has been assigned a texture by its lighting intensity and all triangles are sorted into the appropriate buckets to be rendered.

    3) The End() call is where all of the the triangles are rendered to the screen. The actual rendering is done using OpenGL, but with state set by the renderer to create the non-photorealistic effect. The process works as follows:

    There are some very important features of this rendering method which need mentioning:

    Performance Features:

    1. All rendering is deferred so state changes are minimal.
    2. The triangles of the model are only passed to OpenGL once to render. When they are rendered, the lighting and texture states are turned off for maximum rendering speed. This can be done because we are only interested in the stencil buffer values for each pixel.
    3.  The only textured primitives rendered by the non-photorealistic renderer are the full screen Quads, which have a very low triangle count. Also there are only two times as many full screen Quads rendered as palette textures. Thus the number of textured primitives rendered is independent of the triangle count of the model. Also, since full screen Quads are very cheap to render, the only real performance hit caused by adding palette textures is to the texture memory which must load all the textures in sequence for every frame.
    Thus we can see that the non-photorealistic renderer uses a very efficient mechanism for drawing triangles to the screen. Some slowdown that we see in comparison with OpenGL comes from the software lighting calculations. This could be replaced in the future with an initial full lighting rendering pass and then frame buffer read back to get the OpenGL lighting for every triangle. At the moment we are not sure that this will really improve performance on all platforms. Experimentation on this is required.

    Esthetics Features:

    1. The renderer always renders the textures on a full screen Quad. Apart from the performance benefits, we also have the benefit that the textures are not warped by OpenGL's perspectively correct texturing. For example if we simply texture mapped the original triangles of the model with a crosshatch pattern then, if a triangle was tiled back away from the camera, we would see the crosshatch lines converging as depth increase (think of railway tracks converging to a point in the distance). This is a common effect we take for granted with texture mapping, which looks correct is we image that the texture is painted on the original triangle. But when we do non-photorealistic rendering we want the crosshatch pattern to always appear flat to the camera, so that depth variation in the original model don't effect how the texture appears. This is what you expect when you see a an artist draw a picture of the model using crosshatching.  This is exactly the effect we get using this method of rendering.

     

    Screen Shots

    We did a large amount of exploration with different texture sets determine the usefulness of the system and to better  understand which texture sets worked well and which did not. Here are some of the results:

    In all cases I used a 3D model of a Tie Fighter to show the non-photorealistic effect.
     
    Style
    OpenGL Rendering
    NPR Rendering
    Crosshatch
    Description: The crosshatch texture palette is made up of 10 textures which start from completely transparent and with each texture gain more and more black crosshatch lines until the entire image is black. This gives good results for light colored background images, like the one shown. The best choice for color matching mode is MATCH_INTENSITY since we want to map the various colors of the lighting to black and white texture images.
    Style
    OpenGL Rendering
    NPR Rendering
    Speckle Trace
    Description: The speckle trace texture palette is made up of 16 textures which start from completely transparent and with each texture gain more and more black speckles until the entire image is black.
    Style
    OpenGL Rendering
    NPR Rendering
    Wood
    Description: The wood texture set contains 30 images of various wood grain textures taken from the internet.
    Style
    OpenGL Rendering
    NPR Rendering
    Rock
    Description: The rock texture set has 17 images of various rock patterns taken from the internet. This is the only set with which I used MATCH_COLORS color matching mode so that triangles lit in red would match reddish rock textures and triangles lit in blue would match blue rock textures.
    Style
    OpenGL Rendering
    NPR Rendering
    Mist
    Description: The mist texture set has 13 images of multicolored mist that vary in color and transparency across the images. 
    Style
    OpenGL Rendering
    NPR Rendering
    Spray Paint
    Description: The spray paint palette has 14 images which start as black spray paint on a transparent background and evolve to include various shades of blue and pink spray paint over top.
    Style
    OpenGL Rendering
    NPR Rendering
    Palette Knife
    Description: This set produces subtle effects that are hard to see without a moving image. Here I used the Palette Knife filter for Adobe PhotoShop applied repeatedly to an image to make   the texture set. I used the MATCH_ORDER matching mode because the images do not vary in intensity. The result is a rendering style that blurs and warps the image for darker triangles and leaves it alone for lighter triangles.
    Style
    OpenGL Rendering
    NPR Rendering
    Offset
    Description: Again this is the result of a PhotoShop filter on a single image. Here I simply offset the image 2 pixels for each image in the texture palette. The result (using MATCH_ORDER color matching) is a style where darker parts of the Tie Fighter are offset more than lighter ones.
    Style
    OpenGL Rendering
    NPR Rendering
    Solerize
    Description: This again results from a PhotoShop filter. Here I applied the solerize operation multiple times to an image, so that darker parts of the Tie Fighter solerize the image more than lighter ones.

    Results & Conclusions

    The first thing that you should notice about the above screen shots is that it is easy to map an arbitrarily complex shading function to the lighting intensity of the tie fighter using this texture palette approach. The solerize filter or spray paint effect were relatively easy to produce in PhotoShop and then import into the system as a texture palette, as compared to the task of reproducing these effects procedurally in code. Thus we can see that many creative rendering styles can be produced easily with this system. The only requirement is that the style be representable in under 30 textures, which is the practical limit to the number of textures supported by the system with reasonable performance.

    The choice of texture set is also very important. It was observed that sets generated in PhotoShop tended to work better (in terms of smoothness of the overall appearance) than those gathered from the web. The reason for this is that texture sets work best if they vary along one parameter and fall evenly and fairly closely together in the parameter dimension. Texture sets that I compiled from the web tended to vary in both all color channels and be unevenly distributed among the color range. Using such a texture set and trying to match color is the same as trying to sample the entire space of RGB color with under 30 samples. With such a low sampling rate you invariable get large jumps between the colors. On the other hand texture sets that varied in one parameter could be matched with MATCH_INTENSITY or MATCH_ORDER modes quite well. This is because in these modes we project the colors of the lighting down to a one dimensional space (overall intensity). It is this space that we need to sample with our textures. Sampling a one dimensional space with under 30 samples is still difficult, but in is a much more manageable task than a 3 dimensional space. The results show much smoother transitions if we do this sampling using evenly spaced and close together samples like the ones we can generate in PhotoShop.

    Future Work

    There are many ways that this could be extended to future work.

    References

    Non-Photorealistic Rendering


    OpenGL emulation code