grsh is an extension of a hybrid program combining the shell from assignment 3 and assignment 4. The available Tcl/Tk commands are identical to the ones from assignment 4 except as noted below.
The length_function procedure takes five parameters. They are theta, phi, fx, fy, fz. They represent the locations of a hair strand. The procedure returns the length of the hair strand for the given location of the follicle. theta and phi are the angular position of the follicle on the ellipsoid. (fx, fy, fz) is the position of the follicle relative to the centre of the ellipsoid. You will note that these parameters are redundant. This is to allows the procedure to choose the most convent parameterisation.
The force_function procedure takes eight parameters. They are theta, phi, fx, fy, fz, x, y, z. They represent the location of a hair strand, and where the force field needs to be evaluated at. Parameters theta through fz are as before. (x, y, z) is the point at which the force field should be evaluated. The procedure returns a triple of the force vector for that strand of hair, at the requested point. This allows the force field to vary both by position, and by particular hair strand.
The clipping_function procedure takes five parameters. They are theta, phi, fx, fy, fz. Parameters theta through fz are as before. The function returns a list of clipping planes. These clipping planes are used to cut hair growing at a given location. A clipping plane is represented as { {px py pz} {nx ny nz} }. {px py pz} is one point on the clipping plane, and {nx ny nz} is the normal vector for the plane.
face.gr is a grsh script that manipulates a face with hair. It is run with the command ./grsh face.gr.
By holding button one, the face can be rotated with an arcball device in the centre of the window.
Pressing the “Hair Lighting” button renders the scene with full lighting and shadows on the hair. Pressing the “Ray Trace” button renders the scene with ray tracing for full lighting and shadows on all objects and hair.
Different hairstyles can be picked from the “Style” menu. The “Parameters” menu contain two options. The “Colour” option brings up a dialog box that can be used to adjust the colour of the hair. The “Length” option brings up a dialog box used to adjust the length of the hair.
The “File” menu allows you to save the image, and quit the program.
There are two segments to this project. The first and most important part is the Tcl/Tk shell grsh. This shell is an extension of the shell created in assignment 3 and assignment 4. The second part is the script face.gr. This is a script that allows the user to manipulate a head with hair. Its purpose is to illustrate the abilities of the shell grsh. face.gr should be considered as a special interactive supplement to the main body of work, grsh, which is normally considered as a non-interactive rendering program.
The program base of grsh is a hybrid of the shells from assignments 3 and 4. The Tk routines that were removed from assignment 4 were re-introduced, as well as the OpenGL rendering routines. The data structures were only expanded when creating assignment 4, so they were be left untouched.
The object hierarchy can be rendered in two different ways. It can be rendered with the routine doRender found in gr.c. This will ray-trace the scene, rendering to an OpenGL context, complete with z-buffer information. More about this latter. The second way it can be rendered is with the Gr_renderNode also found in gr.c. This renders in the same way as the hierarchy was rendered in Assignment 3.
Assignment 3’s OpenGL rendering routines could only render spheres. This was enhanced to render instances and polyhedrons. Of special note is that back-face culling was turned off. I found that is was useful to use negative scaling factors on object instances. This reverses the orientation of the polygons in a polyhedron, and culling would erroneously remove them. Since most of the rendering time is spend rendering the hair as opposed to rendering polyhedrons, I felt the simplest solution was to not use back-face culling.
Not all primitives that can be ray-traced can be rendering using OpenGL. I decided to focus my efforts on rendering hair than on allowing more primitives to be rendered in OpenGL. It would be a simple matter to extend the program to render all the primitives.
In assignment 4 rays were traced to accumulate colour data for a pixel. The colour data was written to memory, and eventually this data was written to a file on disk. The ray-tracing process in this project is the same; however the data is instead written to the OpenGL context. The ray-tracing process calculates the intersection point in the WCS, so I use OpenGL’s method of writing points to draw a point at the intersection point with the colour of the calculated colour data. By using the same projection matrix that was used when rendering objects using OpenGL, the pixel appears at the right place in the OpenGL context, and accumulates the correct data in the z-buffer. The routine writePixel in gr.c handles some of this process.
Hair was added to the list primitives. The data for hair is stored in the object hierarchy in exactly the same way other primitives are held. But hair is rendered in an entirely different way. The hair primitive represents a collection of many strands of hair that encompass the surface of an ellipsoid.
The ray-tracing routine ignores data in hair objects and renders nothing. The OpenGL rendering calls Gr_GLPrepareHair. This routine calculates the curved lines for each strand of hair. The segment joints of each strand are projected as the would be by OpenGL by using the gluProject routine. Lighting calculations are performed at each joint. Then each strand is divided up into segments that can later be sorted in depth order. The segments are stored in the hairList array. Each element of this array stores the information to render a segment of hair. This data includes the two endpoints of the hair, the colour at each end point, and the alpha value of the hair. Look at HairSegNode in gr.c to see this data structure. No actual rendering of hair is done yet.
A hair strand is represented by a series of line segments. The shape of the strand is given by a cantilever model (Anjyo, 1992). The bending of the joints of the strand are derived by a physical equation of elastic deformation and a force field (such as gravity).
Physically two-dimensional objects bend according to the equation d²y/dx² = -M/(EI), where M is the bending moment, and (EI) is the flexural rigidity (made up of Young’s modulus E, and the second moment of area I). Even though this equation only applies to small deformations, we use it anyway.
If a hair strand is made up of k segments of equal length d, then the bending moment of the ithjoint, is approximately Mi = -||f||d(k-i+1)²/2, where f is the force being applied to the strand at the joint. So the displacement of the ith node is given by yi = (-½)(Mi/(EI))d².
This approach can be generalised to the three-dimensions by apply this procedure to two directions perpendicular the strand, and taking the components of the force in these directions.
To avoid having strands pass through the head, collision detection is implemented for ellipsoids. By approximating the head by an ellipsoid, we can inexpensively avoid hair passing through the head. Points on the hair strand are derived from the pore to the tip. If a hair segment in the strand passes through the ellipsoid, the end is repositioned appropriately so that the intersection is avoided.
Force fields in addition to gravity are added to the force field. These styling forces approximate the inter-hair forces that a stylist creates to make different hairstyles. They can also be used to model the forces applied by hairpins and hair bands.
Each hair strand has a length, flexural rigidity, pore position and angle. Different regions of the head will have different parameters, and these parameters are jittered to model the non-uniform nature of hair. Cantilever.c contains the code used to create this model of hair strands.
Distributing hair evenly on the surface of a sphere or an ellipsoid is not easy. There are a couple methods of doing this. One method would be to inscribe an icosahedron into the ellipsoid, and then evenly distribute hair on each face of the icosahedron. The way I chose to distribute hair was to loop from the north to south pole of the ellipsoid, and spacing the hair evenly around lines of latitude on the ellipsoid.
A density parameter determines how close together the strands are. Each line of latitude is evenly spaced by angle, and the number of strands on each line of latitude is such that the space between the strands on the lines of latitude is about the same as the space between the lines of latitude. So there are fewer strands on the lines of latitude nearer to the poles than there are strands on the equator.
The hair parameters such as length and force are passed as Tcl procedure names. These procedures take positional parameters and return the required data. For example the length of a hair strand is calculated by calling the Tcl interpreter to evaluate the passed function at the phi and theta angle values of a position on the ellipsoid, and the (x,y,z) point relative to the centre of the ellipsoid. The Tcl command returns what the length should be for a hair strand at that point. Both the angular data and positional data are passed so that the Tcl command can use whatever data is most convenient to perform the calculation.
Once again the ray-tracing routines are employed to calculate the colour of the hair at each end-point of a segment of hair. Since a hair has no normal vector, the normal lighting calculation cannot be used. A modified version of the lighting model is used. The calculation is handled in HairLightModel in gr.c. Essentially the maximum intensity possible is picked from all the planes with a normal perpendicular to the tangent to the hair segment. So the diffuse lighting coefficient is sin θ, where θ is the angle between the vector pointing to the light, and the vector of the line tangent to the hair segment. The specular lighting coefficient is cosn(π - φ - θ), where φ is the angle between the vector pointing to the eye, and the vector of the line tangent to the hair segment. The ambient lighting remains the same as in assignment 4. The details of the lighting calculation can be found in Leblanc, 1991.
Of course if the hair is found to lie in a shadow, then only the ambient light contribution is used.
The hair segments have been calculated and coloured and projected by a previous call to Gr_renderNode. First this data is sorted using qsort, so they are in depth order. The depth buffer is put into read-only mode, and pixel blending is turned on. Because the projection has already been done, an orthographic projection matrix is set up. Finally a call to DrawHairSegs in gr.c renders the hair.
Because the width of a strand of hair is assumed to be less than the width of a pixel, normal line rendering is not acceptable. Instead we use pixel blending. The strands are drawn from back to front, and blended with the previous pixel. The amount of blending is approximately the amount that the hair covers of the pixel. This is proportional to the radius of the hair strand. To simply things, the radius of the hair is not a parameter, instead the user provides the alpha value to use when pixel blending.
The depth buffer is put in read-only mode so that it does not interfere with the blending process, but hair that is occluded by objects is not rendered.
Rendering the hair from the point of view of each light source creates a shadow buffer. The z-buffer from this rendering is stored in an array. In the actual ray-tracing of a scene, these z-buffers are used to calculate shadows form the hair onto objects. During the lighting calculation, the intersection point being considered is projected onto each light source. If the point is deeper than the value in the shadow buffer, then that point is in shadow. If it is above the corresponding value in the depth buffer, then the usual lighting calculation continues to determine if other objects in the scene cast a shadow on the point.
The shadow buffer is created before any other rendering step. It is started by HairMakeShadowBuffers in gr.c. Actually six shadow buffers are created per light source. The light is encased in a cube made up of these six shadow buffers. The code that actually creates the shadow buffers is in shadowbuffer.c. An OpenGL matrix is made to create a view with a 90-degree field of view at the light source, looking down one of the axis of the WCS. The hair is rendered by calling a callback function. The rendered data from the z-buffer is stored in an array. This process is repeated for a view along each axis in each of the 2 direction on the axis. These six arrays are stored in the data structure for the light source.
During ray-tracing, to determine if the shadow buffer indicates that a point is in the shadow, a call is made to inShadow in shadowbuffer.c. The projections matrices were stored when the shadow buffers were generated, so the point in question is transformed with these same matrices using gluProject. If the z value of the transformed point is below the threshold of the shadow buffer, true is returned indicating the point is indeed in the shadow.
The primary purpose of this project is to add rendering of hair to the ray-tracer from assignment 4. As such it is not really designed to be an interactive project. Because of the use of OpenGL in the project, it was easy and worth while to create a interactive Tcl program to demonstrate features of the project.
face.gr is a script that allows you to manipulate a head with hair. The data for the face came from the Fascia project made by Fred Parke, and Andrew Marriott in 1991 at Curtin University of Technology. Fascia is a facial expression program, but I just use the data as something nice to put hair on.
The script is based on the puppet scripted made in assignment 3. Buttons were added to allow different levels of rendering. Rendering a complete image takes several minutes, so for interacting, lighting calculations are ignored, and hair density is reduced. Using these buttons can activate full rendering and ray-tracing.
Different styles of hair can be selected from the menu. These styles were created by manipulating the length and force field routines until I was satisfied with the style. Allowing the user to generate their own force fields would be much to difficult to implement; however, one can write a script to run grsh and generate whatever fields they want. They just can not use face.gr to do this.
The data directory contains a collection of other images created with grsh. In particular Intersect.png, and NoIntersect.png illustrate the effect of collision detection of the hair with the head. Intersect.png was generated with a version of grsh the NO_COLLISION_DETECT #defined. NoIntersect.png is the same image generated with the normal version of grsh. NoPixelBlend.png and PixelBlend.png are also the same image, except that NoPixelBlend.png was generated by a version of grsh with NO_PIXEL_BLEND #defined.
All images have the corresponding script used to generate them in the data directory.