In this tutorial we will introduce the notion of the Surface
Definition. A surface definition is used to define a material's visual
characteristics, such as its color, its specular properties, its bumpiness and its
texture. Once a surface definition has been created with the Ni_Surface
command it can be linked to one or more objects or instances. In normal practice it is
recommended that a surface definition be assigned to an object definition rather than to
any instances derived from an object definition.
For this example we will create a scene with 25 spheres in it, each
linked to 25 different surface definitions. These are illustrated in the following image:
Each row demonstrates the following different surface
characteristics:
The top row varies the surface opacity from 0.3 on the left to 0.9 on
the right. The surface becomes more opaque as the opacity value nears 1.0.
The second row from the top demonstrates the Blinn specular highlight
model. From left-to-right, the C3 coefficient is varied from 0.0 to 0.4 (it sets the size
of the highlight) and the Index-of-Refraction coefficient is varied from 1 to 60 (it sets
the roughness).
The third row from the top demonstrates the Phong specular highlight
model (which is the default). From left-to-right, the Phong shininess parameter is varied
from 4.0 to 64.0.
The second row from the bottom demonstrates different values assigned
to the ambient, diffuse, and specular mixing coefficients. These coefficients determine
the relative amounts of the ambient, diffuse and specular colors used in the shading
calculations. The left-most sphere only has the ambient color added to it. The next sphere
only has the diffuse shaded component. The third sphere has the diffuse component plus a
specular highlight. The fourth sphere has the ambient component plus the diffuse
component. The last sphere has all three components (ambient, diffuse and specular) added
together using the default mixing values (0.3, 0.4 and 0.7).
The bottom row demonstrates how different colors can be assigned to
the ambient, diffuse and specular shading components. The first sphere uses white for all
3 shading components. The second sphere uses white for the diffuse and specular colors,
and red for the ambient color. The third sphere uses white for the ambient and diffuse
colors, and red for the specular color. The fourth sphere uses blue for the ambient color,
white for the diffuse color and red for the specular color. The last sphere is the same as
sphere # 1 except that all three shading components are yellow instead of white.
To begin this example, let's see how the 25 different surface
definitions are created. We will begin with the bottom row of spheres which demonstrate
how to assign different colors to the ambient, diffuse and specular shading components.
The diffuse color of a surface is specified with the Nt_COLOR
option. By default, this same color is also used for the ambient and specular surface
colors unless the Nt_AMBIENTCOLOR or Nt_SPECULARCOLOR options are explicitly
specified. The following C code shows all of the ways that the ambient, diffuse and
specular colors can be specified. Note that the Nt_METAL parameter of the current
specular highlight model must be greater than 0.0 if the color specified with the Nt_SPECULARCOLOR
option is to be visible (if the default metal parameter of 0.0 is used then the highlight
will take on the color of the light source rather than the explicit highlight color).
{
/* Default colors: white ambient, diffuse and specular colors */
Ni_Surface("surface 1",
Nt_COLOR, Nt_COLORNAME, "white", Nt_CMDSEP,
Nt_CMDEND);
/* White diffuse & specular colors, red ambient color */
Ni_Surface("surface 2",
Nt_COLOR, Nt_COLORNAME, "white", Nt_CMDSEP,
Nt_AMBIENTCOLOR,
Nt_COLOR, Nt_COLORNAME, "red",
Nt_ENABLED, Nt_ON,
Nt_CMDSEP,
Nt_CMDEND);
/* White ambient & diffuse colors, red specular color */
Ni_Surface("surface 3",
Nt_COLOR, Nt_COLORNAME, "white", Nt_CMDSEP,
/* !! NOTE: The "metal" coefficient must be > 0.0 for the */
/* specular color to be seen. */
Nt_SPECULARCOLOR,
Nt_COLOR, Nt_COLORNAME, "red",
Nt_ENABLED, Nt_ON,
Nt_CMDSEP,
Nt_MODEL, Nt_PHONG,
Nt_METAL, (Nd_Float *) &metal_coefficient,
Nt_CMDSEP,
Nt_CMDEND);
/* Blue ambient, white diffuse, and red specular colors */
Ni_Copy(Nt_SURFACE, "surface 3", "surface 4", Nt_CMDEND);
Ni_Surface("surface 4",
Nt_AMBIENTCOLOR,
Nt_COLOR, Nt_COLORNAME, "blue",
Nt_ENABLED, Nt_ON,
Nt_CMDSEP,
Nt_CMDEND);
}
Next, for the second row from the bottom, we will demonstrate how to
assign different values to the ambient, diffuse, and specular mixing coefficients. These
coefficients determine the relative amounts of the final ambient, diffuse and specular
colors used in the shading calculations.
The left-most sphere uses an ambient coefficient of 0.5; the surface
appears to be flat since there is no shading applied to it except for the unidirectional
ambient components.
The second sphere uses a diffuse coefficient of 1.0. There is no
ambient color or specular highlight added to this surface at all. Thus, the surface
appears to look like chalk or the illumination reflected from the moon.
The third sphere has a high diffuse component and a high specular
highlight component added to it.
The fourth sphere is the same as the second sphere except that the
ambient component has been added in (this makes the dark areas appear brighter).
The last sphere illustrates the default ambient, diffuse and
specular shading coefficient values of 0.3, 0.4 and 0.7. In general you will not want to
set these coefficients any higher than these values.
{
/* Shading model coefficients */
Nd_Float sphere_ambient_coeff;
Nd_Float sphere_diffuse_coeff;
Nd_Float sphere_specular_coeff;
/* The second row from the bottom varies the ambient, diffuse */
/* and specular surface coefficients. */
for (surf_num = 6; surf_num <= 10; ++surf_num)
{
sprintf(handle_name, "surface %d", surf_num);
switch (surf_num)
{
case 6: /* All ambient */
sphere_ambient_coeff=0.5;
sphere_diffuse_coeff=0.0;
sphere_specular_coeff=0.0;
break;
case 7: /* All diffuse */
sphere_ambient_coeff=0.0;
sphere_diffuse_coeff=1.0;
sphere_specular_coeff=0.0;
break;
case 8: /* Diffuse + specular highlight */
sphere_ambient_coeff=0.0;
sphere_diffuse_coeff=1.0;
sphere_specular_coeff=0.7;
break;
case 9: /* Ambient + diffuse */
sphere_ambient_coeff=0.3;
sphere_diffuse_coeff=0.5;
sphere_specular_coeff=0.0;
break;
case 10: /* Default mixture of ambient, diffuse & specular */
sphere_ambient_coeff=0.3;
sphere_diffuse_coeff=0.4;
sphere_specular_coeff=0.7;
break;
}
Ni_Surface(handle_name, Nt_SHADINGCOEFFS,
Nt_AMBIENT, (Nd_Float *) &sphere_ambient_coeff,
Nt_DIFFUSE, (Nd_Float *) &sphere_diffuse_coeff,
Nt_SPECULAR, (Nd_Float *) &sphere_specular_coeff,
Nt_CMDSEP, Nt_CMDEND);
}
}
The third row from the top demonstrates the Phong specular highlight
model (which is the default). The highlight model is used by the shading routines to
create the bright specular highlight that appears on curved surfaces. Metals usually have
wide and unfocused highlights whereas shiny plastic has narrow focused highlights.
The following C code varies the Phong highlight size from 4.0 for
the left-most sphere (wide highlight) to 64.0 for the right-most sphere (narrow
highlight). The Nt_SHININESS parameter usually ranges from 4 to 200.
{
/* Specular model coefficients */
Nd_Int phong_shininess;
for (surf_num = 11; surf_num <= 15; ++surf_num)
{
sprintf(handle_name, "surface %d", surf_num);
/* Vary the Phong highlight size from 4 to 64 */
phong_shininess=(surf_num-11)/4.0 * 60 + 4;
Ni_Surface(handle_name, Nt_MODEL, Nt_PHONG,
Nt_SHININESS, (Nd_Int *) &phong_shininess,
Nt_CMDSEP, Nt_CMDEND);
}
}
The second row from the top demonstrates the Blinn specular
highlight model. This is similar to the Phong specular highlight model except that
it produces better results for non-metallic and/or edge-lit surfaces.
The following C code varies the Blinn C3 coefficient from 0.0 to 0.4
(the size of the highlight) and the Index-of-Refraction coefficient from 1 to 60 (the
roughness).
{
/* Specular model coefficients */
Nd_Float c3_coefficient;
Nd_Float metal_coefficient = 1.0;
Nd_Float index_of_refraction;
for (surf_num = 16; surf_num <= 20; ++surf_num)
{
sprintf(handle_name, "surface %d", surf_num);
/* Vary the Blinn IoR parameter from 1 to 60 */
index_of_refraction=(surf_num-15)/5.0 * 60.0;
/* Vary the Blinn C3 coefficient from 0.0 to 0.4 */
/* (this controls the size of the highlight) */
c3_coefficient=(surf_num-16)/4.0 * 0.4;
Ni_Surface(handle_name, Nt_MODEL, Nt_BLINN,
Nt_C3, (Nd_Float *) &c3_coefficient,
Nt_INDEXREFRACT, (Nd_Float *) &index_of_refraction,
Nt_CMDSEP, Nt_CMDEND);
}
}
The top row demonstrates how to change the face opacity (or inversely,
the face transparency) of a surface. Values near 1.0 make the surface more opaque whereas
values near 0.0 make it more transparent. Note that only the face opacity of the surface
is varied in the following C code; the reflected colors opacity, the edge opacity and the
drop-off exponent values are set to static values.
Once a surface definition has been created it can be linked to an
object definition or an instance using the Ni_Surfacelink command;
some geometric primitives also allow surfaces to be assigned to single polygons. When an
instance is about to be rendered a special priority scheme is used to determine which
surface should be used while rendering the instance.
The renderer automatically assigns the "default'' surface
definition to all new objects created. This is overridden if the command is used to
explicitly assign a surface definition.
The following C code shows the syntax to link a surface definition
to an object definition:
Next, lets look at some more runtime rendering options that can be
specified with the command. To set the output image size to 512x512 the Nt_RESOLUTION
option is specified. To remove polygons which are facing away from the camera (and which
cannot be seen since all surfaces are closed), we specify the 'Nt_CULLING,Nt_BACK'
option; this reduces the number of polygons that have to be rendered.
To be a little different from that of the previous tutorial, we will
set the sphere subdivision level globally rather than on a local per-object basis by
specifying the 'Nt_SUBDIVISIONS,Nt_SPHERE' option.
{
/* Image Resolution */
Nd_Int image_res[2];
/* Resolution of the image */
#define IMAGE_RESOLUTION 512
/* Set the resolution of the image */
image_res[Na_X_VALUE] = IMAGE_RESOLUTION;
image_res[Na_Y_VALUE] = IMAGE_RESOLUTION;
Ni_Option(
Nt_CULLING, Nt_BACK, Nt_CMDSEP,
Nt_RESOLUTION, (Nd_Int *) image_res, Nt_CMDSEP,
Nt_SHADING, Nt_SMOOTH, Nt_CMDSEP,
Nt_SUBDIVISIONS, Nt_SPHERE,
Nt_USUBDIV, (Nd_Int *) &sph_u_subdiv,
Nt_VSUBDIV, (Nd_Int *) &sph_v_subdiv,
Nt_CMDSEP,
Nt_CMDEND);
}
The first two tutorials used a point light source which shines from
a particular location in space in all directions. For this example we will use a
directional light source which shines in a particular direction rather than from
a specific point. The following code defines a directional light source which shines in
the direction of (1,1,1) which is a non-normalized vector relative to the origin.
{
/* Direction of the 'directional' line source (non-normalized vector) */
Nd_Vector light1_shinefrom = { 1.0, 1.0, 1.0 };
/* Add one directional light source to the scene */
Ni_Light("light1",
Nt_MODEL, Nt_DIRECTION, Nt_CMDSEP,
Nt_SHINEFROM, light1_shinefrom, Nt_CMDSEP,
Nt_CMDEND);
}
Different background patterns can be added to an image with the Ni_Background
command, such as a constant color, a two color checkerboard, a multi-colored
checker pattern or any 2D bitmap image. For this example we will the 'Nt_PATTERN,Nt_TYPE1'
checkerboard pattern which displays a 7x7 grid of colors (2 blacks, white, red, green,
blue and yellow).
One of the most powerful aspects of NuGraf is it output driver
mechanism. The mechanism allows for 1-bit through 32-bit output to a variety of
different frame buffer and file drivers.
To demonstrate this power the following C code shows how to install
the internal GIF file driver as the current output driver. Since the GIF driver can only
accept 256-color colormapped scanline data, the renderer can be made to automatically
reduce the 16 million color (24-bit) rendered data down to 256 colors (8-bits) by enabling
the Nt_IMAGEQUANTIZE option; this option enables the Wu quantization algorithm
which compresses the 16 million colors down to the best 256 colors which characterize the
image; the final image output to the GIF file will look very close to the original 24-bit
rendered image.
{
/* Select the GIF file output driver. Send image to 'example2.gif' */
/* and quantize the image down from 24-bits to 256 colors. */
Ni_Output_Driver("driver#1",
Nt_FILE, Nt_GIF, Nt_CMDSEP,
Nt_FILENAME, "example2.gif", Nt_CMDSEP,
Nt_OPTIONSTRING, "gif89a=off", Nt_CMDSEP,
Nt_IMAGEQUANTIZE,
Nt_ENABLED, Nt_ON,
/* The number of quantization colors is taken */
/* from the device's colormap length. */
Nt_NUMCOLORS, (Nd_Int *) &dummy_int,
Nt_CMDSEP,
Nt_CMDEND);
}
Lastly, the following C code shows how to retrieve statistics about
the last rendering performed. This is achieved by specifying the Nt_STATISTICS
option to the Ni_Render command. Once the rendering has completed
the renderer will stuff statistics into the buffer passed to the Ni_Render
command. The following C code only prints out a few of the rendering statistics.
{
/* Rendering statistics buffer */
Nd_Float stats_buffer[Nc_NUM_STATISTIC_PARAMETERS];
/* Now render the scene and return rendering */
/* statistics in the 'stats_buffer' array of floats. */
Ni_Render(Nt_STATISTICS, stats_buffer, Nt_CMDEND);
/* Print out some of the rendering statistics */
printf("Total memory used (during rendering only) = %ldkb.\n",
(long) stats_buffer[Na_STATS_MEMORY_USED] / 1024L);
printf("Number of polygons processed (after culling) = %ld.\n",
(long) stats_buffer[Na_STATS_NUM_POLYGONS]);
}