ACADEMIC RESEARCH AND PUBLICATIONS
during Graduate and Undergraduate Studies
Doctoral Candidate in Computer Science
Visual Computing Division, Digital Production Arts
Clemson University | School of Computing
REAL-TIME PROCEDURAL LABRADORITE:
MATCHING THE REAL IN GLSL
//---------------------------------
void main ()
{
// Array of noise floats packaged together. (At one point tried cycling through a couple when using.)
float noises[6] = float[6] (noise( texture_coords * vec2(16.0, 10.0) ), // An option for the bright-color shapes.
noise( texture_coords * vec2(10.0, 9.0) ), // For bright-color shapes.
noise( texture_coords * 8.0 ), // For making dark stripes.
// Higher frequency noises, for distorting back-face normals.
noise( texture_coords * vec2(400.0) ),
noise( texture_coords * vec2(50.0, 40.0) ),
noise( texture_coords * 200.0 ) );
vec3 N = normal_eye;
vec3 P = position_eye;
float specular_exponent = 600.0;
bool front = false;
vec2 centerFront = vec2(0.7, 0.35); // Center of rock's front face based on UV mask.
vec3 tc = vec3(texture(rockFacesMask, texture_coords));
// Distort normal a lot if not the smooth, front face.
if ((tc.x > 0.9) && (tc.y > 0.9) && (tc.z > 0.9)) // White enough
{
front = true; // Flag used later.
}
else
{
N = normalize( N + vec3(noises[3], noises[4], noises[5]) );
}
// Original Blinn diffuse intensity: vec3 Id = Ld * Kd * dot_prod;
vec3 Is = vec3(0.0);
vec3 Id = vec3(0.0);
vec3 surface_to_viewer_eye = normalize (-P); // Vector toward camera for specular.
// Interpolation boundaries for color mixing.
float[10] areas = float[10] (0.5, 0.7, 0.85, 0.97, 0.0,
0.5, 0.8, 0.9, 1.0, 0.0 );
// Diffuse and specular intensities per light
for (int i=0; i<2; i++)
{
// Vectors here are appended with _eye as a reminder once in view space versus world space.
// "Eye" is used instead of "camera" since reflectance models often phrased that way.
vec3 light_position_eye = vec3 (view_mat * vec4 (light_positions_world[i], 1.0));
vec3 distance_to_light_eye = light_position_eye - P;
vec3 direction_to_light_eye = normalize (distance_to_light_eye);
float dot_prod = max( 0.0, dot(direction_to_light_eye, N) );
// Blinn specular
vec3 half_way_eye = normalize (surface_to_viewer_eye + direction_to_light_eye);
float dot_prod_specular = max (dot (half_way_eye, N), 0.0);
float specular_factor = pow (dot_prod_specular, specular_exponent);
Is += Ls * Ks * specular_factor; // final specular intensity
// Intensities based on labradorite lamellae thicknesses.
vec3 lab0 = labradoriteIntensity( dot_prod, IORs, vec2(80.0, 60.0), vec2(60.0, 50.0) );
vec3 lab1 = labradoriteIntensity( dot_prod, IORs, vec2(72.0, 67.0), vec2(16.0, 16.0) );
vec3 lab2 = labradoriteIntensity( dot_prod, IORs, vec2(90.0, 60.0), vec2(20.0, 16.0) );
vec3 lab3 = labradoriteIntensity( dot_prod, IORs, vec2(152.0, 60.0), vec2(20.0, 16.0) );
// Use intensities to influence each bright color. (The extra space in the array is due to
// blendColors supporting up to 10 colors.)
vec3[10] colors = vec3[10]( blue*lab0, teal*lab1, yellow*lab2, orange*lab3,
Kd, Kd, Kd, Kd, Kd, Kd );
// Incorporate the surface's main diffuse color Kd when calculating view-dependent labradorescence.
// Multiply by the specular dot product to have the maximum glint show before seeing the specular lobe reflected.
vec3 brightColor = blendColors( float[10] (areas[0], areas[1], areas[2],
areas[3], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
colors, noises[1], 4 );
// Noise to add variation to the base gray of the rock.
vec2 st0 = rotate2d( noise(texture_coords*vec2(-5.0)) ) * texture_coords;
vec3 diffuseTexture = marble( vec2(5.0)*st0, 1.0 );
vec2 st2 = rotate2d( noise(texture_coords*vec2(-5.0)) ) * texture_coords;
vec3 breakColor = vec3(1.0) - marble( vec2(10.0)*st2, 1.0 );
vec2 st3 = vec2(20.0, 10.0) * texture_coords;
st3 = rotate2d( -1.0 ) * st3;
vec3 scratches = marble( st3, 3.0 );
scratches = mix( black, scratches, marble(0.5*st3, 2.0) );
// Combine the base gray, bright colors, and dot products based on light directions into
// the diffuse component.
vec3 newKd = mix( lightGray, Kd, scratches*diffuseTexture );
Id += Ld * newKd * dot_prod;
brightColor *= pow(dot_prod_specular, 30.0);
Id += brightColor;
Id *= 0.5; // Averaging components of the two lights.
// Adjust diffuse (blotchy colors) and specular based on what side it is.
if (front)
{
// Bright colors more focused around center of the front face.
float middle = circle( texture_coords, centerFront, 0.08, 2.0 );
Id = mix(Id, brightColor, 0.75*middle); // Gets dark without scaling down.
}
else
{
Is *= 0.5; // Dim the back side.
}
// Add black stripes to diffuse afterwards so they're on top.
vec2 st1 = rotate2d( noise(texture_coords*vec2(2.0)) ) * texture_coords;
vec3 stripes = marble( vec2(5.0, 8.0)*st1, 2.8 );
stripes = mix(white, stripes, noises[2]);
Id *= stripes;
} // End per light
// Final color with no ambient.
fragment_color = vec4 (Is + Id, 1.0);
}
Language: C++ and GLSL
Date: Fall 2018
Class: Rendering and Shading (Graduate)
I photographed a piece of labradorite from various angles to use as reference images when modeling its shape in Maya. After creating the model, I designed a procedural shader in GLSL to produce a "labradorescence" effect to texture the stone.
Analyzing Weidlich and Wilkie’s 2009 paper on rendering labradorescence (https://www.cg.tuwien.ac.at/research/publications/2009/weidlich_2009_REL/weidlich_2009_REL-.pdf), I implemented the function they presented that approximates intensity at a surface point from the light based on the refraction among Labradorite’s lamellae (very thin layers that produce the optical effect). Additionally, I made a function for blending multiple colors based on weights as described in this paper.
I layered Perlin noise for adding detail to the base gray color, distorting normals on the rougher part of the rock, and controlling where the brighter colors are. I also used Perlin’s “marble” function for additional details and distortions. These functions are described in the original 1985 paper (https://dl.acm.org/citation.cfm?id=325247). Noise and a simple mask image distinguishing the front and back faces of the rock affect its smoothness or bumpiness by not changing or altering the normal per fragment.
The bright colors are ultimately view-dependent when combined into the diffuse component. The provided code is the main function of the fragment shader. The videos below are the model of the labradorite procedurally shaded beside a reference video of the real stone, and the same shader texturing my fox model.