v4k-git-backup/demos/art/shadertoys/XsfGWj.fs

594 lines
17 KiB
GLSL

// Ben Weston - 15/08/2013
/*
Eye ball effects:
• Ray-marched shape
• Ray-traced iris refraction
• Fake photon mapping on iris
• Subsurface scattering on sclera
• HDR reflections with fresnel
• Eyelid reflection occlusion
• Eyelid ambient occlusion
• Procedural textures
• Procedural animation
*/
// KEY CONTROLS - (click on eye to give keyboard focus)
const int Key_M = 77; // mouse controls camera / eye direction
const int Key_E = 69; // refraction on/off
const int Key_P = 80; // photon mapping on/off
const int Key_L = 76; // change photon mapping technique (both fake, but one is imitating reality and the other is prettier)
const int Key_S = 83; // subsurface scattering on/off
const int Key_A = 65; // ambient occlusion on/off
const int Key_R = 82; // reflection on/off
const int Key_O = 79; // reflection eyelid occlusion on/off
const int Key_C = 67; // iris colour
const int Key_N = 78; // iris normal
// Lights
#if (1)
// High-contrast light edge-on
const vec3 lightDir = vec3(-2,2,.5);
const vec3 lightColour = vec3(1.0);
const vec3 fillLightDir = vec3(0,1,0);
const vec3 fillLightColour = vec3(.65,.7,.8)*.7;//vec3(.15,.2,.25);
#else
// more neutral "good" lighting (doesn't show off the effects)
const vec3 lightDir = vec3(-2,2,-1);
const vec3 lightColour = vec3(.83,.8,.78);
const vec3 fillLightDir = vec3(0,1,0);
const vec3 fillLightColour = vec3(.65,.7,.8);
#endif
// Constants
const float tau = 6.28318530717958647692;
// Forward declarations
float Noise( in vec3 x );
vec2 Noise2( in vec3 x );
// Gamma correction
#define GAMMA (2.2)
vec3 ToLinear( in vec3 col )
{
// simulate a monitor, converting colour values into light values
return pow( col, vec3(GAMMA) );
}
vec3 ToGamma( in vec3 col )
{
// convert back into colour values, so the correct light will come out of the monitor
return pow( col, vec3(1.0/GAMMA) );
}
// key is javascript keycode: http://www.webonweboff.com/tips/js/event_key_codes.aspx
bool ReadKey( int key, bool toggle )
{
float keyVal = texture( iChannel3, vec2( (float(key)+.5)/256.0, toggle?.75:.25 ) ).x;
return (keyVal>.5)?true:false;
}
// ------- EDIT THESE THINGS! -------
// Camera (also rotated by mouse)
const vec3 CamPos = vec3(0,0.0,-250.0);
const vec3 CamLook = vec3(0,0,0);
const float CamZoom = 10.0;
const float NearPlane = 0.0; // actually not needed
const float drawDistance = 1000.0;
const vec3 SkyColour = vec3(.4,.25,.2);//fillLightColour*.5;//vec3(.1,.3,.5);
vec3 SkyDome( vec3 rd )
{
//the cube maps have lines in, and aren't HDR, so make our own shapes
// random variation
vec3 result = ToLinear(SkyColour)*2.0*Noise(rd);
// square sky-light
result = mix( result, vec3(8), smoothstep(.8,1.0,rd.y/max((rd.x+1.0),abs(rd.z))) );
return result;
}
// Eye params
const float IrisAng = tau/12.0;
const float PupilAng = (1.6*IrisAng/5.0);
const float EyeRadius = 10.0;
const float BulgeRadius = 6.0; // used for photon trace, must be bigger than EyeRadius*sin(IrisAng)
vec4 ComputeEyeRotation()
{
vec2 rot;
if ( !ReadKey( Key_M, true ) && iMouse.w > .00001 )
rot = .25*vec2(1.0,1.0)*tau*(iMouse.xy-iResolution.xy*.5)/iResolution.x;
else
{
float time = iGlobalTime/2.0;
time += Noise(vec3(0,time,0)); // add noise to time (this adds SO MUCH character!)
float flick = floor(time)+smoothstep(0.0,0.05,fract(time));
rot = vec2(.2,.1)*tau*(texture( iChannel0, vec2((flick+.5)/256.0,.5), -100.0 ).rb-.5);
}
return vec4(cos(rot.x),sin(rot.x),cos(rot.y),sin(rot.y));
}
vec3 ApplyEyeRotation( vec3 pos, vec4 rotation )
{
pos.yz = rotation.z*pos.yz + rotation.w*pos.zy*vec2(1,-1);
pos.xz = rotation.x*pos.xz + rotation.y*pos.zx*vec2(1,-1);
return pos;
}
// Shape
// This should return continuous positive values when outside and negative values inside,
// which roughly indicate the distance of the nearest surface.
float Isosurface( vec3 pos, vec4 eyeRotation )
{
pos = ApplyEyeRotation(pos,eyeRotation);
/* float f = length(pos)-EyeRadius;
// f += Noise(pos*3.0)*.008;
// cornea bulge
float o = EyeRadius*cos(IrisAng)-sqrt(BulgeRadius*BulgeRadius-EyeRadius*EyeRadius*pow(sin(IrisAng),2.0));
float g = length(pos-vec3(0,0,-o))-BulgeRadius;
//g += Noise(pos/2.0)*.5;
return min(f,g);
//return -log(exp(-g*2.0)+exp(-f*2.0))/2.0;*/
vec2 slice = vec2(length(pos.xy),pos.z);
float aa = atan(slice.x,-slice.y);
float bulge = cos(tau*.2*aa/IrisAng);
bulge = bulge*.8-.8;
bulge *= smoothstep(tau*.25,0.0,aa);
// sharp-edged bulge
// if ( aa < IrisAng )
// bulge += cos(tau*.25*aa/IrisAng)*.5;
bulge += cos(tau*.25*aa/IrisAng)*.5 * smoothstep(-.02,.1,IrisAng-aa); // slightly softer
return length(slice) - EyeRadius - bulge;
}
float GetEyelidMask( vec3 pos, vec4 eyeRotation )
{
vec3 eyelidPos = pos;
float eyelidTilt = -.05;
eyelidPos.xy = cos(eyelidTilt)*pos.xy + sin(eyelidTilt)*pos.yx*vec2(1,-1);
float highLid = tan(max(tau*.05,asin(eyeRotation.w)+IrisAng+.05));
float lowLid = tan(tau*.1);
float blink = smoothstep(.0,.02,abs(Noise(vec3(iGlobalTime*.2,0,0))-.5 ));
highLid *= blink;
lowLid *= blink;
return min(
(-eyelidPos.z-2.0) - (-eyelidPos.y/lowLid),
(-eyelidPos.z-2.0) - (eyelidPos.y/highLid)
);
}
float GetIrisPattern( vec2 uv )
{
return Noise( vec3( 10.0*uv/pow(length(uv),.7), 0 ) );
}
// Colour
vec3 Shading( vec3 worldPos, vec3 norm, float shadow, vec3 rd, vec4 eyeRotation )
{
vec3 view = normalize(-rd);
// eyelids - just match BG colour
float eyelidMask = GetEyelidMask(worldPos, eyeRotation);
if ( eyelidMask < 0.0 || (-worldPos.z-3.0) < (worldPos.x/tan(tau*.23)) )
{
return ToLinear(SkyColour);
}
vec3 pos = ApplyEyeRotation(worldPos,eyeRotation);
float lenposxy = length(pos.xy);
float ang = atan(lenposxy/(-pos.z));
if ( ang < 0.0 )
ang += tau/2.0;
// refract ray
vec3 irisRay = ApplyEyeRotation(-view,eyeRotation);
vec3 localNorm = ApplyEyeRotation(norm,eyeRotation);
float a = dot(irisRay,localNorm);
float b = cos(acos(a)*1.33);
if ( !ReadKey( Key_E, true ) )
irisRay += localNorm*(b-a);
irisRay = normalize(irisRay);
// intersect with plane
float planeDist = -cos(IrisAng)*EyeRadius;
float t = (planeDist-pos.z)/irisRay.z;
vec3 ppos = t*irisRay+pos;
// polar coord map
float rad = length(ppos.xy);
float pupilr = EyeRadius*sin(PupilAng);
float irisr = EyeRadius*sin(IrisAng);
float irisPattern = GetIrisPattern(ppos.xy); // reduce contrast of this now we have actual lighting!
/* vec3 iris = mix( mix( vec3(.3,.1,.1)*.5+.5*vec3(.6,.4,.1), vec3(.6,.4,.1), irisPattern ), // hazel
mix( vec3(.2,.2,.2)*.5+.5*vec3(.5,.45,.2), vec3(.5,.45,.2), irisPattern ),*/
/* vec3 iris = mix( mix( vec3(.1,.1,.4), vec3(.7,.9,1), irisPattern ), // blue
mix( vec3(.1,.1,.4), vec3(.3,.4,.7), irisPattern ),*/
// smoothstep(pupilr*2.0,irisr,rad));
vec3 iris = ToLinear( mix( pow( vec3(.65,.82,.85), 2.0*vec3(1.2-sqrt(irisPattern)) ),
vec3(1,.5,.2), .7*pow( mix( smoothstep(pupilr,irisr,rad), Noise(ppos), .7), 2.0) ));
if ( ReadKey( Key_C, true ) )
iris = vec3(1);
// darken outer
iris *= pow( smoothstep( irisr+1.0, irisr-1.5, rad ), GAMMA );
vec3 irisNorm;
irisNorm.x = GetIrisPattern(ppos.xy+vec2(-.001,0)) - GetIrisPattern(ppos.xy+vec2(.001,0));
irisNorm.y = GetIrisPattern(ppos.xy+vec2(0,-.001)) - GetIrisPattern(ppos.xy+vec2(0,.001));
// add a radial lump
irisNorm.xy += -.01*normalize(ppos.xy)*sin(1.*tau*rad/irisr);
irisNorm.z = -.15; // adjust severity of bumps
irisNorm = normalize(irisNorm);
if ( ReadKey( Key_N, true ) )
irisNorm = vec3(0,0,-1);
// lighting
// fake photon mapping by crudely sampling the photon density
// apply lighting with this modified normal
vec3 lightDirN = normalize(lightDir);
vec3 localLightDir = ApplyEyeRotation(lightDirN,eyeRotation);
vec3 fillLightDirN = normalize(fillLightDir);
vec3 localFillLightDir = ApplyEyeRotation(fillLightDirN,eyeRotation);
// Bend the light, imitating results of offline photon-mapping
// Jimenez's paper makes this seem very complex, because their mapping used a non-flat receiver
// but the self-shadowing was negligible, so the main effect was just like premultiplying by a normal
// where we'd get better results by using the actual normal.
float photonsL, photonsFL;
if ( !ReadKey( Key_P, true ) )
{
if ( !ReadKey( Key_L, true ) )
{
// Nice retro-reflective effect, but not correct
vec3 nn = normalize(vec3( ppos.xy, -sqrt(max(0.0,BulgeRadius*BulgeRadius-rad*rad)) ));
vec3 irisLDir = localLightDir;
vec3 irisFLDir = localFillLightDir;
// irisLDir.z = -cos(acos(-irisLDir.z)/1.33); // experiments showed it cuts out at 120 degrees, i.e. 1.33*the usual 90 degree cutoff
// irisFLDir.z = -cos(acos(-irisFLDir.z)/1.33); // experiments showed it cuts out at 120 degrees, i.e. 1.33*the usual 90 degree cutoff
float d = dot(nn,irisLDir);
irisLDir += nn*(cos(acos(d)/1.33) - d);
d = dot(nn,irisFLDir);
irisFLDir += nn*(cos(acos(d)/1.33) - d);
irisLDir = normalize(irisLDir);
irisFLDir = normalize(irisFLDir);
photonsL = smoothstep(0.0,1.0,dot(irisNorm,irisLDir)); //soften terminator
photonsFL = (dot(irisNorm,irisFLDir)*.5+.5);
//Seriously, this^ looks really nice, but not like reality. Bah!
/* reverse it, to make it look a lot like the accurate version - meh
vec3 nn = normalize(vec3( -ppos.xy, -sqrt(max(0.0,BulgeRadius*BulgeRadius-rad*rad)) ));
vec3 irisLDir = localLightDir;
vec3 irisFLDir = localFillLightDir;
float d = dot(nn,irisLDir);
irisLDir += nn*(cos(acos(d)/1.33) - d);
d = dot(nn,irisFLDir);
irisFLDir += nn*(cos(acos(d)/1.33) - d);
irisLDir = normalize(irisLDir);
irisFLDir = normalize(irisFLDir);
float photonsL = smoothstep(0.0,1.0,dot(irisNorm,irisLDir)); // soften the terminator
float photonsFL = (dot(irisNorm,irisFLDir)*.5+.5);
*/
}
else
{
//this is a reasonable match to the dark crescent effect seen in photos and offline photon mapping, but it looks wrong to me.
vec3 irisLDir = localLightDir;
vec3 irisFLDir = localFillLightDir;
irisLDir.z = -cos(acos(-irisLDir.z)/1.5); // experiments showed it cuts out at 120 degrees, i.e. 1.33*the usual 90 degree cutoff
irisFLDir.z = -cos(acos(-irisFLDir.z)/1.5); // experiments showed it cuts out at 120 degrees, i.e. 1.33*the usual 90 degree cutoff
irisLDir = normalize(irisLDir);
irisFLDir = normalize(irisFLDir);
photonsL = smoothstep(0.0,1.0,dot(irisNorm,irisLDir)); // soften the terminator
photonsFL = (dot(irisNorm,irisFLDir)*.5+.5);
// dark caustic ring
photonsL *= .3+.7*smoothstep( 1.2, .9, length(ppos.xy/irisr+.2*irisLDir.xy/(irisLDir.z-.05)) );
// photonsFL *= ...;
}
}
else
{
// no photons
photonsL = max( 0.0, dot(irisNorm,localLightDir) );
photonsFL = .5+.5*dot(irisNorm,localLightDir);
}
vec3 l = ToLinear(lightColour)*photonsL;
vec3 fl = ToLinear(fillLightColour)*photonsFL;
vec3 ambientOcclusion = vec3(1);
vec3 eyelidShadow = vec3(1);
if ( !ReadKey( Key_A, true ) )
{
// ambient occlusion on fill light
ambientOcclusion = mix( vec3(1), ToLinear(vec3(.8,.7,.68)), pow(smoothstep( 5.0, 0.0, eyelidMask ),1.0) );
// shadow on actual light
eyelidShadow = mix( vec3(1), ToLinear(vec3(.8,.7,.68)), smoothstep( 2.0, -2.0, GetEyelidMask( worldPos+lightDir*1.0, eyeRotation ) ) );
}
fl *= ambientOcclusion;
l *= eyelidShadow;
iris *= l+fl;
// darken pupil
iris *= smoothstep( pupilr-.01, pupilr+.5, rad );
// veins
float theta = atan(pos.x,pos.y);
theta += Noise(pos*1.0)*tau*.03;
float veins = (sin(theta*60.0)*.5+.5);
veins *= veins;
veins *= (sin(theta*13.0)*.5+.5);
veins *= smoothstep( IrisAng, tau*.2, ang );
veins *= veins;
veins *= .5;
vec3 sclera = mix( ToLinear(vec3(1,.98,.96)), ToLinear(vec3(.9,.1,0)), veins );
float ndotl = dot(norm,lightDirN);
// subsurface scattering
// float subsurface = max(0.0,-2.0*ndotl*EyeRadius);
// l = pow(ToLinear(vec3(.5,.3,.25)),vec3(subsurface*.2)); // more intense the further light had to travel
// fake, because that^ approximation gives a hard terminator
l = pow(ToLinear(vec3(.5,.3,.25)), vec3(mix( 3.0, 0.0, smoothstep(-1.0,.2,ndotl) )) );
if ( ReadKey( Key_S, true ) )
// l = mix( l, vec3(max(0.0,ndotl)), 0.5 );
// else
l = vec3(max(0.0,ndotl));
l *= ToLinear(lightColour);
fl = ToLinear(fillLightColour)*(dot(norm,fillLightDirN)*.5+.5);
fl *= ambientOcclusion;
l *= eyelidShadow;
sclera *= l+fl;
// blend between them
float blend = smoothstep(-.1,.1,ang-IrisAng);
vec3 result = mix(iris,sclera,blend);
// eyelid ambient occlusion/radiosity
// if ( !ReadKey( Key_A, true ) )
//result *= mix( vec3(1), ToLinear(vec3(.65,.55,.55)), exp2(-eyelidMask*2.0) );
// result *= mix( vec3(1), ToLinear(vec3(.8,.7,.68)), pow(smoothstep( 5.0, 0.0, eyelidMask ),1.0) );
// bumps - in specular only to help sub-surface scattering look smooth
vec3 bumps;
bumps.xy = .7*Noise2( pos*3.0 );
bumps.z = sqrt(1.0-dot(bumps.xy,bumps.xy));
bumps = mix( vec3(0,0,1), bumps, blend );
norm.xy += bumps.xy*.1;
norm = normalize(norm);
float glossiness = mix(.7,1.0,bumps.z);
// reflection map
float ndoti = dot( view, norm );
vec3 rr = -view+2.0*ndoti*norm;
vec3 reflection = SkyDome( rr );
// specular
vec3 h = normalize(view+lightDir);
float specular = pow(max(0.0,dot(h,norm)),2000.0);
// should fresnel affect specular? or should it just be added?
reflection += specular*32.0*glossiness*ToLinear(lightColour);
// reflection of eyelids
//float eyelidReflection = smoothstep( 1.8, 2.0, eyelidMask );
// apply some parallax (subtle improvement when looking up/down at eye)
float eyelidReflection = smoothstep( .8, 1.0, GetEyelidMask( normalize(worldPos + rd*2.0)*EyeRadius, eyeRotation ) );
if ( !ReadKey( Key_O, true ) )
reflection *= eyelidReflection;
// fresnel
float fresnel = mix(.04*glossiness,1.0,pow(1.0-ndoti,5.0));
if ( !ReadKey( Key_R, true ) )
result = mix ( result, reflection, fresnel );
//anti-alias the edge
float mask2 = min( eyelidMask, (-worldPos.z-3.0) - (worldPos.x/tan(tau*.23)) );
result = mix( ToLinear(SkyColour), result, smoothstep(.0,.3,mask2) );
return result;
}
// Precision controls
const float epsilon = .003;
const float normalPrecision = .1;
const float shadowOffset = .1;
const int traceDepth = 100; // takes time
// ------- BACK-END CODE -------
vec2 Noise2( in vec3 x )
{
vec3 p = floor(x.xzy);
vec3 f = fract(x.xzy);
f = f*f*(3.0-2.0*f);
// vec3 f2 = f*f; f = f*f2*(10.0-15.0*f+6.0*f2);
vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
vec4 rg = textureLod( iChannel0, (uv+0.5)/256.0, 0.0 );
return mix( rg.yw, rg.xz, f.z );
}
float Noise( in vec3 x )
{
return Noise2(x).x;
}
float Trace( vec3 ro, vec3 rd, vec4 eyeRotation )
{
float t = 0.0;
float dist = 1.0;
for ( int i=0; i < traceDepth; i++ )
{
if ( abs(dist) < epsilon || t > drawDistance || t < 0.0 )
continue;
dist = Isosurface( ro+rd*t, eyeRotation );
t = t+dist;
}
return t;//vec4(ro+rd*t,dist);
}
// get normal
vec3 GetNormal( vec3 pos, vec4 eyeRotation )
{
const vec2 delta = vec2(normalPrecision, 0);
vec3 n;
// it's important this is centred on the pos, it fixes a lot of errors
n.x = Isosurface( pos + delta.xyy, eyeRotation ) - Isosurface( pos - delta.xyy, eyeRotation );
n.y = Isosurface( pos + delta.yxy, eyeRotation ) - Isosurface( pos - delta.yxy, eyeRotation );
n.z = Isosurface( pos + delta.yyx, eyeRotation ) - Isosurface( pos - delta.yyx, eyeRotation );
return normalize(n);
}
// camera function by TekF
// compute ray from camera parameters
vec3 GetRay( vec3 dir, float zoom, vec2 uv )
{
uv = uv - .5;
uv.x *= iResolution.x/iResolution.y;
dir = zoom*normalize(dir);
vec3 right = normalize(cross(vec3(0,1,0),dir));
vec3 up = normalize(cross(dir,right));
return dir + right*uv.x + up*uv.y;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 camPos = CamPos;
vec3 camLook = CamLook;
vec2 camRot = .5*tau*(iMouse.xy-iResolution.xy*.5)/iResolution.x;
if ( !ReadKey( Key_M, true ) )
camRot = vec2(0,0);
camPos.yz = cos(camRot.y)*camPos.yz + sin(camRot.y)*camPos.zy*vec2(1,-1);
camPos.xz = cos(camRot.x)*camPos.xz + sin(camRot.x)*camPos.zx*vec2(1,-1);
vec4 eyeRotation = ComputeEyeRotation();
if ( Isosurface(camPos, eyeRotation) <= 0.0 )
{
// camera inside ground
fragColor = vec4(0,0,0,0);
return;
}
vec3 ro = camPos;
vec3 rd;
rd = GetRay( camLook-camPos, CamZoom, uv );
ro += rd*(NearPlane/CamZoom);
rd = normalize(rd);
float t = Trace(ro,rd,eyeRotation);
vec3 result = ToLinear(SkyColour);
if ( t > 0.0 && t < drawDistance )
{
vec3 pos = ro+t*rd;
vec3 norm = GetNormal(pos,eyeRotation);
// shadow test
float shadow = 1.0;
if ( Trace( pos+lightDir*shadowOffset, lightDir, eyeRotation ) < drawDistance )
shadow = 0.0;
result = Shading( pos, norm, shadow, rd, eyeRotation );
// fog
// result = mix ( SkyColour, result, exp(-t*t*.0002) );
}
fragColor = vec4( ToGamma( result ), 1.0 );
}