Will Cavanagh
TECH319

"All That Glitters Is Not Gold"
by Will Cavanagh
Inspiration:
Final Image:
Texturing:

Dirt Painting Progress:
Challenges:

Water Refraction and Caustics:
Water Brightness:
Water AO:

RIB Wrangling
by Will Cavanagh
Final Image:
Modelling Progress:
Reference:
Goal: The goal of this project was to model, light, texture and render a scene entirely by coding RIB files by hand.
Challenges:
Flipped Normals:

Spout:

When modeling the spout, I initially tried to use Paraboloids to create the shape. To get the angled spout at the end, I used a boolean difference operation. This is accomplished by creating a primitive solid from the geometry, and a second primitive solid for the geometry to be subtracted, as seen below.


Using a difference operation to create the spout.

Here's what that looks like in the RIB file:
SolidBegin "difference"
SolidBegin "primitive"
    ### CODE FOR GENERATING SPOUT GOES HERE ###
SolidEnd
SolidBegin "primitive"
    ### THIS IS THE GEOMETRY TO SUBTRACT FROM THE SPOUT:
    TransformBegin
        Translate 0 -0.1 1.25
        Rotate -35 1 0 0
        Disk 2 1 360
        Cylinder 1 0 2 360
        Disk 0 1 360
    TransformEnd
SolidEnd
SolidEnd

While this does produce a workable model of the spout, it didn't produce the smooth curves I wanted. To try and get a better model, I decided to try using curves.

I started by making a simple curve with varying width to attempt to approximate the shape of the spout.
AttributeBegin
Translate 0 .8 .3
ShadingInterpolation "smooth"
Attribute "dice" "int roundcurve" [1]
ReadArchive "metal_material.rib"
Basis "b-spline" 1 "b-spline" 1
Curves "cubic" [10] "nonperiodic" 
    "P" [-9 -.8 0   -7 -1.1 0   -5 -.8 0   -4.8 -.75 0   -4 -.85 0   -3.5 -1 0
    	-2.2 -1.5 0    -1 -1.5 0   0 -1 0   1 1 0]
    "width" [1.9  1.7 1.75  2.0 2.3   2.7     3   3.5]
AttributeEnd    

Unfortunately, curves in Renderman are actually 2d ribbons, not tubes (as I need them to render.) Luckily, there is an Attribute used for hair that makes the curves render as semi-circular.

This gives the curve a 3 dimentional appearance, but still doesn't give the extruded tube I wanted.



To solve this problem, I duplicated my tube and used the ReverseOrientation keyword to flip the normals and create the backside of the tubes. I then repeated this process with a second copy of the same curve with a smaller radius. This created the interior of the tube.

I used the same boolean process as above to angle the tip of the spout.
SolidBegin "difference"
    # Spout Primitive
    SolidBegin "primitive"
        
            #Interior
            ReadArchive "spout_curves_small.rib"
            ReverseOrientation
            ReadArchive "spout_curves_small.rib"
            
            #Exterior
            ReadArchive "spout_curves.rib"
            ReverseOrientation
            ReadArchive "spout_curves.rib"
            
            #End Cap
            TransformBegin
                Translate -7 -.35 .2
                Rotate -90 0 1 0
                Disk 0 .95 360
            TransformEnd
            
    SolidEnd
    # Subtraction Primitive
    SolidBegin "primitive"
        TransformBegin
            Rotate -90 0 1 0
            Translate 0 -0.1 6.5                
            Rotate -15 1 0 0
            Scale 2 2 2
            Surface ""
                Opacity 0 0 0
            Disk 2 1 360
            Cylinder 1 0 2 360
            Disk 0 1 360
        TransformEnd
    SolidEnd
SolidEnd
And finished by adding a torus for the lip.
Closing Thoughts:

This project has forced me to become much more comfortable with thinking in coordinate systems and understanding transformations. I found that as I worked I got much faster and better at picturing what effect a particular transformation would have on the scene. Additionally, I gained a great deal of appreciation for the simplicity and layers of abstraction that the GUIs on modern modelling programs provide. In being forced to generate a scene entirely from code, I got good at thinking creatively about how to build an object from primitives.

Efficiency in operations became much more important working this way, and in solving repetetive problems, I often found myself thinking about how to simplify a problem or find a more elegant solution.

ST Coloration
by Will Cavanagh
Final Image:
Animatable Shader:
Goal:

The goal of this project was to generate materials using RSL that calculate surface color based on user inputs and global variables.
Process:

To generate the first 3 shaders, I looked at the reference image, and guessed what mathematical function the image appeared based on.

For the leftmost shader, I used a sin function, as seen here:
if(sin(sFreq*s) <= t)
    surfcolor = patcolor;

For the second shader, I used an exponential function, see below:
if((1-t) >= s*s)
    surfcolor *= patcolor;
For the third shader, I based my function on the formula for a circle. I needed to adjust the coordinates so that the circle was centered, so to do this I subtracted 0.5 from both coordinates. This meant that instead of going from 0 to 1, they would go from -0.5 to 0.5. Now that my coordinate system was centered on the polygon, I could use the circle formula (x2 + y2 = r2) as a basis for my equation. In my case, I wanted to shade the inside of my circle, so I would use my newS and newT variables as follows:
float newS = (s - 0.5);
float newT = (t - 0.5);
  
if((pow(newS, 2) + pow(newT, 2)) < pow(rad, 2))
    surfcolor = patcolor;
Extras:

As I was designing my circle shader, I accidentally created an equilateral rhombus by taking the absolute value of newS and newT instead of squaring them. I realized that by raising the absolute values of newS and newT to the 2nd power, I would get a circle, and that by animating between 1.0 and 2.0 I could animate between a circle and a square rhombus. I now set about finding a way to rotate the square around the (new) origin so that it would appear to be a regular square.
To accomplish this, I used the formula for rotating a point around the origin, found here. It is:

In the case of my shader, that looks like this:
float si = sin(rotation);
float co = cos(rotation);
  
float sFixed = (s - 0.5);
float tFixed = (t - 0.5);
  
float snew = sFixed * co - tFixed * si;
float tnew = sFixed * si + tFixed * co;

Now all that was left, was to use my discovery from earlier, with the appropriate varibales plugged in.
if((pow(abs(snew), power) + pow(abs(tnew), power)) < pow(rad, power))
    surfcolor = patcolor;
Hypershade Comparison Networks:

Diagonal Shader:
This is the Hypershade network for the shader seen on the left:
Below, you can see the RSL code that generates the same shader:
surface
diagonal(    float     Kfb = 1 /* [group diagonal 0 1 Label "Intensity"] */;
        float shift = 1.0 /* [group diagonal 0 2 Label "Shift Edge"] */;
        float blur = 0.02 /* [group diagonal 0 .1 Label "Antialias Edge"] */;
        
        
        float sflip = 0; /* [group diagonal 0 or 1 Label "Flip S Coordinate"] */
        float tflip = 0; /* [group diagonal 0 or 1 Label "Flip T Coordinate"] */
        
        color    patcolor = color (1,1,0);    /* [group foreground Label "Color"] */
        string  pat_texname = "";            /* [group foreground Label "Texture"] */
        float    patTransS = 0.0;            /* [group foreground -1 1 Label "Translate S"] */
        float    patTransT = 0.0;            /* [group foreground -1 1 Label "Translate T"] */
        float    patScale = 1.0;                /* [group foreground 0 2 Label "Scale"] */
        
        color    bakcolor = color (.6,.3,1); /* [group background Label "Color"] */
        string  bak_texname = "";            /* [group background Label "Texture"] */
        float    bakTransS = 0.0;            /* [group background -1 1 Label "Translate S"] */
        float    bakTransT = 0.0;            /* [group background -1 1 Label "Translate T"] */
        float    bakScale = 1.0;                /* [group background 0 2 Label "Scale"] */
      )
{
  
color surfcolor;
float tt = (sflip == 1) ? (1-s) : s;
float ss = (tflip == 1) ? (1-t) : t;
  
color patcolor_copy = patcolor;
color bakcolor_copy = bakcolor;
  
float sTex1 = (ss + patTransS) * (1 / patScale);
float tTex1 = (tt + patTransT) * (1 / patScale);
  
float sTex2 = (ss + bakTransS) * (1 / bakScale);
float tTex2 = (tt + bakTransT) * (1 / bakScale);
  
if(pat_texname != "")
    patcolor_copy = texture(pat_texname, sTex1, tTex1);
if(bak_texname != "")
    bakcolor_copy = texture(bak_texname, sTex2, tTex2);
  
float blend = smoothstep(shift - blur, shift + blur, ss+tt);
surfcolor = mix(patcolor_copy, bakcolor_copy, blend);
    
Oi = Os;
Ci = Oi * Cs * surfcolor * Kfb;
}

And here is the UI:
Circle Shader:
This is the Hypershade network for the shader seen on the left:
Below, you can see the code for a shader that produces the same texture and also includes additional functionality.
float rotateS(float rad;)
{
    float si = sin(rad);
    float co = cos(rad);
    
    float sFixed = (s - 0.5);
    float tFixed = (t - 0.5);
    
    return (sFixed * co - tFixed * si);
}
  
float rotateT(float rad;)
{
    float si = sin(rad);
    float co = cos(rad);
    
    float sFixed = (s - 0.5);
    float tFixed = (t - 0.5);
    
    return (sFixed * si + tFixed * co);
}
  
  
  
/* Shader description goes here */
surface
squirgle(    float Kfb = 1; /* [group squirgle 0 1 Label "Intensity"] */
            float blur = 0.02; /* [group squirgle 0 .1 Label "Antialias Edge"] */
            float rad = .5; /* [group squirgle 0 1 Label "Radius"] */
            float power = 2; /* [group squirgle 1 2 Label "Power"] */
            float rotDeg = 0.0; /* [group squirgle 0 1 Label "Rotation"] */
            float sflip = 0; /* [group squirgle 0 or 1 Label "Flip S Coordinate"] */
            float tflip = 0; /* [group squirgle 0 or 1 Label "Flip T Coordinate"] */
                            
            color patcolor = color (1,1,0);/* [group foreground Label "Color"] */
            string  pat_texname = "";/* [group foreground Label "Texture"] */
            float rotDegT1 = 0.0; /* [group foreground 0 1 Label "Rotation"] */
            float patTransS = 0.0;/* [group foreground -1 1 Label "Translate S"] */
            float patTransT = 0.0;/* [group foreground -1 1 Label "Translate T"] */
            float patScale = 1.0;/* [group foreground 0 2 Label "Scale"] */
                            
            color bakcolor = color (.6,.3,1); /* [group background Label "Color"] */
            string  bak_texname = "";/* [group background Label "Texture"] */
            float rotDegT2 = 0.0; /* [group background 0 1 Label "Rotation"] */
            float bakTransS = 0.0;/* [group background -1 1 Label "Translate S"] */
            float bakTransT = 0.0;/* [group background -1 1 Label "Translate T"] */
            float bakScale = 1.0;/* [group background 0 2 Label "Scale"] */
        )
{
  
float rotationShape = radians(rotDeg-45);
float rotationImg1 = radians(rotDegT1);
float rotationImg2 = radians(rotDegT2);
  
float shapeS = rotateS(rotationShape);
float shapeT = rotateT(rotationShape);
  
float sTex1 = (rotateS(rotationImg1) + patTransS + (patScale / 2)) * (1 / patScale);
float tTex1 = (rotateT(rotationImg1) + patTransT + (patScale / 2)) * (1 / patScale);
  
float sTex2 = (rotateS(rotationImg2) + bakTransS + (bakScale / 2)) * (1 / bakScale);
float tTex2 = (rotateT(rotationImg2) + bakTransT + (bakScale / 2)) * (1 / bakScale);
  
color patcolor_copy = patcolor;
color bakcolor_copy = bakcolor;
  
  
if(pat_texname != "")
    patcolor_copy = texture(pat_texname, sTex1, tTex1);
if(bak_texname != "")
    bakcolor_copy = texture(bak_texname, sTex2, tTex2);
  
  
color surfcolor = bakcolor_copy;
  
//(pow(snew, power) + pow(tnew, power)) < pow(rad, power)
  
float val = pow(rad, power);
float thresh = (pow(abs(shapeS), power) + pow(abs(shapeT), power));
float blend = smoothstep(thresh - blur, thresh + blur, val);
surfcolor = mix(bakcolor_copy, patcolor_copy, blend);
  
/* STEP 1 - set the apparent surface opacity */
Oi = Os;
  
/* STEP 2 - calculate the apparent surface color */
Ci = Oi * Cs * surfcolor * Kfb;
}

And here is the UI:
Closing Thoughts:

This project got me very excited about the possibilities of custom shaders, and I can't wait to go further -- I feel like we've hardly skimmed the surface of what's possible.

Thin Film Interference
by Harsh Agrawal and Will Cavanagh
Concept:

Thin film interference is the physical phenomenon that produces colorful patterns on the surface of thin films, for example on an oil slick or soap bubble.

For this project, we set out to write a shader that generated a thin film interference pattern in as physically accurate a way as possible.
Reference:

Renders:

Interfaces:
Background:

Behavior of light

There have been many theories over time to understand the mystical nature of light. Light is a collection of small packets of photons that show electromagnetic wave properties. This means that unlike mechanical waves (for instance sound) light can propogate through a vacuum without losing any energy.

Until the late 17th century, light propogation was considered to be linear. Christiaan Huygens first proposed the wave nature of light propogation, and this understanding was further divided into longitudinal and transverse wave theory. Light transmits energy in quantum photons, which means that they posess momentum. In order to better understand this, it is worth reading up on Plank's Quantum Theory.


fig. 1 Properties of light
Properties of Light

Different properties of light can be understood through two light propogation phenomena -- Linear and Wave. Reflection and refraction can be easily understood through the linear nature of light, while diffraction, interference and polarization can be understood through its wave properties. By the way, waves aren't even real -- waves are just a disturbance in medium, the particles in the medium move to and fro but never change their mean position (fig. 1).

Light Sources
  • Monochromatic
  • Polychromatic
  • Coherent
    • Single: Same phase
    • Double: Constant phase difference
Wave Fronts

fig. 2 Wave superposition
Thin Film Interference

Thin film interference occurs when light reflects from and refracts through a very thin layer of transparent or translucent material which has a diferent refractive index than the surrounding media. The interference is caused due to multiple bounces of light within the thin film media. These refracted light rays interfere with the first reflected light. Thus based on the wavelengths of interfering light we see a colorful pattern on the surface of the material. This is the color pattern we are trying to reproduce.

The interference pattern is a result of phase shift, due to Optical Path Difference, which is calculated as follows:
Optical Path Difference = (AB + BC) - AD

In the case that the film is denser than the surrounding media (n1 > n0) R0 will have a phase shift of π.

This causes light and dark intensity bands, as seen in Young's Double Slit Experiment. In the case of thin film interference, the interference will be different at different wavelengths of the light. If the incident light is monochrome we will only see dark and light bands of that particular color and not a colorful pattern.

fig. 3 Thin Film Interference
Equations First it is necessary to calculate the ratio of IOR across interfaces. We do that with this simple function:
void calculateRelativeIOR(
                float n0; float n1; float n2; output varying float eta0;
                output varying float eta1; output varying float delta
                         )
{
    eta0 = n0/n1;
    eta1 = n1/n2;
  
    float delta10 = (n1 > n0) ? PI : 0;
    float delta12 = (n2 > n1) ? PI : 0;
    delta = delta10 + delta12;
}

The reflectance coefficients for s and p polarization is calculated using Fresnel equations. Schlick's approximation is widely used in CG to calculating fresnel, by ignoring polarization, which is not physically accurate. For this reason, we chose to implement our own Fresnel function, rather than using RSL's built in Fresnel function.














void calculatePolarization(
                            vector incLightVec; vector Nn; float n0; float n1; float n2;
                            output varying float costheta1; output varying float alpha_s;
                            output varying float alpha_p; output varying float beta_s;
                            output varying float beta_p; output varying float lightRatio;
                            output varying float frontside
)
{
    /*  Calculate Theta0   */
    vector Ll = normalize(incLightVec);                                 // normalized light vector
    float costheta0 = Ll.Nn;                                         // calculate cos of angle of incidence
    frontside = (costheta0 > 0) ? 1 : 0;
    /*  End Calculate Theta0   */
        
    /*  Calculate Theta1   */
    float sintheta1 = pow(n0 / n1, 2) * (1 - pow(costheta0, 2));     // calculat sin of angle of refraction (sintheta1)
    sintheta1 = (sintheta1 > 1) ? 1 : sintheta1;                     // clamp total internal refraction
    costheta1 = sqrt(1 - sintheta1);                                   // convert from sin to cos
    /*  End Calculate Theta1   */
        
        
    /*  Calculate Theta2   */
    float sintheta2 = pow(n0 / n2, 2) * (1 - pow(costheta0, 2));    // calculat sin of angle of refraction (sintheta1)
    sintheta2 = (sintheta2 > 1) ? 1 : sintheta2;                       // clamp total internal refraction
    float costheta2 = sqrt(1 - sintheta2);                           // convert from sin to cos
    /*  End Calculate Theta2   */
  
    //calculate alpha s and t separately
    alpha_s = ((n1 * costheta1 - n0 * costheta0) / (n1 * costheta1 + n0 * costheta0))*((n1 * costheta1 - n2 * costheta2) / (n1 * costheta1 + n2 * costheta2));
    alpha_p = ((n0 * costheta1 - n1 * costheta0) / (n1 * costheta0 + n0 * costheta1)) * ((n2 * costheta1 - n1 * costheta2) / (n1 * costheta2 + n2 * costheta1));
        
    //calculate beta s and t separately
    beta_s = (2 * n0 * costheta0 / (n0 * costheta0 + n1 * costheta1)) * (2 * n1 * costheta1 / (n1 * costheta1 + n2 * costheta2)); 
    beta_p = (2 * n0 * costheta0 / (n0 * costheta1 + n1 * costheta0)) * (2 * n1 * costheta1 / (n1 * costheta2 + n2 * costheta1));
    
    // calculate light ratio
    lightRatio = (n2 * costheta2) / (n0 * costheta0);
}

Now, s-polarized reflectance coefficient is calculated with the equation



and the p-polarized reflectance coefficient is calculated with the equation



Based on the assumption of 50%-50% polarization, the reflection coefficient can be calculated as:

     R = (Rs + Rs) / 2

Now, the transmission coefficient is calculated based on conservation of energy as:

     T = 1 - R



fig. 4 Reflectance and Transmission coefficients for varying light incident angle


float calculateIntensity(
            float lambda; float n1; float calculatedThickness; float costheta1;
            float delta; float alpha_s; float alpha_p; float beta_s; float beta_p;
            float lightRatio; float frontside
                        )
{
    // calculate optical path difference
    float phi = (((2 * PI) / lambda) * (2 * n1 * calculatedThickness * costheta1) + delta);
    
    //transmitted intensity for two possible polarizations
     float ts = pow(beta_s, 2) / (pow(alpha_s, 2) - 2 * alpha_s * cos(phi) + 1);
     float tp = pow(beta_p, 2) / (pow(alpha_p, 2) - 2 * alpha_p * cos(phi) + 1);
  
    
    //Transmission based 50% average distribution
    float transIntensity = (ts + tp) * lightRatio / 2;
    
    //Reflectance based on transmission intensity (conservation of energy assumption)
    float reflectIntensity = 1 - transIntensity;
    
    
    if(frontside)    return reflectIntensity;
    else            return 1;
}

Spectral Color Conversion

Since we are calculating from physically based equations, color values are not expressed as RGB, as CG rendering requires, but rather as an intensity at a given wavelength. The calculateIntensity() function above takes a parameter of wavelength λ and returns intensity Ir. In order to generate color values, we will sample Ir  for various values of λ at each θ0. We will apply the resulting values of Ir to a lookup table for XYZ color coordinates, and find the average color, which will be returned.

illuminance(P)
{
    float costheta1;
    float alpha_s;
    float alpha_p;
    float beta_s;
    float beta_p;
    float lightRatio;
    float frontside;
    calculatePolarization(L, Nn, n0, n1, n2, costheta1, alpha_s, alpha_p, beta_s, beta_p, lightRatio, frontside);

    // variables to store color based on varying lambda and reflectance and transmission intensities
    float X = 0;
    float Y = 0;
    float Z = 0;
    float i;

    // calculate transmission and reflectance intensitie for varying lambda
    for(i = 0; i < 81; i+=1)
    {
        float lambda = i * 5 + 380;
        float reflectIntensity = calculateIntensity(lambda, n1, thickness, costheta1, delta, alpha_s, alpha_p, beta_s, beta_p, lightRatio, frontside);
        //Add current color to color coordinate sums, multiplied by reflectance Intensity
        X += comp(lookup[i], 0) * reflectIntensity;
        Y += comp(lookup[i], 1) * reflectIntensity;
        Z += comp(lookup[i], 2) * reflectIntensity;

    }

    // identity of CIE x + y + z = 1, therefore xOut = x / (x + y + z)
    float XYZ = X + Y + Z;
    float outX = X / XYZ;
    float outY = Y / XYZ;
    float outZ = Z / XYZ;

    color outcolor = color "XYZ" (outX, outY, outZ);
    outcolor = adjustHSV(outcolor, "XYZ", hueShift, saturation, 0);
    
    interference_col *= (Cl * outcolor);
}
Important Topics:

Thin Film Interference
Reflection Fresnel Equations Snell's Law
Huygens-Fresnel Principle
Christiaan Huygens
Planck's Quantum Theory
Resources Consulted:

http://hyperphysics.phy-astr.gsu.edu/hbase/phyopt/oilfilm.html
http://dl.acm.org/citation.cfm?id=1122506
http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/thin-film-interference-for-computer-graphics-r2962
http://forums.odforce.net/topic/4788-coatings-iridescence/
http://www.houdinihelp.com/#!iridescent-point-color/c20ha
http://en.wikipedia.org/wiki/Interference_(wave_propagation)
http://forums.odforce.net/topic/17017-iridescent-shader/