Shading
theory and implementation inside XSI
Orignal author: Amaan Akram
List of translations:
Serbo-Croatian by Jovana Milutinovich
Portuguese by Artur Weber
Translations to other languages by third-parties is actively encouraged
Lesson 1: Vectors
The process of rendering, or generating an image
in a 3D graphics program, involves firing out rays from a 3D camera
and then tracking what the rays hit, one ray at a time. Such rays
are fired atleast once for every pixel that is to be rendered on an
image.
So how are these rays represented internally
in a 3D software? With Vectors. The reason for choosing vectors to
represent such rays is fairly simple. Vectors provide a convenient
way to represent direction and distance in a single form, or symbol.
So, in firing a single ray from a 3D camera in a given direction,
a software can determine if a ray has hit an object, and if it has,
then what is the distance from the camera of the object that
the ray has hit.
Some of you will remember vectors from math
classes from school. Some of you will have happy memories of vectors,
others will have nightmarish, and then there might be some who might
not have had the chance to study them at all in school. So, for the
purpose of bringing everyone at the same level, I am going to cover
some basic vector theory in this lesson.
As stated earlier, Vectors represent two quantities
with a single symbol. The graphical way of a vector is with an arrow.
The tip of the arrow shows the direction, and its length represents
the length of the vector in 3D space. This is represented in the figure
below, with a vector OQ, where O represented the origin in 3D world
space, and Q represents a point that space.

Note that vector OQ is not the same as the vector
QO. The length may be the same, but the direction has been inverted.
Also note that two vectors are equal provided
they have the same length and direction (parallel). It is not necessary
for the two to originate from the same point in 3D space.
For more information on vectors, please visit,
read and understand what's written on the following pages.
http://www.netcomuk.co.uk/~jenolive/vect1.html (Some general
rules)
http://www.netcomuk.co.uk/~jenolive/vect2.html ( Adding vectors )
http://www.netcomuk.co.uk/~jenolive/vect4.html ( Using components to describe vectors)
http://www.netcomuk.co.uk/~jenolive/trig.html (Basic trig for working out components)
http://www.netcomuk.co.uk/~jenolive/vect5.html ( Finding the magnitude or length of a vector)
Once everyone's read the above, please let me
know. We'll then move on to Lesson 2 then. Ofcourse if there is anything
that is not clear about vectors after reading the information available
on the website above, please ask.
Lesson 2: Vectors & their length
The length of a vector is calculated by using
the famous Pythagoras Theorem, which states that the square of the
length of the diagonal of a right angle triangle is the sum of the
square of the triangle's base and it's height. This I think we have
all pretty much studied in school. The only thing that we do different
is that we are looking at vectors in 3-dimensional space. So a coordinate
will have 3 components, x, y and z. In school, I don't think I went
beyond two dimensional spaces.
The above is illustrated in the beautiful diagram below.

OK so we've just looked at finding the length
of a vector, which starts at the origin (0,0,0) and ends at some point.
But what about finding the length of a vector that is not starting
at the origin? Well, that's simple.
Say the vector starts at point A with coordinates
(x1, y1, z1), and ends at point B with coordinates (x2, y2, z2). The
following picture shows how we handle such cases for calculating vector
lengths. (for the purpose of simplicity, I have chosen an example
of a vector confined to the XY plane)

as an exercise, can anyone tell me what calculations
we would have to do if the vector in the above example was NOT confined
to the XY plane, and actually had a Z component as well? Like say,
point B was at (5, 4, 8 ) in 3D space?
You must be wondering why are we going through
vectors. Well, you will find out when we all write our first Phong
shaders!
But without understanding vectors, there's little point in moving
on.
Lesson 3: Vector Dot Product
OK so far we have covered what vectors are,
and how to calculate distances between 2 points in space. Now, we
will look at dot products, and how useful they are for rendering in
computer graphics.
The addition or subtraction of two vectors leads
to another vector. Dot products, on the other hand, lead to just a
number--a number which can be very important to shader writing, when
used in the right way. In fact, this number holds the key to how shading
is done at any given point on the surface of a 3D object. This number
is a scalar quantity, which basically means that it's just a number,
not a vector. A
dot product between two vectors A and B is denoted by:
A.B --- (read as A dot B)
So, how is the dot product calculated? It's
calculated by multiplying two parallel vectors' magnitudes. Recall
that we can calculate the magnitude of a vector A with coordinates
X, Y, Z by:
Code: |
SquareRoot
( X*X + Y*Y + Z*Z)
Also note that the symbol for representing the magnitude of
A is |A| |
So the dot product of two vectors A and B is
given by:
A.B=|A| * |B|
But what happens when the vectors are not parallel?
Well, some of you may recall from college vector classes that a vector
is comprised of two components, a horizontal component, and a vertical
component.
Have a look at the following image

The image shows two vectors, A and B. The vector
B is not parallel to A, but as said earler, every vector has two components.
In this case, B is at an angle denoted by theta (greek symbol) relative
to the vector A (which is parallel to the X axis). So, the component
of B parallel to A is denoted by BCos(theta).
So, as the image says, the dot product of two
vectors A and B becomes:
A.B=|A| * |B| Cos (theta) ----we take the magnitudes
of both vectors here because remember a Dot Product is a scalar quantity
and we were multiplying the magnitudes of both vectors to define the
dot product
Following is another example of a dot product.

To further your understanding of dot products,
have a look at the interactive Java applet at http://www.falstad.com/dotproduct/ . This
is a great example of how dot products work and the applet
is a very intuitive way of furthering your understanding about dot
products.
Lesson 4: Unit vectors & Dot Products
So, here we go.... today we are going to look
at unit vectors and how they affect dot product calculations.
So what is a unit vector? It was stated in the
first lesson that a unit vector is a vector that has a length of
1 unit. So how do we convert any vector into a unit vector? We
take the three components --X, Y, Z -- of the vector and divide each
of them with the length of the vector. Have a look at the example
below
Vector A = ( X, Y, Z )
Length L = SquareRoot ( X*X + Y*Y + Z*Z)
So, Unit Vector A = ( X/L, Y/L, Z/L )
This method of converting any vector to a unit
vector is called Normalization
This unit vector has a very significant impact
on dot product calculations. Recall from the previous lesson that
a dot product can be calculated from:
A.B=|A| * |B| Cos (theta) -- where theta is
the angle between the 2 vectors A and B
But what happens when vectors A and B in the
above equation are unit vectors (of length 1)? We know that |A| and
|B | are both equal to 1. The dot product formula can be rewritten
then as:
A.B=1 * 1 Cos (theta)
simplified : A.B = Cos (theta)
This is a very important result. What it tells us is that the dot
product of two unit vectors is simply the cosine of the angle between
them. This is the key to unlocking shading calculations in rendering!
Now let's go and see how to get the value of the Cosine between two
unit vectors A and B. We can now very easily calculate the angle between
two vectors simply by calculating the dot product. Now lets reverse
the above equation.
Cos (theta) = A.B
but what is A.B? to calculate Cos (theta), we
need another way of expressing vectors A and B. This is where the
another method of calculating dot products comes in. Have a look at
the following.
Vector A = (X1, Y1, Z1)
Vector B = (X2, Y2, Z2)
A.B = (X1*X2) + (Y1*Y2) + (Z1*Z2) (this
is just a straight multiplication of each of the components of the
two vectors, and adding the result of each multiplication.
So, now we can easily use the above equation
to calculate the angle between two vectors.
Code: |
Vector A = (X1, Y1, Z1)
Vector B = (X2, Y2, Z2)
Given that vectors A and B have been Normalized:
Cos (theta) = (X1*X2) + (Y1*Y2) + (Z1*Z2)
|
When two unit vectors are parallel, A.B
and cos(theta) will both be equal to 1. (since Cosine of 0 degrees
is 1 --see graph of cosine below)
When two unit vectors are perpendicular A.B and cos(theta) will both be equal to 0. (since Cosine of 90 degrees
is 0 --see graph of cosine below)
Following is a graphical representation of how
the value of A . B (dot product) changes when both A and B are unit
vectors.
Since A.B = Cos (theta), the variation in the
value of A . B as the angle between them increases from 0 degrees
onwards is simply the cosine curve
The Y-axis represents Cos (theta), which is
the same as A . B -- the dot product. And the X-axis represents the
variation in the angle between the two vectors A and B

this is a very good way of representing how
the dot product varies between two unit vectors. This graph also shows
that the rate of change of the dot product with respect to the angle
between the vectors is a non-linear change
Once again, please visit http://www.falstad.com/dotproduct/ again and
play with the Java applet. see how the dot product values change as
u play with the magnitude of each of the vectors and the angles between
them
OK, I think in the next lesson we will look
at 3D graphics, and math (well, OK a bit of math)
Lesson 5: Vectors inside your 3D application, and the Lambert
Shading model
OK so now that we all know what dot products
are, we can start looking at 3D applications and how they use vectors
to shade a polygon.
Each object in a 3D application consists of
polygons. Every polygon has a vector that is perpendicular to the
plane that contains the polygon. What is a plane? It's just 2D space.
One example is the XY plane in your 3D application. Similarly, the
XZ plane. Similarly, an ideal polygon is always confined to its own
plane.
So, every polygon has a vector stored in it
that is perpendicular to the plane of the polygon. This vector is
called a Surface Normal, or for simplicity, just a Normal. Have a
look at the following image

(This Surface Normal should not be confused
with the process of Normalization. The two are completely different
things)
This Normal is a critical element in calculating
the shading on that polygon. The shading on this polygon will be the
brightest when its normal is pointing at a light source. As the polygon
turns away from the light source, the normal of the polygon will start
pointing away from the light, and the shading on the polygon will
decline in brightness. This is what shading is all about in a nutshell. Have a look at the following image.

Say we have a polygon to render. In particular,
we have to render a pixel P on that polygon. We know that that pixel
on the polygon will have a Normal, say N. The next thing we need is
a light in the scene to be able to shade the pixel. So, now assume
that there's a light in the scene as well, and there is a vector L
that extends from the pixel P out to the light source. And finally,
let's say the angle between N and L is theta. (see figure below)

(note the direction of the vectors L and N.
They are both point outwards from the pixel P. If the direction of
L was reversed, our calcuations would be wrong as the new angle theta
would become 180-theta. Check school vector books for more info. For
now just keep in mind that angle calculations are done at the tail
ends of vectors L and N)
Since we are only interested in finding out
the angle between N and L, we can use the dot product formula to calculate
the angle. Recall that the dot product is given by
L . N = |L| * |N| * Cos (theta)
Since the dot product (and the angle theta)
in the above formula is dependant on the magnitudes of the vectors
L and N, we need to remove |L| and |N| from the above equation. The
way to do that is to normalize L and N so that |L| and |N| both become
1.
So assuming we have normalized N and L, the
normalized vectors are N' and L', respectively. Their components are:
L' = (L' x, L' y, L' z)
N' = (N' x, N' y, N' z)
So the shading at pixel P is defined by:
Cos (theta) = L' . N' -- where L' . N'
= (L' x * N' x) + (L' y * N' y) + (L' z * N' z) -- this equation is
the Lambertian Shading Model
The above equation is evaluated for every pixel
that is on the surface of a polygon to be rendered. As the angle between
L' and N' increases from zero to 90 degrees, the shading falls from
1 to 0 (same as in a cosine curve).
Lesson 6: More on vectors and other shader variables
In the last lesson we learned how the Lambert
shading model gives us diffuse shading. This shading model is at the
heart of many of todays shading algorithms, simply because it provides
a fast and easy way to approximate surface diffuse shading.
In this lesson, we will take a look at some
other variables and surface normals before we move onto some practical
applications.
Earlier I mentioned that to render each pixel,
the renderer fires atleast one ray (the camera vector) from the scene's
camera. If that ray doesn't hit anything, then no shading calculations
are done. However, if the ray does hit a polygon then the relevant
shader assigned to that polygon is looked up and shading calculations
are done for that point where the camera vector has intersected with
the surface of the polygon. This point is often referred to as the Intersection Point. For each pixel that is to be rendered on
a polygon, there is always an intersection point. On a very basic
level, the data contained within the Intersection Point data structure
is simply the X,Y, & Z coordinates of that point in 3D space.
So the camera vector V for rendering the pixel with an intersection
point P is VP.
Two types of Normals:
It is important to mention that there are two
kinds of normals on a polygon. One is the Geometry Normal, and the
other is the Interpolated Normal. The following figures show the difference
between the two normals.
GEOMETRY NORMALS

A Geometry Normal is always perpendicular to
the plane defined by each polygon. This normal is dotted with the
light vector of a light in a scene to produce shading for the entire
polygon. This shading calculation is done once, and the color is applied
to the entire surface of the polygon. This kind of shading method,
with the Geometry Normal, produces what is known is Flat Shading.
A Flat Shaded object looks like:

So the single geometry normal's direction defines
the color (shading) of each polygon.
INTERPOLATED NORMALS
Interpolated Normals are spread out evenly across
the polygons of an object. However, their directions are smoothed
out over the surface of an object to make the object appear smoother
in shading calculations. Have a look at the following figure

The same object that was used to generate the
flat shaded version using Geometry Normals is used here again, but
the normals on the surface of the object have been smoothed out. In
the lower section of the above figure, you can see how the normals
are fanned out over the surface. This approximation is calculated
using a Normal Interpolation algorithm for Phong shading that we will
not get into during this session, as it is beyond the scope of what
we are trying to cover here. But it is important to know that when
an object is 'smoothed', the internal shading calculations switch
from using Geometry Normals to Interpolated Normals.
So, at each intersection point at a pixel that
is to be shaded, the Interpolated Normal is looked up and is dotted
with a light vector to calculated diffuse shading.
On a side (but interesting) note, bump mapping
works with interpolated normals too. All it does is that the Interpolated
normals are further perturbed according to the gray-scale intensity
of a pixel from a bump map image. Then these perturbed normals are
dotted with the camera vector to calculate diffuse shading..
Lesson 7: A video to watch - practical implemention via low-level shading nodes in Mental Ray
with XSI
The file is compressed with the WinRAR v3.51.
If you are unfortunate enough to not have winrar, then you can download
it umm... for free from http://www.rarlabs.com/download.htm . It's
a small download, only 990kb. If you already have WinRar installed
and it says that the Rar file on the website is corrput,.then you
need to upgrade to the latest version of winrar.
The AVI for this lesson can be downloaded from:
http://cgexpanse.com/phpBB2/sessions/shaderWriting/Lesson7.rar
You will also require the (free) Techsmith codec
to view avi. This is a small file -- just 170kb. It can be downloaded
from:
http://www.techsmith.com/download/tsccdefault.asp
That's all that is required to view this lesson.
I apologize for the poor quality of the audio. I kept the quality
down to keep the filesize down...
Lesson 8: Surface Illumination
In this lesson, we are just going to quickly
look at how the overall illumination on any polygon or surface is
calculated. Let's start out by breaking down the different elements
that contribute to the illumination on a surface.
1) Ambience
2) Diffuse
3) Specular
4) Reflection
Given the above elements, we can now write down
the shading on any polygon as an equation:
Shading = Ambience + Diffuse + Specular +
Reflection
note: for all dot product calculations in the
above equation, clamp dot product values to above or equal to 0. Dot
products can be negative when the angle between two vectors goes higher
than 90 and less than 270 degrees Negative values of dot products
make no sense for calculating illumination. If they are not clamped
to be above 0, the final shading will look umm..strange.
Ambience is simply a user-specified
color value. This can be implemented in XSI's render tree via a Color
Share node, which can be plugged into the final Material node of an
object.
Diffuse shading is simply added
to the Ambience shading via a Color Math Basic node, using an Additive
operator. Specular and Reflection shading is added in a similar manner.
Since Ambience is very easy to implement, and
we have already implemented Diffuse shading, let's now move on to
Specular shading. However, before we do that, I'd like to point out
that my diffuse calculations are simplified on purpose. I did not
look up the color, intensity and other properties of the light. In
a more realistic scenario, all such properties may need to be looked
up and then multiplied with the light vector.
Specular
shading
There is no such thing in real life as
specular shading. It is just something that has been invented by computer
graphics scientists to help approximate reflection of a light source.
All it does is that it creates an illusion of a reflecton lightsource
on a surface.
So how do we calculate specular reflection?
Well, there are two methods out their. One is to use a reflection
vector, and the other is to use a what is called a Half Vector. Both
methods are outlined in the figures below.

Figure A represents a situation with a light
vector, a camera vector, and a surface normal. I have drawn the reflection
vector, which is just a reflection of the light vector about the surface
normal.
Figure B shows how I calculate the reflection
vector R (there are other ways to do this calculation--I am presenting
my own method below. Others may use another method, but the result
is the same). I write down the point H, which lies half way between
R and L, as an equation involving R and L. I used a method from trignometry
that we have all used in school. To calculate the mid point of two
points, we just add each component and divide each component by two.
The forumla, expressed in terms of R and L is
H= ((Rx+Lx)/2) + ((Ry+Ly)/2) + ((Rz+Lz)/2) --
equation 1 (trignometric form)
This can also be written in vector form
H=(R+L) / 2 -- equation 1.1
As the R is a reflection of L about N, the midpoint
H also lies on the normal vector. This comes in very handy to calculate
H in another way. Whenever we do a dot product between two normalized
vectors, the result is the length of the projection of one
vector onto another. In this case, when we do N . L, we find out the
length of the projection of L on N. This is very easy to visualize
in another way. When L and N are parallel, the angle between them
is 0, and the dot product of the two is 1. That means that the length
of the projection of L on N is 1 times N (1*N). If the angle between
the two is 45 degrees, the dot product gives us 0.707 as the result.
This means that the length of the projection of L on N is 0.707 times
N (0.707*N). If the angle was 90 degrees, the dot product would be
0 and the length of the projection of L on N would be 0 times N (0*N)--hence
zero.
So point H can simply be written as
H=N * (N . L) -- equation 1.2
When we equate the two equations 1.1 and 1.2,
we get:
R = 2N * ( N . L) - L
The specular component then is simply the dot
product between R and the camera vector C. When the two line up, the
camera sees the light's reflection perfectly. As L or C move, the
specular strength changes accordingly. The final forumla for specular
reflection via this method is:
C . (2N * ( N . L) - L)
Now let's take a look at the half vector
method.
This is Jim Blinn's approximation of a specular
reflection. The reason this method exists, and is more commonly used,
is because it involves fewer computations that the reflection vector
method for calculating.
The image below shows two different positions
for the light vector L, but with the camera vector and the surface
normal unchanged.

In Figure A, the light is positioned in such
a way that R lines up perfectly with C. If we calculate the half vector
H between L and C, we will find out that H lines up perfectly with
the normal N.
In Figure B, I have moved L a bit up, and as
a result R has moved away from C, and H has moved away from N. So,
given this behaviour of H with N, and its relation with R, we can
say that specular reflection can be calculated by just comparing the
angle between H and N by using a dot product calculation.
That's it! We now know how to computer specular
reflection. The last remaining topic in this discussion
is that of just reflection. It's actually quite simple to calculate
reflections. We have to calculate the camera vector reflected about
the surface normal. This vector is our Reflection Vector of the camera, which we will call RC. (the Reflection Vector in the
specular calculation section is the reflection vector of the light,
not the camera). What we do with RC to calculate reflection is to
evaluate the full shading equation by temporarily assuming that our
camera vector is the same as RC. Hence, we substitute any references
to the camera vector with RC in the following equation
Reflection = Ambience + Diffuse + Specular
+ Reflection Hence,
the full equation for calculating shading isShading
=Ambience + Diffuse + Specular+ (Ambience
+ Diffuse + Specular + Reflection)
The bit in green is our reflection component
Normally, raytracers have a user-defined
limit for how many times reflection is to be evaluated. In Mental
Ray in XSI, this option is available under the Optimization tab of
Render Options.
This pretty much wraps up this tutorial.
Many thanks for your interest. If you have any questions, please do
not hesitate to contact me via my website at www.warpedspace.org To see a working example of how to implement
a phong shader (without reflection), please download the following
XSI 4.2 scene
http://www.cgexpanse.com/phpBB2/download.php?id=4509
The shader lets you control the following elementsColor
Diffuse (This diffuse acts like it does in Max or Lightwave, NOT like
in XSI)
Diffuse Spread (similar to Diffuse Sharpness in Lightwave)
Primary Specular Color
Primary Specular Intensity
Secondary Specular Color
Secondary Specular IntensityThe
controls in the flow look like: The
two speculars were just an experiment to get metallic shading into
the render tree. Below is a sample image rendered with the 2 speculars
To
see the full render tree, please see:
http://www.warpedspace.org/wips/My_Phong_Shader/render_tree.jpg (warning!: 2500 pixels wide!) I'd
like to urge everyone to play around with the render-tree and low-level
XSI nodes. This is a very artist-friendly way of experimenting low-level
shading without stepping into the complex world of programming. As
an example of what can be achieved with just playing around, have
a look at my 'hack-SSS' shader below. It can give visually interesting
results that may be useful, and it does it faster than the
simplest of phong shaders :)

Click for a larger version -- model courtesy of Mario
Ucci
*ACKNOWLEDGEMENT
This
tutorial would have been impossible without the help of my friend Andy Nicholas. His lessons in shading theory and
how to implement it inside xsi's render tree got me started on this
path--for which I am grateful. Please visit his website -- www.andynicholas.com