/** * RCskin.sl * * Copyright (C) 2002, Rudy Cortes rcortes AT hntb DOT com * created 09/16/2002 * * This software is placed in the public domain and is provided as is * without express or implied warranty. * * Surface shader that implements a shading model that should have a visual * appearence generall similar to that of skin. * * Feel free to use this shader to create skin for any character, anywhere and * everywhere, Just list me on the credits under "Shading Team" ******************************* * PARAMETERS * Kd = the amount of uniform diffusion applyied to the skin * skincolor, skinmap = the color of the skin. Using a map overrides original skincolor * color value. * sheencolor, shinmap = the color of the skin at grazing angles. Using a map overrides * original sheencolor color value. * blemishfreq, blemishthresh,blemhishopac = control the freqency, threshold and opacity * of blemishes on the skin. Use to give skin a little variation. * blemishmap = Use a grayscale map to control where the belmishes will be more visible * xdir, angle = control the direction of the specular highlights. * Oily,oilmap = controls how oily the skin looks. Use a grayscale map to contol * oiliness.---Note--- teh oiliy parameter is multiplied with the map. * xroughness,yroughness = how rough is the specular highlight on x and Y? * poresfreq, poresthresh poresdepth = control the frequency, threshold and depth of the * pores. ************************************** * * You can replace the header functions by using * #include "locilum.h" and #include "noises.h". * NOTE- you must copy the entire subsurface skin function to your * "locIllum.h" file * * Enjoy!! * * Modification history: * 9/22/02 Tal Lancaster * Renamed RudyCSkin for RMR. * Made changes so would compile. * **/ /* NECESARY FUNCTIONS */ /*---------FROM BMRT's locillum.h----- * Greg Ward Larson's anisotropic specular local illumination model. * The derivation and formulae can be found in: Ward, Gregory J. * "Measuring and Modeling Anisotropic Reflection," ACM Computer * Graphics 26(2) (Proceedings of Siggraph '92), pp. 265-272, July, 1992. */ color LocIllumWardAnisotropic (normal N; vector V; vector xdir; float xroughness, yroughness;) { float sqr (float x) { return x*x; } float cos_theta_r = clamp (N.V, 0.0001, 1); vector X = xdir / xroughness; vector Y = (N ^ xdir) / yroughness; color C = 0; extern point P; illuminance (P, N, PI/2) { /* Must declare extern L & Cl because we're in a function */ extern vector L; extern color Cl; float nonspec = 0; lightsource ("__nonspecular", nonspec); if (nonspec < 1) { vector LN = normalize (L); float cos_theta_i = LN . N; if (cos_theta_i > 0.0) { vector H = normalize (V + LN); float rho = exp (-2 * (sqr(X.H) + sqr(Y.H)) / (1 + H.N)) / sqrt (cos_theta_i * cos_theta_r); C += Cl * ((1-nonspec) * cos_theta_i * rho); } } } return C / (4 * xroughness * yroughness); } /*---locillum.h ends---*/ /* ------ ADAPTED FROM Mat Pahr's Skin.sl------ * --- subsurfaceSkin------ * Surface shader that implements a shading model that should have a visual * appearence generall similar to that of skin. Based on phenomenological * information about skin reflectance from Hanrahan and Krueger, * "Reflection from layered surfaces due to subsurface scattering", * proceedings of Siggraph 1993. */ /* Evaluate the Henyey-Greenstein phase function for two vectors with an asymmetry value g. v1 and v2 should be normalized and g should be in the range (-1, 1). Negative values of g correspond to more back-scattering and positive values correspond to more forward scattering. */ float phase(vector v1, v2; float g) { float costheta = -v1 . v2; return (1. - g*g) / pow(1. + g*g - 2.*g*costheta, 1.5); } /* Compute a the single-scattering approximation to scattering from a one-dimensional volumetric surface. Given incident and outgoing directions wi and wo, surface normal n, asymmetry value g (see above), scattering albedo (between 0 and 1 for physically-valid volumes), and the thickness of the volume, use the closed-form single-scattering equation to approximate overall scattering. */ float singleScatter(vector wi, wo; normal n; float g, albedo, thickness) { float win = abs(wi . n); float won = abs(wo . n); return albedo * phase(wo, wi, g) / (win + won) * (1. - exp(-(1/win + 1/won) * thickness)); } vector efresnel(vector II; normal NN; float eta; output float Kr, Kt;) { vector R, T; fresnel(II, NN, eta, Kr, Kt, R, T); Kr = smoothstep(0., .5, Kr); Kt = 1. - Kr; return normalize(T); } /* Implements overall skin subsurface shading model. Takes viewing and surface normal information, the base color of the skin, a color for an oily surface sheen. */ color subsurfaceSkin(vector Vf; normal Nn; color skinColor, sheenColor; float eta, thickness) { extern point P; float Kr, Kt, Kr2, Kt2; color C = 0; vector T = efresnel(-Vf, Nn, eta, Kr, Kt); illuminance(P, Nn, PI/2) { vector Ln = normalize(L); vector H = normalize(Ln + Vf); if (H . Nn > 0) C += Kr * sheenColor * Cl * (Ln . Nn) * pow(H . Nn, 4.); C += Kr * sheenColor * Cl * (Ln . Nn) * .2; vector T2 = efresnel(-Ln, Nn, eta, Kr2, Kt2); C += skinColor * Cl * (Ln . Nn) * Kt * Kt2 * (singleScatter(T, T2, Nn, .8, .8, thickness) + singleScatter(T, T2, Nn, .3, .5, thickness) + singleScatter(T, T2, Nn, 0., .4, thickness)); } return C; } /* subsurfaceSkin --ends */ #define snoise(p) (2 * (float noise(p)) - 1) /* *shader beggins here. */ surface RudyCSkin( float Kd = .1; color skincolor = color (0.7, 0.56, 0.52); string skinmap = ""; color sheenColor = 1.; string sheenmap = ""; float blemishfreq = 12, belmishthresh = 4, blemishopac = 1; string blemishmap = ""; float eta = 1.01, thickness = 1; varying vector xdir = dPdu; float angle = 180; float oily = 1.5; string oilmap = ""; float xroughness = .3, yroughness = .5; float poresfreq = 60, poresthresh = 3, poresdepth = -0.001;) { /* initialize local variables*/ normal Nf, NN; vector Vf = -normalize(I); color sc , lc; float lo; point PP; float turb, f; float maxfreq = 8; color Cskin = skincolor; color Csheen = sheenColor; float oilVal = oily; /*--- layer 1 - add pores to the skin*/ PP = transform ("shader",P) * poresfreq; turb = 0; for (f = 1; f< maxfreq; f *= 2) turb += abs(snoise(PP * f)) / f; turb = pow(turb,poresthresh) * poresdepth; /* displace normals*/ NN = calculatenormal(P + turb * normalize(N)); Nf = faceforward(normalize(NN),I); /* layer 1 .- apply a subsurface scattered BRDF to the surface. * you can use an image map for the color of the skin and the sheen. * If the map is not provided use defined colors*/ if (skinmap != "") Cskin = color texture(skinmap); if ( sheenmap != "") Csheen = color texture(sheenmap); sc = Cskin; /* layer 2 - create small skin blemishes over the skin. Use a map to control * where the blemishes are visible */ PP = transform ("shader",P) * blemishfreq; turb = 0; for (f = 1; f< maxfreq; f *= 2) turb += abs(snoise(PP * f)) / f; turb = pow(turb, belmishthresh ); color red01 = Cskin + 0.05, red02 = red01 - 0.05, darkred01 = Cskin - 0.05, darkred02 = darkred01 - 0.05; color blemishcol = color spline ( turb, red01, red02, darkred01, red01, darkred01, red02, darkred01, darkred02, darkred01, darkred02, red01); lc = subsurfaceSkin(Vf, Nf, blemishcol, Csheen, 1/eta, thickness); lo = 1 * (blemishopac) ; if (blemishmap != "") lo *= float texture (blemishmap); sc = mix (sc,lc,lo); /* --Layer 3-- apply an Anisotropic BRDF to simulate the oil layer that lies * outside the skin. Oilyness is controlled by the oily, xrougness and yroughness. * */ if (oily > 0.01){ vector anisoDir = xdir; if (angle !=0) { matrix rot = rotate(matrix 1, radians(angle),Nf); anisoDir = vtransform(rot,anisoDir); } lc = LocIllumWardAnisotropic(Nf,Vf,anisoDir,xroughness,yroughness); if (oilmap != "") oilVal *= float texture(oilmap); sc += (lc * (oilVal * 0.1) ); } /* output*/ Oi = Os; Ci = sc + Kd * diffuse(Nf) ; }