JavaScript EditorFree JavaScript Editor     Ajax Editor 

Main Page
Previous Page
Next Page

18.2. Technical Illustration Example

Pick up just about any instruction manual, technical book, or encyclopedia and you will see a variety of illustrations other than photographs or photorealistic graphics. Technical illustrators have learned various techniques over the years to convey relevant information as simply and as clearly as possible. Details that do not contribute to understanding are omitted, and details that are crucial to understanding are clear and straightforward. This style of illustration differs from mainstream computer graphics for which an enormous amount of detail may be presented in an image to make it look more realistic. The effort to convey information as succinctly as possible in a technical illustration is summed up by a strategy referred to by Edward Tufte (1997) as "the smallest effective difference." In his book, Visual Explanations, Tufte says, "Make all visual distinctions as subtle as possible, but still clear and effective."

Various NPR practitioners are attempting to develop algorithms to create technical illustrations. The goal is to simplify or even automate the task of creating high-quality technical illustrations according to time-honored illustration techniques. Much of our comprehension about an object's shape comes from lighting. Yet the traditional lighting equation is deficient at conveying shape information in areas that are not lit directly, because these areas appear flat. The only lighting in these areas comes from the ambient term, which is constant. Technical illustrations also highlight important edges in black so that they are distinct and clearly convey the shape of the object. If a small ambient term is used in the traditional lighting model, black edge highlights typically are indistinguishable from unlit regions of the object, which are also very near black.

In 1998, Bruce and Amy Gooch, Peter Shirley, and Elaine Cohen surveyed illustrations and came up with a list of common characteristics for color illustrations done with airbrush and pen.

  • Surface boundaries, silhouette edges, and discontinuities in the surface of an object are usually drawn with black curves.

  • A single light source is used, and it produces a white highlight on objects.

  • The light source is usually positioned above the object so that the diffuse reflection term varies from [0,1] across the visible portion of the object.

  • Effects that add complexity (realism) such as shadows, reflections, and multiple light sources are not shown.

  • Matte objects are shaded with intensities far from white and black so as to be clearly distinct from (black) edges and (white) highlights.

  • The warmth or coolness of the color indicates the surface normal (and hence the curvature of the surface).

These characteristics were incorporated into a "low dynamic range artistic tone algorithm" that we now refer to as GOOCH SHADING.

One of the important aspects of Gooch shading is the generation of black curves that represent important edges. There are a number of techniques for rendering such edges. Perhaps the best method is to have them identified during the modeling process by the person designing the model. In this case, the edges can be rendered as antialiased black lines that are drawn on top of the object itself, that is, in a second rendering pass that draws the edges after the objects in the scene have been completely rendered.

If important edges have not been identified during the modeling process, several methods can generate them automatically. Quality of results varies according to the method used and the characteristics of the objects in the scene. Interior boundary or crease edges should also be identified, and these are sometimes critical to comprehension. A technique that identifies boundary or crease edges as well as silhouette edges involves using vertex and fragment shaders to write world-space normals and depth values into the framebuffer. The result is stored as a texture, and a subsequent rendering pass with different vertex and fragment shaders can use an edge detection algorithm on this "image" to detect discontinuities (i.e., edges) in the scene (Jason Mitchell (2002)).

A technique for drawing silhouette edges for simple objects, described by Jeff Lander (2000) in Under the Shade of the Rendering Tree, requires drawing the geometry twice. First, we draw just the front-facing polygons using filled polygons and the depth comparison mode set to GL_LESS. The Gooch shaders are active when we do this. Then, we draw the back-facing polygons as lines with the depth comparison mode set to GL_LEQUAL. This has the effect of drawing lines such that a front-facing polygon shares an edge with a back-facing polygon. We draw these lines in black, using fixed functionality OpenGL with polygon mode set so that back-facing polygons are drawn as lines. The OpenGL calls to do this are shown in Listing 18.3.

Listing 18.3. C code for drawing silhouette edges on simple objects

// Enable culling

// Draw front-facing polygons as filled using the Gooch shader
glPolygonMode(GL_FRONT, GL_FILL);
drawSphere(0.6f, 64);

// Draw back-facing polygons as black lines using standard OpenGL
glPolygonMode(GL_BACK, GL_LINE);
glColor3f(0.0, 0.0, 0.0);
drawSphere(0.6f, 64);

A second aspect of Gooch shading is that specular highlights are computed with the exponential specular term of the Phong lighting model and are rendered in white. Highlights convey information about the curvature of the surface, and choosing white as the highlight color ensures that highlights are distinct from edges (which are black) and from the colors shading the object (which are chosen to be visually distinct from white or black).

A third aspect of the algorithm is that a limited range of luminance values is used to convey information about the curvature of the surfaces that are being rendered. This part of the shading uses the color of the object, which is typically chosen to be an intermediate value that doesn't interfere visually with white or black.

Because the range of luminance values is limited, a warm-to-cool color gradient is also added to convey more information about the object's surface. Artists use "warm" colors (yellow, red, and orange) and "cool" colors (blue, violet, and green) to communicate a sense of depth. Warm colors, which appear to advance, indicate objects that are closer. Cool colors, which appear to recede, indicate objects that are farther away.

The actual shading of the object depends on two factors. The diffuse reflection factor generates luminance values in a limited range to provide one part of the shading. A color ramp that blends between two colors provides the other part of the shading. One of the two colors is chosen to be a cool (recessive) color, such as blue, to indicate surfaces that are angled away from the light source. The other color is chosen to be a warm (advancing) color, such as yellow, to indicate surfaces facing toward the light source. The blue-to-yellow ramp provides an undertone that ensures a cool-to-warm transition regardless of the diffuse object color that is chosen.

The formulas used to compute the colors used for Gooch shading are as follows:

kcool is the color for the areas that are not illuminated by the light source. We compute this value by adding the blue undertone color and the diffuse color of the object, kdiffuse. The value a is a variable that defines how much of the object's diffuse color will be added to the blue undertone color. kwarm is the color for the areas that are fully illuminated by the light source. This value is computed as the sum of the yellow undertone color and the object's diffuse color multiplied by a scale factor,b.

The final color is just a linear blend between the colors kcool and kwarm based on the diffuse reflection term N · L, where N is the normalized surface normal and L is the unit vector in the direction of the light source. Since N · L can vary from [1,1], we add 1 and divide the result by 2 to get a value in the range [0,1]. This value determines the ratio of kcool and kwarm to produce the final color value.

18.2.1. Application Setup

We implement this shading algorithm in two passes (i.e., we draw the geometry twice). We use the Lander technique to render silhouette edges in black. In the first pass, we cull back-facing polygons and render the front-facing polygons with the Gooch shader. In the second pass, we cull all the front-facing polygons and use OpenGL fixed functionality to render the edges of the back-facing polygons in black. We draw the edges with a line width greater than one pixel so that they can be seen outside the object. For this shader to work properly, only vertex positions and normals need to be passed to the vertex shader.

18.2.2. Vertex Shader

The goals of the Gooch vertex shader are to produce a value for the (1 + N · L) / 2 term in the previous equations, and to pass on the reflection vector and the view vector so that the specular reflection can be computed in the fragment shader (see Listing 18.4). Other elements of the shader are identical to shaders discussed earlier.

Listing 18.4. Vertex shader for Gooch matte shading

uniform vec3  LightPosition;  // (0.0, 10.0, 4.0)

varying float NdotL;
varying vec3  ReflectVec;
varying vec3  ViewVec;

void main()
    vec3 ecPos      = vec3(gl_ModelViewMatrix * gl_Vertex);
    vec3 tnorm      = normalize(gl_NormalMatrix * gl_Normal);
    vec3 lightVec   = normalize(LightPosition - ecPos);
    ReflectVec      = normalize(reflect(-lightVec, tnorm));
    ViewVec         = normalize(-ecPos);
    NdotL           = (dot(lightVec, tnorm) + 1.0) * 0.5;
    gl_Position     = ftransform();

18.2.3. Fragment Shader

The fragment shader implements the tone-based shading portion of the Gooch shading algorithm and adds the specular reflection component (see Listing 18.5). The colors and ratios are defined as uniform variables so that they can be easily modified by the application. The reflection and view vectors are normalized in the fragment shader because interpolation may have caused them to have a length other than 1.0. The result of rendering with the Gooch shader and the silhouette edge algorithm described by Lander is shown in Color Plate 28.

Listing 18.5. Fragment shader for Gooch matte shading

uniform vec3  SurfaceColor;  // (0.75, 0.75, 0.75)
uniform vec3  WarmColor;     // (0.6, 0.6, 0.0)
uniform vec3  CoolColor;     // (0.0, 0.0, 0.6)
uniform float DiffuseWarm;   // 0.45
uniform float DiffuseCool;   // 0.45

varying float NdotL;
varying vec3  ReflectVec;
varying vec3  ViewVec;

void main()
    vec3 kcool    = min(CoolColor + DiffuseCool * SurfaceColor, 1.0);
    vec3 kwarm    = min(WarmColor + DiffuseWarm * SurfaceColor, 1.0);
    vec3 kfinal   = mix(kcool, kwarm, NdotL);

    vec3 nreflect = normalize(ReflectVec);
    vec3 nview    = normalize(ViewVec);

    float spec    = max(dot(nreflect, nview), 0.0);
    spec          = pow(spec, 32.0);

    gl_FragColor  = vec4(min(kfinal + spec, 1.0), 1.0);

Previous Page
Next Page

JavaScript EditorAjax Editor     JavaScript Editor