Sunday, May 29, 2011

Step By Step Pixar Style Eyes Shader. Step3: The Specular Components












This is the last component needed for our shader eyes - specular glare and reflections.

ReflectivityComponents_SpecAndSpecRefl from Alex Mirgorodsky on Vimeo.

// cEyeShader.sl written by Alex Mirgorodsky 

// any questions about this shader code send on my e-mail: amir543@gmail.com
// This code free to copy and distribute. 

class cEyeShader(
    uniform float EyeIrisXPosition = 0.5;
    uniform float EyeIrisYPosition = 0.5;
    uniform color EyeIrisInColor = (0.322, 0.18, 0.0);
    uniform color EyeIrisMidColor = (0.541, 0.278, 0.063);
    uniform color EyeIrisOutColor = (0.322, 0.18, 0.0);
    uniform float EyePupilRadius = 0.03;
    uniform float EyeIrisRadius = 0.06;
    uniform float EyeIrisSeed = 1;
    uniform float EyeIrisCircularNoiseLevel = 0.2;
    uniform float EyeIrisRadialNoiseLevel = 0.75;
    uniform float EyeIrisRadialShift = 0.3;
    float EyeIrisMinLevel = 0.05;
    float EyeIrisCausticLevel = 3;
    float EyeKd = 0.7; // Koeffs Diffuse
    float EyeWa = 0.1; // Wideness Light Angle
    color ScleraHighlight = color(0.835, 0.9, 0.965);
    color ScleraLowlight = color(0.234, 0.234, 0.276);
    color ScleraCenterColorize = color(1);
    color ScleraOutColorize = color(1);
    float OffsetCircularColor = 3;
    float EyeShadowLevel = 0.1;
    string Category = "eye";
    // Specular 
    float EyeKs = 1;
    float SpecularRoughness = 0.003;
    float SpecularSharpness = 0.9;
    // Reflection
    float Refectivity = 1.0;
    float MaxDistance = 256;
    uniform float ReflSamples = 3;
   
    )
{
   
uniform float pi2 = 6.283185307;
shader lights[] ={};
shader categoryLights[] = {};
shader notCategoryLights[] = {};
constant float countLights = 0;
constant float countCategoryLights = 0;
constant float countNotCategoryLights = 0;

float linstep(float min, max, x)
    {
    float f;
    if (x < min) f = 0;
    if (x >= max) f = 1;
    f = (x-min)/(max-min);
    return f;
    }

float IrisNoise()
    {
    float angle;
    uniform float noiseScale1 = 100;
    uniform float noiseScale2 = 500;
   
    float d = distance(point(EyeIrisXPosition, EyeIrisXPosition, 0), point(s, t, 0));
        if (d != 0)     angle = asin((EyeIrisYPosition-t)/d);
        else   angle = 0;
        if (EyeIrisXPosition-s < 0) angle = PI - angle;
   
    float n1 = noise(s*noiseScale1, t*noiseScale1) * clamp(noise(s*noiseScale2, t
                *noiseScale2),.5, 1) * 2;
    float n2 = noise(angle*5+EyeIrisSeed);
       
    n1 = (n1 * EyeIrisCircularNoiseLevel) + (1-EyeIrisCircularNoiseLevel);
    n2 = (n2 * EyeIrisRadialNoiseLevel) + (1-EyeIrisRadialNoiseLevel);
   
    return n1*n2;
    }

normal NormalSurf()
{
    normal Ns ;
    normal Nn = normalize(N);
    uniform float depth;
               
    rayinfo("depth", depth);
        if(depth > 0) Ns = faceforward(Nn, normalize(I), Nn);
        else
        {
            uniform float sides = 2;
            attribute("Sides", sides);
            if(sides == 2)    Ns = faceforward(Nn, normalize(I), Nn);
            else Ns = Nn;
        }
               
    return Ns;
}   

float angularDistribution(float rotationAngle)
{
    float angleRad = radians(rotationAngle);
    float Sn = s-EyeIrisXPosition;
    float Tn = t - EyeIrisYPosition;
    float S_Rot = (Sn* cos(angleRad) - Tn*sin(angleRad)) + EyeIrisXPosition;
    float T_Rot = (Tn* cos(angleRad) + Sn*sin(angleRad)) + EyeIrisYPosition;
    float ss = (atan(T_Rot-EyeIrisYPosition, S_Rot-EyeIrisXPosition) + PI) / pi2;
    float outputDistribution = float spline ("bspline", ss, 1, 1, 0.5, 0, 0.5, 1, 1);
    return outputDistribution;
   
}
public void construct()
{
    lights = getlights();
    countLights = arraylength(lights);
   
    uniform string __category;
    uniform float i;
    for (i = 0; i < countLights; i += 1)
    {
        getvar(lights[i], "__category", __category);
        if(__category == Category) {
            push(categoryLights, lights[i]);
        } else {
            push(notCategoryLights, lights[i]);
        }
       
    }
    countCategoryLights = arraylength(categoryLights);
    countNotCategoryLights = arraylength(notCategoryLights);

}

public void surface(output color Ci, Oi)
    {
   
    // Dummy Difuse Color
    color Diffuse = color(1);
    // Pupil color (You can make your self solution for Pupil Lighting. But I use siple black color.
    color PupilColor = color(0);  
   
    float RadialDistribution = sqrt(pow((s-EyeIrisXPosition), 2) + pow((t-EyeIrisYPosition),2));
       
    float reDistribution = RadialDistribution / (EyeIrisRadius-EyePupilRadius);
    float kKorrection  = EyePupilRadius / (EyeIrisRadius-EyePupilRadius);
   
    float kSpline = float spline( "linear", reDistribution, kKorrection,
                    kKorrection, (EyeIrisRadialShift + kKorrection),
                    (1 + kKorrection), (1 + kKorrection));
    color IrisPainting = color spline( "linear", kSpline, EyeIrisInColor,
                    EyeIrisInColor, EyeIrisMidColor, EyeIrisOutColor, EyeIrisOutColor);
    IrisPainting = IrisPainting * IrisNoise(); // Add Noise to Iris
   
    color IrisColor_W_Pupil = mix(PupilColor, IrisPainting ,
                    clamp(linstep( EyePupilRadius, (EyePupilRadius +(EyePupilRadius/100)),
                    RadialDistribution),0,1));
   
   
    //------ Calculate Fake Tangent Map -------------------------------------------------------      
           
    float tangent = angularDistribution(180);
    float binormal = angularDistribution(90);
    color tangentAngularMap = color(tangent, binormal, 0.5);
       
    normal Ns = NormalSurf();
   
    normal Tangent = normal(tangentAngularMap);
    normal ReTangent =  2*Tangent - normal(1,1,1);
    //ReTangent = normal(ReTangent[0], ReTangent[1], ReTangent[2]);
    ReTangent = transform("object", "current", ReTangent);
    normal Unit = normal(0,0,1);
    normal ReNormal = normalize(Ns +(Unit - ReTangent));
                 
    //------ Lighting ----------------------------------------------------------------------------------     
    color fakeCaustic=0;
    float nondiff = 1;
    float InputAngle = -EyeWa*2;
    color diffColor = 0;
    //_lightingstart();
    uniform float i;
    for (i = 0; i < countCategoryLights; i += 1)
    {
        vector L;
        color Cl =0;
        float LightAngle;
        categoryLights[i]->light(L, Cl);
        //------Fake  Caustic  Computing ------------------------------------------------------
        float ratio = ReNormal.normalize(-L);
        fakeCaustic += pow((1+ratio)/2, 2);
        //------ Diffuse Computing With WDA-------------------------------------------------
       
        LightAngle  = normalize(-L).Ns;
        if(Ns.I > 0) LightAngle = -LightAngle;               
        LightAngle = clamp(LightAngle,InputAngle,1);
        LightAngle = (LightAngle - InputAngle) / (1 - InputAngle);
        getvar(categoryLights[i], "__nondiffuse", nondiff);
        if (nondiff <1)
            {
                diffColor += (1-nondiff) * Cl * LightAngle;
            }
    }
   
    //------- Shadows computing ----------------------------------------------------------------
    color inshadow=0;
    color accumShadow = 0;
    float accumRatio = 0;
   
    for (i = 0; i < countLights; i += 1)
    {
        vector L;
        color Cl =0;
        lights[i]->light(L, Cl);
           
        if( 0 != getvar(lights[i], "_shadow", inshadow))
            {
                accumShadow += inshadow;
            }
    }
   
    //accumRatio = accumRatio/countLights;
   
    color shadowHSL = ctransform("hsl", accumShadow);
    float shadowCalc = 1 - shadowHSL[2]*EyeShadowLevel;

    //------- Fake Caustic  --------------------------------------------------------------------------
   
    float pupilMask =  clamp(smoothstep( EyePupilRadius,
                    (EyePupilRadius +(EyePupilRadius/2)),RadialDistribution),0,1);
    float irisMask =  clamp(smoothstep((EyeIrisRadius
                    - (EyePupilRadius/2)), EyeIrisRadius, RadialDistribution),0,1);
    float pupilIrisMask = (1-irisMask)* pupilMask;
    float causticComp =  clamp((EyeIrisMinLevel
                + EyeIrisCausticLevel*pow(fakeCaustic[0], 2)*pupilIrisMask), 0,1);

   
    //------- Assemble Diffuse Components ---------------------------------------------------
    color diffuseHSL = ctransform("hsv", (diffColor*shadowCalc));
    color scleraColorRemap = mix(ScleraLowlight, ScleraHighlight, pow(diffuseHSL[2], 3));
   
    float Sn = s-EyeIrisXPosition;
    float Tn = t - EyeIrisYPosition;
    Sn = 1 - sqrt(2 * (Sn * Sn + Tn * Tn));
    color CircularColor = mix(ScleraOutColorize, ScleraCenterColorize,  pow(Sn,(1+OffsetCircularColor)));
   
    color IrisColor_W_Diffuse = mix(IrisColor_W_Pupil*causticComp, scleraColorRemap*CircularColor, 
                    filterstep(EyeIrisRadius, RadialDistribution));
   
                   
    //------ Reflective Components -------------------------------------------------------------
    float fkr, fkt;
    vector Refl, Refr;
    vector In = normalize(I);
    fresnel(In, Ns, 1.75187, fkr,fkt, Refl, Refr);
    //-------------------- Specular ------------------------------------------------------------------
    vector V = -normalize(I);
    color spec_color = 0;
    float glossyRegion  = SpecularSharpness/2;
    float nonspec = 0;
    color specular = 0;
    //vector Refl = reflect(normalize(I), normalize(N));
    color specOut = 0;
   
    for (i = 0; i < countCategoryLights; i += 1)
    {
        vector L;
        color Cl;
        categoryLights[i]->light(L, Cl);
   
        float nonspec = 0;
        getvar(categoryLights[i], "__nonspecular", nonspec); // getting specular weight koef from light 
       
        vector H = normalize(normalize(-L)+V); // Halfway vector 
       
        specular = Cl*(1-nonspec);
        spec_color += specular * (smoothstep (glossyRegion ,1-glossyRegion , pow(max(0,Ns.H), 1/SpecularRoughness)));
    }
    //---------------------- Environment Reflection --------------------------------------------
    vector ReflDir = reflect(normalize(I), Ns);
    color specularIndirect = 0;
   
    /* The pseudo Fresnel term - like Schlick Aproximation */
    float FrenelTerm = 1 - pow(normalize(-I).Ns, 0.6);
       
    color reflAccumulation = 0;
    color reflColor = 0;
   
    /* Reflection map.
    I use renderman user option string usually 
    for sent to a the scene shaders. 
    But for example I use simle string */
    string envMap = "street.env";
   
    gather("illuminance", P, ReflDir, 0,  ReflSamples,
            "distribution", "cosine",
            "maxdist", MaxDistance,
            "volume:Ci", reflColor)
            {
                reflAccumulation += reflColor;
            } else {
                ReflDir = transform ("ReflEnvMapCoordsys", ReflDir);
                reflAccumulation += environment(envMap, ReflDir, "width", 1 );
            }
    specularIndirect = (reflAccumulation / ReflSamples) * FrenelTerm * Refectivity;
   
    //------ Assemble All Components ---------------------------------------------------------
    color outcolor = IrisColor_W_Diffuse+spec_color+specularIndirect;
   
    Oi = color(1);
    Ci=outcolor;

    }

}
 The next final step will be a small tuning to a custom specular shapes - a rectangle and ellipse.
Coming soon

3 comments:

  1. Thanks, Alex, very nice and useful.

    ReplyDelete
  2. Hi Alex.
    First, thanx for the shader, and for the explanations.

    I am new to Renderman programing, and so my question will probably be a laughing matter for most guys here. But still, I need to ask...
    :)

    My problem starts with the lighting - after putting in the parts concerning highlights and the rest (part two of your shader and onward), I can't get any light on it, no matter what I do.
    I tried, of course, everything I know (though it's not much...) - re-checking ENV variables, trying various lights and attributes both of Renderman and Maya.

    I'd like to know if anybody else had the same problem, or if anyone has an idea about the reasons.

    Thanks again,
    Moshe

    ReplyDelete