Tuesday, January 4, 2011

Step By Step Pixar Style Eyes Shader. Step2: Diffuse Lighting











Hi All! Marry Christmas and Happy New Year!!!!
Last three month I've been really busy and tightened with the publication of the second part. So now I wrote it. In a first gone the Illustrations and all shaders code and in a second a minor clarifications to certain parts of code.



































Eye Shader Illustrations 1 from Alex Mirgorodsky on Vimeo.
The code:
class cEyeShader(
    uniform float EyeIrisXPosition = 0.5;
    uniform float EyeIrisYPosition = 0.5;
    uniform color EyeIrisInColor = (0.025, 0.05, 0.01);
    uniform color EyeIrisMidColor = (0.7, 0.3, 0.02);
    uniform color EyeIrisOutColor = (0.025, 0.05, 0.01);
    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.1;
    float EyeIrisCausticLevel = 3;
    float EyeKd = 1; // Koeffs Diffuse
    float EyeWa = 0.2; // 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.0;
    string Category = "eye";
    )
{
    
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;
    
    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;
            }
    }
    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));
    
    //------ Assemble All Components ---------------------------------------------------------
    color outcolor = IrisColor_W_Diffuse;

    Oi = color(1);
    Ci=outcolor;
    }
}
I use synth of normal map for fake illumination of iris-pupil stria. Of course we could do it without any color implementations. But it easy for understanding of.
float tangent = angularDistribution(180);
float binormal = angularDistribution(90);
color tangentAngularMap = color(tangent, binormal, 0.5);

In a real eye( human eye) this is narrow groove. But in a cartoon animations we use exaggerations for most effects - and I make full Iris-size fake caustic highlight. In any case You may change the part of code for making "pupilIrisMask":
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;
Diffuse Lighting of sclera I separate in two illumination loops. Lighting I make ONLY the special light with __category and a shadows I catching from all light in scene. And finnaly I make color remapping for the diffuse component of sclera by couples variables "ScleraHighlight-ScleraLowlight" and "ScleraCenterColorize-ScleraOutColorize"
In final third part - we will make reflection components for the Eye Shader.
next step 3
previous step 1

No comments:

Post a Comment