· 4 years ago · Aug 19, 2021, 02:10 AM
1// Selfie Girl on Shadertoy
2// See: https://www.shadertoy.com/view/WsSBzh
3
4// =============================================================================================
5// Common
6
7// Basic utility functions (SDFs, noises, shaping functions)
8// and also the camera setup which is shared between the
9// background rendering code ("Buffer A" tab) and the character
10// rendering code ("Image" tab)
11
12
13
14// http://iquilezles.org/www/articles/smin/smin.htm
15float smin( float a, float b, float k )
16{
17 float h = max(k-abs(a-b),0.0);
18 return min(a, b) - h*h*0.25/k;
19}
20
21// http://iquilezles.org/www/articles/smin/smin.htm
22float smax( float a, float b, float k )
23{
24 k *= 1.4;
25 float h = max(k-abs(a-b),0.0);
26 return max(a, b) + h*h*h/(6.0*k*k);
27}
28
29// http://iquilezles.org/www/articles/smin/smin.htm
30float smin3( float a, float b, float k )
31{
32 k *= 1.4;
33 float h = max(k-abs(a-b),0.0);
34 return min(a, b) - h*h*h/(6.0*k*k);
35}
36
37// http://iquilezles.org/www/articles/smin/smin.htm
38float sclamp(in float x, in float a, in float b )
39{
40 float k = 0.1;
41 return smax(smin(x,b,k),a,k);
42}
43
44// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
45float opOnion( in float sdf, in float thickness )
46{
47 return abs(sdf)-thickness;
48}
49
50// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
51float opRepLim( in float p, in float s, in float lima, in float limb )
52{
53 return p-s*clamp(round(p/s),lima,limb);
54}
55
56float det( vec2 a, vec2 b ) { return a.x*b.y-b.x*a.y; }
57float ndot(vec2 a, vec2 b ) { return a.x*b.x-a.y*b.y; }
58float dot2( in vec2 v ) { return dot(v,v); }
59float dot2( in vec3 v ) { return dot(v,v); }
60
61// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
62float sdTorus( in vec3 p, in float ra, in float rb )
63{
64 return length( vec2(length(p.xz)-ra,p.y) )-rb;
65}
66
67// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
68float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb)
69{
70 p.x = abs(p.x);
71 float k = (sc.y*p.x>sc.x*p.z) ? dot(p.xz,sc) : length(p.xz);
72 return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
73}
74
75// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
76float sdSphere( in vec3 p, in float r )
77{
78 return length(p)-r;
79}
80
81// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
82float sdEllipsoid( in vec3 p, in vec3 r )
83{
84 float k0 = length(p/r);
85 float k1 = length(p/(r*r));
86 return k0*(k0-1.0)/k1;
87}
88
89// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
90float sdBox( in vec3 p, in vec3 b )
91{
92 vec3 d = abs(p) - b;
93 return min( max(max(d.x,d.y),d.z),0.0) + length(max(d,0.0));
94}
95
96// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
97float sdArc( in vec2 p, in vec2 scb, in float ra )
98{
99 p.x = abs(p.x);
100 float k = (scb.y*p.x>scb.x*p.y) ? dot(p.xy,scb) : length(p.xy);
101 return sqrt( dot(p,p) + ra*ra - 2.0*ra*k );
102}
103
104#if 1
105// http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
106// { dist, t, y (above the plane of the curve, x (away from curve in the plane of the curve))
107vec4 sdBezier( vec3 p, vec3 va, vec3 vb, vec3 vc )
108{
109 vec3 w = normalize( cross( vc-vb, va-vb ) );
110 vec3 u = normalize( vc-vb );
111 vec3 v = ( cross( w, u ) );
112 //----
113 vec2 m = vec2( dot(va-vb,u), dot(va-vb,v) );
114 vec2 n = vec2( dot(vc-vb,u), dot(vc-vb,v) );
115 vec3 q = vec3( dot( p-vb,u), dot( p-vb,v), dot(p-vb,w) );
116 //----
117 float mn = det(m,n);
118 float mq = det(m,q.xy);
119 float nq = det(n,q.xy);
120 //----
121 vec2 g = (nq+mq+mn)*n + (nq+mq-mn)*m;
122 float f = (nq-mq+mn)*(nq-mq+mn) + 4.0*mq*nq;
123 vec2 z = 0.5*f*vec2(-g.y,g.x)/dot(g,g);
124//float t = clamp(0.5+0.5*(det(z,m+n)+mq+nq)/mn, 0.0 ,1.0 );
125 float t = clamp(0.5+0.5*(det(z-q.xy,m+n))/mn, 0.0 ,1.0 );
126 vec2 cp = m*(1.0-t)*(1.0-t) + n*t*t - q.xy;
127 //----
128 float d2 = dot(cp,cp);
129 return vec4(sqrt(d2+q.z*q.z), t, q.z, -sign(f)*sqrt(d2) );
130}
131#else
132float det( vec3 a, vec3 b, in vec3 v ) { return dot(v,cross(a,b)); }
133
134// my adaptation to 3d of http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
135// { dist, t, y (above the plane of the curve, x (away from curve in the plane of the curve))
136vec4 sdBezier( vec3 p, vec3 b0, vec3 b1, vec3 b2 )
137{
138 b0 -= p;
139 b1 -= p;
140 b2 -= p;
141
142 vec3 d21 = b2-b1;
143 vec3 d10 = b1-b0;
144 vec3 d20 = (b2-b0)*0.5;
145
146 vec3 n = normalize(cross(d10,d21));
147
148 float a = det(b0,b2,n);
149 float b = det(b1,b0,n);
150 float d = det(b2,b1,n);
151 vec3 g = b*d21 + d*d10 + a*d20;
152 float f = a*a*0.25-b*d;
153
154 vec3 z = cross(b0,n) + f*g/dot(g,g);
155 float t = clamp( dot(z,d10-d20)/(a+b+d), 0.0 ,1.0 );
156 vec3 q = mix(mix(b0,b1,t), mix(b1,b2,t),t);
157
158 float k = dot(q,n);
159 return vec4(length(q),t,-k,-sign(f)*length(q-n*k));
160}
161#endif
162
163// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
164vec2 sdSegment(vec3 p, vec3 a, vec3 b)
165{
166 vec3 pa = p-a, ba = b-a;
167 float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
168 return vec2( length( pa - ba*h ), h );
169}
170
171// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
172vec2 sdSegmentOri(vec2 p, vec2 b)
173{
174 float h = clamp( dot(p,b)/dot(b,b), 0.0, 1.0 );
175 return vec2( length( p - b*h ), h );
176}
177
178// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
179float sdFakeRoundCone(vec3 p, float b, float r1, float r2)
180{
181 float h = clamp( p.y/b, 0.0, 1.0 );
182 p.y -= b*h;
183 return length(p) - mix(r1,r2,h);
184}
185
186// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
187float sdCone( in vec3 p, in vec2 c )
188{
189 vec2 q = vec2( length(p.xz), p.y );
190
191 vec2 a = q - c*clamp( (q.x*c.x+q.y*c.y)/dot(c,c), 0.0, 1.0 );
192 vec2 b = q - c*vec2( clamp( q.x/c.x, 0.0, 1.0 ), 1.0 );
193
194 float s = -sign( c.y );
195 vec2 d = min( vec2( dot( a, a ), s*(q.x*c.y-q.y*c.x) ),
196 vec2( dot( b, b ), s*(q.y-c.y) ));
197 return -sqrt(d.x)*sign(d.y);
198}
199
200// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
201float sdRhombus(vec3 p, float la, float lb, float h, float ra)
202{
203 p = abs(p);
204 vec2 b = vec2(la,lb);
205 float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 );
206 vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h);
207 return min(max(q.x,q.y),0.0) + length(max(q,0.0));
208}
209
210// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
211vec4 opElongate( in vec3 p, in vec3 h )
212{
213 vec3 q = abs(p)-h;
214 return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) );
215}
216
217//-----------------------------------------------
218
219// ray-infinite-cylinder intersection
220vec2 iCylinderY( in vec3 ro, in vec3 rd, in float rad )
221{
222 vec3 oc = ro;
223 float a = dot( rd.xz, rd.xz );
224 float b = dot( oc.xz, rd.xz );
225 float c = dot( oc.xz, oc.xz ) - rad*rad;
226 float h = b*b - a*c;
227 if( h<0.0 ) return vec2(-1.0);
228 h = sqrt(h);
229 return vec2(-b-h,-b+h)/a;
230}
231
232// ray-infinite-cone intersection
233vec2 iConeY(in vec3 ro, in vec3 rd, in float k )
234{
235 float a = dot(rd.xz,rd.xz) - k*rd.y*rd.y;
236 float b = dot(ro.xz,rd.xz) - k*ro.y*rd.y;
237 float c = dot(ro.xz,ro.xz) - k*ro.y*ro.y;
238
239 float h = b*b-a*c;
240 if( h<0.0 ) return vec2(-1.0);
241 h = sqrt(h);
242 return vec2(-b-h,-b+h)/a;
243}
244
245//-----------------------------------------------
246
247float linearstep(float a, float b, in float x )
248{
249 return clamp( (x-a)/(b-a), 0.0, 1.0 );
250}
251
252vec2 rot( in vec2 p, in float an )
253{
254 float cc = cos(an);
255 float ss = sin(an);
256 return mat2(cc,-ss,ss,cc)*p;
257}
258
259float expSustainedImpulse( float t, float f, float k )
260{
261 return smoothstep(0.0,f,t)*1.1 - 0.1*exp2(-k*max(t-f,0.0));
262}
263
264//-----------------------------------------------
265
266vec3 hash3( uint n )
267{
268 // integer hash copied from Hugo Elias
269 n = (n << 13U) ^ n;
270 n = n * (n * n * 15731U + 789221U) + 1376312589U;
271 uvec3 k = n * uvec3(n,n*16807U,n*48271U);
272 return vec3( k & uvec3(0x7fffffffU))/float(0x7fffffff);
273}
274
275//---------------------------------------
276
277float noise1( sampler3D tex, in vec3 x )
278{
279 return textureLod(tex,(x+0.5)/32.0,0.0).x;
280}
281float noise1( sampler2D tex, in vec2 x )
282{
283 return textureLod(tex,(x+0.5)/64.0,0.0).x;
284}
285float noise1f( sampler2D tex, in vec2 x )
286{
287 return texture(tex,(x+0.5)/64.0).x;
288}
289float fbm1( sampler3D tex, in vec3 x )
290{
291 float f = 0.0;
292 f += 0.5000*noise1(tex,x); x*=2.01;
293 f += 0.2500*noise1(tex,x); x*=2.01;
294 f += 0.1250*noise1(tex,x); x*=2.01;
295 f += 0.0625*noise1(tex,x);
296 f = 2.0*f-0.9375;
297 return f;
298}
299
300float fbm1( sampler2D tex, in vec2 x )
301{
302 float f = 0.0;
303 f += 0.5000*noise1(tex,x); x*=2.01;
304 f += 0.2500*noise1(tex,x); x*=2.01;
305 f += 0.1250*noise1(tex,x); x*=2.01;
306 f += 0.0625*noise1(tex,x);
307 f = 2.0*f-0.9375;
308 return f;
309}
310float fbm1f( sampler2D tex, in vec2 x )
311{
312 float f = 0.0;
313 f += 0.5000*noise1f(tex,x); x*=2.01;
314 f += 0.2500*noise1f(tex,x); x*=2.01;
315 f += 0.1250*noise1f(tex,x); x*=2.01;
316 f += 0.0625*noise1f(tex,x);
317 f = 2.0*f-0.9375;
318 return f;
319}
320float bnoise( in float x )
321{
322 float i = floor(x);
323 float f = fract(x);
324 float s = sign(fract(x/2.0)-0.5);
325 float k = 0.5+0.5*sin(i);
326 return s*f*(f-1.0)*((16.0*k-4.0)*f*(f-1.0)-1.0);
327}
328vec3 fbm13( in float x, in float g )
329{
330 vec3 n = vec3(0.0);
331 float s = 1.0;
332 for( int i=0; i<6; i++ )
333 {
334 n += s*vec3(bnoise(x),bnoise(x+13.314),bnoise(x+31.7211));
335 s *= g;
336 x *= 2.01;
337 x += 0.131;
338 }
339 return n;
340}
341
342//--------------------------------------------------
343//const float X1 = 1.6180339887498948; const float H1 = float( 1.0/X1 );
344//const float X2 = 1.3247179572447460; const vec2 H2 = vec2( 1.0/X2, 1.0/(X2*X2) );
345//const float X3 = 1.2207440846057595; const vec3 H3 = vec3( 1.0/X3, 1.0/(X3*X3), 1.0/(X3*X3*X3) );
346 const float X4 = 1.1673039782614187; const vec4 H4 = vec4( 1.0/X4, 1.0/(X4*X4), 1.0/(X4*X4*X4), 1.0/(X4*X4*X4*X4) );
347
348//--------------------------------------
349mat3 calcCamera( in float time, out vec3 oRo, out float oFl )
350{
351 vec3 ta = vec3( 0.0, -0.3, 0.0 );
352 vec3 ro = vec3( -0.5563, -0.2, 2.7442 );
353 float fl = 1.7;
354#if 0
355 vec3 fb = fbm13( 0.2*time, 0.5 );
356 ta += 0.025*fb;
357 float cr = -0.01 + 0.006*fb.z;
358#else
359 vec3 fb1 = fbm13( 0.15*time, 0.50 );
360 ro.xyz += 0.010*fb1.xyz;
361 vec3 fb2 = fbm13( 0.33*time, 0.65 );
362 fb2 = fb2*fb2*sign(fb2);
363 ta.xy += 0.005*fb2.xy;
364 float cr = -0.01 + 0.002*fb2.z;
365#endif
366
367 // camera matrix
368 vec3 ww = normalize( ta - ro );
369 vec3 uu = normalize( cross(ww,vec3(sin(cr),cos(cr),0.0) ) );
370 vec3 vv = ( cross(uu,ww));
371
372 oRo = ro;
373 oFl = fl;
374
375 return mat3(uu,vv,ww);
376}
377
378#define ZERO min(iFrame,0)
379#define ZEROU min(uint(iFrame),0u)
380
381// =============================================================================================
382// Buffer A
383
384// Renders the background (trees, ground, river and bridge).
385// The render uses a super basic implementation of Temporal
386// Antialiasing (TAA) without color clipping or anything,
387// but it's enough to stabilize aliasing. It also outputs
388// the deph buffer into the alpha channel for the next pass
389// ("Buffer B") to consume and do proper Depth Of Field.
390
391
392// The ground - it's a simple box deformed by a few sine waves
393//
394float sdGround( in vec3 pos )
395{
396 pos -= vec3(120.0,-35.0,-700.0);
397 pos.x += -150.0;
398 pos.z += 30.0*sin(1.00*pos.x*0.016+0.0);
399 pos.z += 10.0*sin(2.20*pos.x*0.016+1.0);
400 pos.y += 20.0*sin(0.01*pos.x+2.0)*sin(0.01*pos.z+2.0);
401
402 return sdBox(pos,vec3(1000.0,2.0,400.0))-10.0;
403}
404
405// The bridge. It's made of five boxes repeated forever
406// with some mod() call, which are distorted with gentle
407// sine waves so they don't look like perfectly geometrical.
408//
409vec2 sdBridge( in vec3 pos )
410{
411 float issnow = 0.0;
412 vec3 opos = pos;
413 pos.x += 50.0*sin(pos.z*0.01)+10.0;
414 pos.xz += 0.05*sin(pos.yx+vec2(0,2));
415 vec3 sos = vec3(abs(pos.x),pos.yz);
416 float h = -16.0;
417
418 // floor
419 vec3 ros = vec3(sos.xy,mod(sos.z+2.0,4.0)-2.0 )-vec3(0.0,h,0.0);
420 float d = sdBox(ros,vec3(20.0,1.0,1.85));
421
422 // thick bars
423 ros = vec3(sos.xy,mod(sos.z+5.0,10.0)-5.0 )-vec3(20.0,h+5.0-0.4,0.0);
424 float d2 = sdBox(ros,vec3(1.2,5.0,0.7)+0.1)-0.1;
425 d = min(d,d2);
426
427 #if 0
428 {
429 float id = floor((sos.z+5.0)/10.0);
430 ros = vec3(sos.xy,mod(sos.z+5.0,10.0)-5.0 )-vec3(20.0,h-0.4,0.0);
431 ros-=vec3(-1.5,1,0);
432 ros.x -= ros.y;
433 float ra = 0.5 + 0.5*sin(float(id)+4.0);
434 float d2 = sdEllipsoid(ros,vec3(2.0,2.0,1.3)*ra);
435 issnow = clamp( 0.5+0.5*(d-d2)/0.7, 0.0, 1.0 );
436 d = smin(d,d2,0.7);
437 }
438 #endif
439
440 // small bars
441 ros = vec3(sos.xy,mod(sos.z+1.25,2.5)-1.25 )-vec3(20.0,h+5.0,0.0);
442 d2 = sdBox(ros,vec3(0.2,5.0,0.2))-0.05;
443 d = min(d,d2);
444
445 // handle
446 d2 = sdBox(sos-vec3(20.0,h+10.0,0.0),vec3(0.5,0.1,300.0))-0.4;
447 d = min(d,d2);
448
449 // foot bar
450 d2 = sdBox(sos-vec3(20.0,h+2.4,0.0),vec3(0.7,0.1,300.0))-0.2;
451 d = min(d,d2);
452
453 return vec2(d,issnow);
454}
455
456// The trees are ultra basic and look really bad without
457// defocus, but all I needed was something that looked like
458// pine trees so the viewers would complete the picture in
459// their heads. Only four trees are evaluated at any time,
460// even though there are inifinte many of them. Yet these
461// four trees consume most of the rendering budget for the
462// painting.
463//
464vec3 sdForest( in vec3 pos, float tmin )
465{
466 float shid = 0.0;
467
468 const float per = 200.0;
469
470 pos -= vec3(120.0,-16.0,-600.0);
471
472 vec3 vos = pos/per;
473 vec3 ip = floor(vos);
474 vec3 fp = fract(vos);
475
476 bool hit = false;
477 float d = tmin;
478 float occ = 1.0;
479
480 for( int j=0; j<=1; j++ )
481 for( int i=0; i<=1; i++ )
482 {
483 vec2 of = vec2(i,j);
484 ivec2 tid = ivec2(ip.xz + of );
485 tid.y = min(tid.y,-0);
486
487 uint treeId = uint(tid.y)*17u+uint(tid.x)*1231u;
488
489 vec3 rf = hash3( uint(treeId) )-0.5;
490
491 vec3 ros = vec3( (float(tid.x)+rf.x)*per,
492 0.0,
493 (float(tid.y)+rf.y)*per );
494
495
496 float hei = 1.0 + 0.2*sin( float(tid.x*115+tid.y*221) );
497 hei *= (tid.y==0) ? 1.0 : 1.5;
498
499 hei *= 275.0;
500
501 float d2 = sdFakeRoundCone( pos-ros,hei,7.0,1.0);
502 if( d2<d)
503 {
504 d = d2;
505 hit = false;
506 }
507
508 if( d2-150.0>d ) continue;
509
510 vec2 qos = pos.xz - ros.xz;
511 float an = atan(qos.x,qos.y);
512 float ra = length(qos);
513 float vv = 0.3*sin(11.0*an) + 0.2*sin(28.0*an)+ 0.10*sin(53.0*an+4.0);
514
515
516 // trick - only evalute 4 closest of the 10 cones
517 int segid = int(floor(16.0*(pos.y-ros.y)/hei));
518 for( uint k=ZEROU; k<4u; k++ )
519 {
520 uint rk = uint( min(max(segid+int(k),5),15) );
521
522 float h = float(rk)/15.0;
523
524 vec3 ran = hash3( treeId*24u+rk );
525
526 h += 0.1*(1.0-h)*(ran.z-0.5) + 0.05*sin(1.0*an);
527
528 ros.y = h*hei;
529
530 float hh = 0.5 + 0.5*(1.0-h);
531 float ww = 0.1 + 0.9*(1.0-h);
532 hh *= 0.7+0.2*ran.x;
533 ww *= 0.9+0.2*ran.y;
534 hh *= 1.0+0.2*vv;
535
536 vec2 rrr = vec2( ra, pos.y-ros.y );
537 vec2 tmp = sdSegmentOri( rrr,vec2(120.0*ww,-100.0*hh));
538 float d2 = tmp.x-mix(1.0,vv,tmp.y);
539 if( d2<d )
540 {
541 hit = true;
542 d = d2;
543 shid = rf.z;
544 occ = tmp.y * clamp(ra/100.0+h,0.0,1.0);
545 }
546 }
547 }
548
549 if( hit )
550 {
551 float dis = 0.5+0.5*fbm1(iChannel0,0.1*pos*vec3(1,0.3,1));
552 d -= 8.0*dis-4.0;
553 //occ = dis;
554 }
555
556 return vec3(d,shid,occ);
557}
558
559
560// The SDF of the landscape is made by combining ground,
561// bridge, river and trees.
562//
563vec4 map( in vec3 pos, in float time, out float outMat, out vec3 uvw )
564{
565 pos.xz = rot(pos.xz,0.2);
566
567 vec4 res = vec4(pos.y+36.0,0,0,0);
568
569 outMat = 1.0;
570 uvw = pos;
571
572 //-------
573 {
574 vec2 d2 = sdBridge(pos);
575 if( d2.x<res.x )
576 {
577 res.xy = d2;
578 outMat = 2.0;
579 }
580 }
581 //-------
582 float d = sdGround(pos);
583 if( d<res.x )
584 {
585 res.x = d;
586 outMat = 4.0;
587 }
588 //-------
589 float bb = pos.z+450.0;
590 if( bb<d )
591 {
592 vec3 d2 = sdForest(pos,d);
593 if( d2.x<res.x )
594 {
595 res.x = d2.x;
596 res.y = d2.y;
597 res.z = d2.z;
598 outMat = 3.0;
599 }
600 }
601
602 return res;
603}
604
605// The landscape SDF again, but with extra high frequency
606// modeling detail. While the previous one is used for
607// raymarching and shadowing, this one is used for normal
608// computation. This separation is conceptually equivalent
609// to decoupling detail from base geometry with "normal
610// maps", but done in 3D and with SDFs, which is way simpler
611// and can be done correctly (something rarely seen in 3D
612// engines) without any complexity.
613//
614float mapD( in vec3 pos, in float time )
615{
616 float matID; vec3 kk2;
617 float d = map(pos,time,matID,kk2).x;
618
619 if( matID<1.5 ) // water
620 {
621 float g = 0.5 + 0.5*fbm1f(iChannel2,0.02*pos.xz);
622 g = g*g;
623 float f = 0.5 + 0.5*fbm1f(iChannel2,pos.xz);
624 d -= g*12.0*(0.5+0.5*f*g*2.0);
625 }
626 else if( matID<2.5 ) // bridge
627 {
628 d -= 0.07*(0.5+0.5*fbm1(iChannel0, pos*vec3(8,1,8) ));
629 }
630 else if( matID<4.5 ) // ground
631 {
632 float dis = fbm1(iChannel0,0.1*pos);
633 d -= 3.0*dis;
634 }
635
636 return d;
637}
638
639// Computes the normal of the girl's surface (the gradient
640// of the SDF). The implementation is weird because of the
641// technicalities of the WebGL API that forces us to do
642// some trick to prevent code unrolling. More info here:
643//
644// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm
645//
646vec3 calcNormal( in vec3 pos, in float time, in float t )
647{
648 float eps = 0.001*t;
649#if 0
650 vec2 e = vec2(1.0,-1.0)*0.5773;
651 return normalize( e.xyy*mapD( pos + e.xyy*eps,time ) +
652 e.yyx*mapD( pos + e.yyx*eps,time ) +
653 e.yxy*mapD( pos + e.yxy*eps,time ) +
654 e.xxx*mapD( pos + e.xxx*eps,time ) );
655#else
656 vec4 n = vec4(0.0);
657 for( int i=ZERO; i<4; i++ )
658 {
659 vec4 s = vec4(pos, 0.0);
660 s[i] += eps;
661 n[i] = mapD(s.xyz, time);
662 //if( n.x+n.y+n.z+n.w>100.0 ) break;
663 }
664 return normalize(n.xyz-n.w);
665#endif
666}
667
668// Compute soft shadows for a given light, with a single ray
669// insead of using montecarlo integration or shadowmap
670// blurring. More info here:
671//
672// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm
673//
674float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax, in float time, float k )
675{
676 float res = 1.0;
677 float t = mint;
678
679 // first things first - let's do a bounding volume test
680 float tm = (480.0-ro.y)/rd.y; if( tm>0.0 ) tmax=min(tmax,tm);
681
682 // raymarch and track penumbra
683 for( int i=ZERO; i<128; i++ )
684 {
685 float kk; vec3 kk2;
686 float h = map( ro + rd*t, time, kk, kk2 ).x;
687 res = min( res, k*h/t );
688 t += clamp( h, 0.05, 25.0 );
689 if( res<0.002 || t>tmax ) break;
690 }
691 return max( res, 0.0 );
692}
693
694// Computes convexity for our landscape SDF, which can be
695// used to approximate ambient occlusion. More info here:
696//
697// https://iquilezles.org/www/material/nvscene2008/rwwtt.pdf
698//
699float calcOcclusion( in vec3 pos, in vec3 nor, in float time, float sca, in vec2 px )
700{
701 float kk; vec3 kk2;
702 float ao = 0.0;
703 float off = textureLod(iChannel3,px/256.0,0.0).x;
704 vec4 k = vec4(0.7012912,0.3941462,0.8294585,0.109841)+off;
705 for( int i=ZERO; i<16; i++ )
706 {
707 k = fract(k + H4);
708 vec3 ap = normalize(-1.0+2.0*k.xyz);
709 float h = k.w*1.0*sca;
710 ap = (nor+ap)*h;
711 float d = map( pos+ap, time, kk, kk2 ).x;
712 ao += max(0.0,h-d);
713 if( ao>10000.0 ) break;
714 }
715 ao /= 16.0;
716 return clamp( 1.0-ao*2.0/sca, 0.0, 1.0 );
717}
718
719// Computes the intersection point between our landscape SDF
720// and a ray (coming form the camera in this case). It's a
721// traditional and uncomplicated SDF raymarcher. More info:
722//
723// https://iquilezles.org/www/material/nvscene2008/rwwtt.pdf
724//
725vec2 intersect( in vec3 ro, in vec3 rd, in float time, out vec3 cma, out vec3 uvw )
726{
727 cma = vec3(0.0);
728 uvw = vec3(0.0);
729 float matID = -1.0;
730
731 float tmax = 2500.0;
732 float t = 15.0;
733 // bounding volume test first
734 float tm = (480.0-ro.y)/rd.y; if( tm>0.0 ) tmax=min(tmax,tm);
735
736 // raymarch
737 for( int i=ZERO; i<1024; i++ )
738 {
739 vec3 pos = ro + t*rd;
740
741 float tmp;
742 vec4 h = map(pos,time,tmp,uvw);
743 if( (h.x)<0.0002*t )
744 {
745 cma = h.yzw;
746 matID = tmp;
747 break;
748 }
749 t += h.x*0.8;
750 if( t>tmax ) break;
751 }
752
753 return vec2(t,matID);
754}
755
756// Renders the landscape. It finds the ray-landscape
757// intersection point, computes the normal at the
758// intersection point, computes the ambient occlusion
759// approximation, does per material setup (color,
760// specularity, and paints some fake occlusion), and
761// finally does the lighting computation.
762//
763vec4 renderBackground( in vec2 p, in vec3 ro, in vec3 rd, in float time, in vec2 px )
764{
765 // sky color
766 vec3 col = vec3(0.45,0.75,1.1) + rd.y*0.5;
767 vec3 fogcol = vec3(0.3,0.5,1.0)*0.25;
768 col = mix( col, fogcol, exp2(-8.0*max(rd.y,0.0)) );
769
770 // -------------------------------
771 // find ray-landscape intersection
772 // -------------------------------
773 float tmin = 1e20;
774 vec3 cma, uvw;
775 vec2 tm = intersect( ro, rd, time, cma, uvw);
776
777 // --------------------------
778 // shading/lighting
779 // --------------------------
780 if( tm.y>0.0 )
781 {
782 tmin = tm.x;
783
784 vec3 pos = ro + tmin*rd;
785 vec3 nor = calcNormal(pos, time, tmin);
786
787 col = cma;
788
789 float ks = 1.0;
790 float se = 16.0;
791 float focc = 1.0;
792 float occs = 1.0;
793 float snow = 1.0;
794
795 // --------------------------
796 // materials
797 // --------------------------
798
799 // water
800 if( tm.y<1.5 )
801 {
802 col = vec3(0.1,0.2,0.3);
803 occs = 20.0;
804 }
805 // bridge
806 else if( tm.y<2.5 )
807 {
808 float f = 0.5 + 0.5*fbm1(iChannel0,pos*vec3(8,1,8));
809 ks = f*8.0;
810 se = 12.0;
811 col = mix(vec3(0.40,0.22,0.15)*0.63,
812 vec3(0.35,0.07,0.02)*0.2,f);
813 f = fbm1(iChannel0,pos*0.5);
814 col *= 1.0 + 1.1*f*vec3(0.5,1.0,1.5);
815 col *= 1.0 + 0.2*cos(cma.y*23.0+vec3(0,0.2,0.5));
816
817 float g = 0.5 + 0.5*fbm1(iChannel0,0.21*pos);
818 g -= 0.8*nor.x*nor.x;
819 snow *= smoothstep(0.2,0.6,g);
820 }
821 // forest
822 else if( tm.y<3.5 )
823 {
824 col = vec3(0.2,0.1,0.02)*0.7;
825 focc = cma.y*(0.7+0.3*nor.y);
826 occs = 100.0;
827 }
828 // ground
829 else if( tm.y<4.5 )
830 {
831 col = vec3(0.7,0.3,0.1)*0.12;
832 float d = smoothstep(1.0,6.0,pos.y-(-36.0));
833 col *= 0.2+0.8*d;
834 occs = 100.0;
835 snow = 1.0;
836 }
837
838 float fre = clamp(1.0+dot(nor,rd),0.0,1.0);
839 float occ = focc*calcOcclusion( pos, nor, time, occs, px );
840
841 snow *= smoothstep(0.25,0.3,nor.y);
842 if( abs(tm.y-2.0)<0.5 )
843 {
844 snow = max(snow,clamp(1.0-occ*occ*3.5,0.0,1.0));
845 snow = max(snow,cma.x);
846 }
847
848 col = mix( col, vec3(0.7,0.75,0.8)*0.6, snow);
849
850
851 // --------------------------
852 // lighting
853 // --------------------------
854 vec3 lin = vec3(0.0);
855
856 vec3 lig = normalize(vec3(0.5,0.4,0.6));
857 vec3 hal = normalize(lig-rd);
858 float dif = clamp(dot(nor,lig), 0.0, 1.0 );
859 //float sha = 0.0; if( dif>0.001 ) sha=calcSoftshadow( pos, lig, 0.001, 500.0, time, 8.0 );
860 float sha = calcSoftshadow( pos, lig, 0.001, 500.0, time, 8.0 );
861 dif *= sha;
862 float spe = ks*pow(clamp(dot(nor,hal),0.0,1.0),se)*dif*(0.04+0.96*pow(clamp(1.0+dot(hal,rd),0.0,1.0),5.0));
863 vec3 amb = occ*vec3(0.55+0.45*nor.y);
864
865 lin += col*vec3(0.4,0.7,1.1)*amb;
866 lin += col*1.4*vec3(2.3,1.5,1.1)*dif;
867 lin += spe*2.0;
868 lin += snow*vec3(0.21,0.35,0.7)*fre*fre*fre*(0.5+0.5*dif*amb)*focc;
869
870 #if 1
871 if( abs(tm.y-2.0)<0.5 )
872 {
873 float dif = max(0.2+0.8*dot(nor,vec3(-1,-0.3,0)),0.0);
874 lin += col*vec3(0.58,0.29,0.14)*dif;
875 }
876 #endif
877 col = lin;
878
879 col = mix( col, vec3(0.3,0.5,1.0)*0.25, 1.0-exp2(-0.0003*tmin) );
880 }
881
882 // sun flow
883 float glow = max(dot(rd,vec3(0.5,0.4,0.2)),0.0);
884 glow *= glow;
885 col += vec3(6.0,4.0,3.6)*glow*glow;
886
887 return vec4(col,tmin);
888}
889
890// The main rendering entry point. Basically it does some
891// setup or creating the ray that will explore the 3D
892// scene in search of the landscape for each pixel, does
893// the rendering of the landscape, and performs the
894// Temporal Antialiasing before spiting out the color (in
895// linear space, not gama) and the deph of the scene.
896//
897void mainImage( out vec4 fragColor, in vec2 fragCoord )
898{
899 // render
900 vec2 o = hash3( uint(iFrame) ).xy - 0.5;
901 vec2 p = (2.0*(fragCoord+o)-iResolution.xy)/iResolution.y;
902
903 float time = 2.0 + iTime;
904
905 // skip pixels behind girl
906 #if 1
907 if( length((p-vec2(-0.56, 0.2))/vec2(0.78,1.0))<0.85 ||
908 length((p-vec2(-0.56,-0.4))/vec2(1.00,1.0))<0.73)
909 {
910 fragColor = vec4( 0.55,0.55,0.65,1e20 ); return;
911 }
912 #endif
913
914 // camera movement
915 vec3 ro; float fl;
916 mat3 ca = calcCamera( time, ro, fl );
917 vec3 rd = ca * normalize( vec3((p-vec2(-0.52,0.12))/1.1,fl));
918
919 vec4 tmp = renderBackground(p,ro,rd,time,fragCoord);
920 vec3 col = tmp.xyz;
921
922 //---------------------------------------------------------------
923 // reproject from previous frame and average (cheap TAA, kind of)
924 //---------------------------------------------------------------
925
926 mat4 oldCam = mat4( textureLod(iChannel1,vec2(0.5,0.5)/iResolution.xy, 0.0),
927 textureLod(iChannel1,vec2(1.5,0.5)/iResolution.xy, 0.0),
928 textureLod(iChannel1,vec2(2.5,0.5)/iResolution.xy, 0.0),
929 0.0, 0.0, 0.0, 1.0 );
930 bool oldStarted = textureLod(iChannel1,vec2(3.5,0.5)/iResolution.xy, 0.0).x>0.5;
931
932 // world space
933 vec4 wpos = vec4(ro + rd*tmp.w,1.0);
934 // camera space
935 vec3 cpos = (wpos*oldCam).xyz; // note inverse multiply
936 // ndc space
937 vec2 npos = fl * cpos.xy / cpos.z;
938 // undo composition hack
939 npos = npos*1.1+vec2(-0.52,0.12);
940 // screen space
941 vec2 spos = 0.5 + 0.5*npos*vec2(iResolution.y/iResolution.x,1.0);
942 // undo dither
943 spos -= o/iResolution.xy;
944 // raster space
945 vec2 rpos = spos * iResolution.xy;
946
947 if( rpos.y<1.0 && rpos.x<4.0 )
948 {
949 }
950 else
951 {
952 vec3 ocol = textureLod( iChannel1, spos, 0.0 ).xyz;
953 if( !oldStarted ) ocol = col;
954 col = mix( ocol, col, 0.1 );
955 }
956
957 //----------------------------------
958 bool started = textureSize(iChannel0,0).x>=2 &&
959 textureSize(iChannel2,0).x>=2 &&
960 textureSize(iChannel3,0).x>=2;
961
962 if( fragCoord.y<1.0 && fragCoord.x<4.0 )
963 {
964 if( abs(fragCoord.x-3.5)<0.5 ) fragColor = vec4( started?1.0:0.0, 0.0, 0.0, 0.0 );
965 if( abs(fragCoord.x-2.5)<0.5 ) fragColor = vec4( ca[2], -dot(ca[2],ro) );
966 if( abs(fragCoord.x-1.5)<0.5 ) fragColor = vec4( ca[1], -dot(ca[1],ro) );
967 if( abs(fragCoord.x-0.5)<0.5 ) fragColor = vec4( ca[0], -dot(ca[0],ro) );
968 }
969 else
970 {
971 fragColor = vec4( col, tmp.w );
972 }
973
974 if( !started ) fragColor = vec4(0.0);
975}
976
977
978
979// =============================================================================================
980// Buffer B
981
982// Depth of Field (depth defocus) on the background. It's a basic
983// gather approach, where each pixel's neighborhood gets scanned
984// and the Circle of Confusion computed for each one of those
985// neighbor pixels. If the distance to the pixel is smaller than
986// the Circle of Confusion, the current pixel gets a contribution
987// from it with a weight that is inversely proportional to the
988// area of the Circle of Confusion, to conserve energy.
989//
990void mainImage( out vec4 fragColor, in vec2 fragCoord )
991{
992 vec4 ref = texelFetch( iChannel0, ivec2(fragCoord),0);
993
994 vec2 q = fragCoord/iResolution.xy;
995
996 vec4 acc = vec4(0.0);
997 const int N = 9;
998 for( int j=-N; j<=N; j++ )
999 for( int i=-N; i<=N; i++ )
1000 {
1001 vec2 off = vec2(float(i),float(j));
1002
1003 vec4 tmp = texture( iChannel0, q + off/vec2(1280.0,720.0) );
1004
1005 float coc = 0.01 + 9.0*(1.0-1.0/(1.0+0.01*abs(tmp.w)));
1006
1007 if( dot(off,off) < coc*coc )
1008 {
1009 float w = 1.0/(coc*coc);
1010 acc += vec4(tmp.xyz*w,w);
1011 }
1012 }
1013 vec3 col = acc.xyz / acc.w;
1014
1015 fragColor = vec4(col,ref.w);
1016}
1017
1018
1019// =============================================================================================
1020// Image
1021
1022// Created by inigo quilez - iq/2020
1023// I share this piece (art and code) here in Shadertoy and through its Public API, only for educational purposes.
1024// You cannot use, sell, share or host this piece or modifications of it as part of your own commercial or non-commercial product, website or project.
1025// You can share a link to it or an unmodified screenshot of it provided you attribute "by Inigo Quilez, @iquilezles and iquilezles.org".
1026// If you are a teacher, lecturer, educator or similar and these conditions are too restrictive for your needs, please contact me and we'll work it out.
1027
1028// Source code of the mathematical painting "Selfie Girl".
1029// Making-of video on Youtube:
1030//
1031// https://www.youtube.com/watch?v=8--5LwHRhjk
1032
1033// The image is a single formula, but I had to split it
1034// down into 3 passes here so it could be shared without
1035// breaking the WebGL implementation of the web browsers
1036// (which is what Shadertoy uses to run the code below
1037// that implements the formula).
1038
1039// This "Image" tab in particular renders the girl through
1040// raymarching and then performs the final composition with
1041// the background, which is computed in "Buffer B" (open
1042// the rest of the tabs to see explanations of what each
1043// one does). For the rendering/computer graphics people -
1044// there's no TAA in this pass because I didn't want to
1045// compute velocity vectors for the animation, so things
1046// alias a bit (feel free to change the AA define below to
1047// 2 if you have a fast GPU)
1048
1049#define AA 1
1050
1051
1052// This SDF is really 6 braids at once (through domain
1053// repetition) with three strands each (brute forced)
1054vec4 sdHair( vec3 p, vec3 pa, vec3 pb, vec3 pc, float an, out vec2 occ_id)
1055{
1056 vec4 b = sdBezier(p, pa,pb,pc );
1057 vec2 q = rot(b.zw,an);
1058
1059 vec2 id2 = round(q/0.1);
1060 id2 = clamp(id2,vec2(0),vec2(2,1));
1061 q -= 0.1*id2;
1062
1063 float id = 11.0*id2.x + id2.y*13.0;
1064
1065 q += smoothstep(0.5,0.8,b.y)*0.02*vec2(0.4,1.5)*cos( 23.0*b.y + id*vec2(13,17));
1066
1067 occ_id.x = clamp(length(q)*8.0-0.2,0.0,1.0);
1068 vec4 res = vec4(99,q,b.y);
1069 for( int i=0; i<3; i++ )
1070 {
1071 vec2 tmp = q + 0.01*cos( id + 180.0*b.y + vec2(2*i,6-2*i));
1072 float lt = length(tmp)-0.02;
1073 if( lt<res.x )
1074 {
1075 occ_id.y = id+float(i);
1076 res.x = lt;
1077 res.yz = tmp;
1078 }
1079 }
1080 return res;
1081}
1082
1083// The SDF for the hoodie and jacket. It's a very distorted
1084// ellipsoid, torus section, a segment and a sphere.
1085vec4 sdHoodie( in vec3 pos )
1086{
1087 vec3 opos = pos;
1088
1089 pos.x += 0.09*sin(3.5*pos.y-0.5)*sin( pos.z) + 0.015;
1090 pos.xyz += 0.03*sin(2.0*pos.y)*sin(7.0*pos.zyx);
1091
1092 // hoodie
1093 vec3 hos = pos-vec3(0.0,-0.33,0.15);
1094 hos.x -= 0.031*smoothstep(0.0,1.0,opos.y+0.33);
1095 hos.yz = rot(hos.yz,0.9);
1096 float d1 = sdEllipsoid(hos,vec3(0.96-pos.y*0.1,1.23,1.5));
1097 float d2 = 0.95*pos.z-0.312*pos.y-0.9;
1098 float d = max(opOnion(d1,0.01), d2 );
1099
1100 // shoulders
1101 vec3 sos = vec3( abs(pos.x), pos.yz );
1102 vec2 se = sdSegment(sos, vec3(0.18,-1.6,-0.3), vec3(1.1,-1.9,0.0) );
1103 d = smin(d,se.x-mix(0.25,0.43,se.y),0.4);
1104 d = smin(d,sdSphere(sos-vec3(0.3,-2.2,0.4), 0.5 ),0.2);
1105
1106 // neck
1107 opos.x -= 0.02*sin(9.0*opos.y);
1108 vec4 w = opElongate( opos-vec3(0.0,-1.2,0.3), vec3(0.0,0.3,0.0) );
1109 d = smin(d,
1110 w.w+sdCappedTorus(vec3(w.xy,-w.z),vec2(0.6,-0.8),0.6,0.02),
1111 0.1);
1112
1113 // bumps
1114 d += 0.004*sin(pos.x*90.0)*sin(pos.y*90.0)*sin(pos.z*90.0);
1115 d -= 0.002*sin(pos.x*300.0);
1116 d -= 0.02*(1.0-smoothstep(0.0,0.04,abs(opOnion(pos.x,1.1))));
1117
1118 // border
1119 d = min(d,length(vec2(d1,d2))-0.015);
1120
1121 return vec4(d,pos);
1122}
1123
1124// moves the head (and hair and hoodie). This could be done
1125// more efficiently (with a single matrix or quaternion),
1126// but this code was optimized for editing, not for runtime
1127vec3 moveHead( in vec3 pos, in vec3 an, in float amount)
1128{
1129 pos.y -= -1.0;
1130 pos.xz = rot(pos.xz,amount*an.x);
1131 pos.xy = rot(pos.xy,amount*an.y);
1132 pos.yz = rot(pos.yz,amount*an.z);
1133 pos.y += -1.0;
1134 return pos;
1135}
1136
1137// the animation state
1138vec3 animData; // { blink, nose follow up, mouth }
1139vec3 animHead; // { head rotation angles }
1140
1141// SDF of the girl. It is not as efficient as it should,
1142// both in terms of performance and euclideanness of the
1143// returned distance. Among other things I tweaked the
1144// overal shape of the head though scaling right in the
1145// middle of the design process (see 1.02 and 1.04 numbers
1146// below). I should have backpropagated those adjustements
1147// to the primitives themselves, but I didn't and now it's
1148// too late. So, I am paying some cost there.
1149//
1150// She is modeled to camera (her face's shape looks bad
1151// from other perspectives. She's made of five ellipsoids
1152// blended together for the face, a cone and three spheres
1153// for the nose, a torus for the teeh and two quadratic
1154// curves for the lips. The neck is a cylinder, the hair
1155// is made of three quadratic curves that are repeated
1156// multiple times through domain repetition and each of
1157// them contains three more curves in order to make the
1158// braids. The hoodie is an ellipsoid deformed with
1159// two sine waves and cut in half, the neck is an elongated
1160// torus section and the shoulders are capsules.
1161//
1162vec4 map( in vec3 pos, in float time, out float outMat, out vec3 uvw )
1163{
1164 outMat = 1.0;
1165
1166 vec3 oriPos = pos;
1167
1168 // head deformation and transformation
1169 pos.y /= 1.04;
1170 vec3 opos;
1171 opos = moveHead( pos, animHead, smoothstep(-1.2, 0.2,pos.y) );
1172 pos = moveHead( pos, animHead, smoothstep(-1.4,-1.0,pos.y) );
1173 pos.x *= 1.04;
1174 pos.y /= 1.02;
1175 uvw = pos;
1176
1177 // symmetric coord systems (sharp, and smooth)
1178 vec3 qos = vec3(abs(pos.x),pos.yz);
1179 vec3 sos = vec3(sqrt(qos.x*qos.x+0.0005),pos.yz);
1180
1181 // head
1182 float d = sdEllipsoid( pos-vec3(0.0,0.05,0.07), vec3(0.8,0.75,0.85) );
1183
1184 // jaw
1185 vec3 mos = pos-vec3(0.0,-0.38,0.35); mos.yz = rot(mos.yz,0.4);
1186 mos.yz = rot(mos.yz,0.1*animData.z);
1187 float d2 = sdEllipsoid(mos-vec3(0,-0.17,0.16),
1188 vec3(0.66+sclamp(mos.y*0.9-0.1*mos.z,-0.3,0.4),
1189 0.43+sclamp(mos.y*0.5,-0.5,0.2),
1190 0.50+sclamp(mos.y*0.3,-0.45,0.5)));
1191
1192 // mouth hole
1193 d2 = smax(d2,-sdEllipsoid(mos-vec3(0,0.06,0.6+0.05*animData.z), vec3(0.16,0.035+0.05*animData.z,0.1)),0.01);
1194
1195 // lower lip
1196 vec4 b = sdBezier(vec3(abs(mos.x),mos.yz),
1197 vec3(0,0.01,0.61),
1198 vec3(0.094+0.01*animData.z,0.015,0.61),
1199 vec3(0.18-0.02*animData.z,0.06+animData.z*0.05,0.57-0.006*animData.z));
1200 float isLip = smoothstep(0.045,0.04,b.x+b.y*0.03);
1201 d2 = smin(d2,b.x - 0.027*(1.0-b.y*b.y)*smoothstep(1.0,0.4,b.y),0.02);
1202 d = smin(d,d2,0.19);
1203
1204 // chicks
1205 d = smin(d,sdSphere(qos-vec3(0.2,-0.33,0.62),0.28 ),0.04);
1206
1207 // who needs ears
1208
1209 // eye sockets
1210 vec3 eos = sos-vec3(0.3,-0.04,0.7);
1211 eos.xz = rot(eos.xz,-0.2);
1212 eos.xy = rot(eos.xy,0.3);
1213 eos.yz = rot(eos.yz,-0.2);
1214 d2 = sdEllipsoid( eos-vec3(-0.05,-0.05,0.2), vec3(0.20,0.14-0.06*animData.x,0.1) );
1215 d = smax( d, -d2, 0.15 );
1216
1217 eos = sos-vec3(0.32,-0.08,0.8);
1218 eos.xz = rot(eos.xz,-0.4);
1219 d2 = sdEllipsoid( eos, vec3(0.154,0.11,0.1) );
1220 d = smax( d, -d2, 0.05 );
1221
1222 vec3 oos = qos - vec3(0.25,-0.06,0.42);
1223
1224 // eyelid
1225 d2 = sdSphere( oos, 0.4 );
1226 oos.xz = rot(oos.xz, -0.2);
1227 oos.xy = rot(oos.xy, 0.2);
1228 vec3 tos = oos;
1229 oos.yz = rot(oos.yz,-0.6+0.58*animData.x);
1230
1231 //eyebags
1232 tos = tos-vec3(-0.02,0.06,0.2+0.02*animData.x);
1233 tos.yz = rot(tos.yz,0.8);
1234 tos.xy = rot(tos.xy,-0.2);
1235 d = smin( d, sdTorus(tos,0.29,0.01), 0.03 );
1236
1237 // eyelids
1238 eos = qos - vec3(0.33,-0.07,0.53);
1239 eos.xy = rot(eos.xy, 0.2);
1240 eos.yz = rot(eos.yz,0.35-0.25*animData.x);
1241 d2 = smax(d2-0.005, -max(oos.y+0.098,-eos.y-0.025), 0.02 );
1242 d = smin( d, d2, 0.012 );
1243
1244 // eyelashes
1245 oos.x -= 0.01;
1246 float xx = clamp( oos.x+0.17,0.0,1.0);
1247 float ra = 0.35 + 0.1*sqrt(xx/0.2)*(1.0-smoothstep(0.3,0.4,xx))*(0.6+0.4*sin(xx*256.0));
1248 float rc = 0.18/(1.0-0.7*smoothstep(0.15,0.5,animData.x));
1249 oos.y -= -0.18 - (rc-0.18)/1.8;
1250 d2 = (1.0/1.8)*sdArc( oos.xy*vec2(1.0,1.8), vec2(0.9,sqrt(1.0-0.9*0.9)), rc )-0.001;
1251 float deyelashes = max(d2,length(oos.xz)-ra)-0.003;
1252
1253 // nose
1254 eos = pos-vec3(0.0,-0.079+animData.y*0.005,0.86);
1255 eos.yz = rot(eos.yz,-0.23);
1256 float h = smoothstep(0.0,0.26,-eos.y);
1257 d2 = sdCone( eos-vec3(0.0,-0.02,0.0), vec2(0.03,-0.25) )-0.04*h-0.01;
1258 eos.x = sqrt(eos.x*eos.x + 0.001);
1259 d2 = smin( d2, sdSphere(eos-vec3(0.0, -0.25,0.037),0.06 ), 0.07 );
1260 d2 = smin( d2, sdSphere(eos-vec3(0.1, -0.27,0.03 ),0.04 ), 0.07 );
1261 d2 = smin( d2, sdSphere(eos-vec3(0.0, -0.32,0.05 ),0.025), 0.04 );
1262 d2 = smax( d2,-sdSphere(eos-vec3(0.07,-0.31,0.038),0.02 ), 0.035 );
1263 d = smin(d,d2,0.05-0.03*h);
1264
1265 // mouth
1266 eos = pos-vec3(0.0,-0.38+animData.y*0.003+0.01*animData.z,0.71);
1267 tos = eos-vec3(0.0,-0.13,0.06);
1268 tos.yz = rot(tos.yz,0.2);
1269 float dTeeth = sdTorus(tos,0.15,0.015);
1270 eos.yz = rot(eos.yz,-0.5);
1271 eos.x /= 1.04;
1272
1273 // nose-to-upperlip connection
1274 d2 = sdCone( eos-vec3(0,0,0.03), vec2(0.14,-0.2) )-0.03;
1275 d2 = max(d2,-(eos.z-0.03));
1276 d = smin(d,d2,0.05);
1277
1278 // upper lip
1279 eos.x = abs(eos.x);
1280 b = sdBezier(eos, vec3(0.00,-0.22,0.17),
1281 vec3(0.08,-0.22,0.17),
1282 vec3(0.17-0.02*animData.z,-0.24-0.01*animData.z,0.08));
1283 d2 = length(b.zw/vec2(0.5,1.0)) - 0.03*clamp(1.0-b.y*b.y,0.0,1.0);
1284 d = smin(d,d2,0.02);
1285 isLip = max(isLip,(smoothstep(0.03,0.005,abs(b.z+0.015+abs(eos.x)*0.04))
1286 -smoothstep(0.45,0.47,eos.x-eos.y*1.15)));
1287
1288 // valley under nose
1289 vec2 se = sdSegment(pos, vec3(0.0,-0.45,1.01), vec3(0.0,-0.47,1.09) );
1290 d2 = se.x-0.03-0.06*se.y;
1291 d = smax(d,-d2,0.04);
1292 isLip *= smoothstep(0.01,0.03,d2);
1293
1294 // neck
1295 se = sdSegment(pos, vec3(0.0,-0.65,0.0), vec3(0.0,-1.7,-0.1) );
1296 d2 = se.x - 0.38;
1297
1298 // shoulders
1299 se = sdSegment(sos, vec3(0.0,-1.55,0.0), vec3(0.6,-1.65,0.0) );
1300 d2 = smin(d2,se.x-0.21,0.1);
1301 d = smin(d,d2,0.4);
1302
1303 // register eyelases now
1304 vec4 res = vec4( d, isLip, 0, 0 );
1305 if( deyelashes<res.x )
1306 {
1307 res.x = deyelashes*0.8;
1308 res.yzw = vec3(0.0,1.0,0.0);
1309 }
1310 // register teeth now
1311 if( dTeeth<res.x )
1312 {
1313 res.x = dTeeth;
1314 outMat = 5.0;
1315 }
1316
1317 // eyes
1318 pos.x /=1.05;
1319 eos = qos-vec3(0.25,-0.06,0.42);
1320 d2 = sdSphere(eos,0.4);
1321 if( d2<res.x )
1322 {
1323 res.x = d2;
1324 outMat = 2.0;
1325 uvw = pos;
1326 }
1327
1328 // hair
1329 {
1330 vec2 occ_id, tmp;
1331 qos = pos; qos.x=abs(pos.x);
1332
1333 vec4 pres = sdHair(pos,vec3(-0.3, 0.55,0.8),
1334 vec3( 0.95, 0.7,0.85),
1335 vec3( 0.4,-1.45,0.95),
1336 -0.9,occ_id);
1337
1338 vec4 pres2 = sdHair(pos,vec3(-0.4, 0.6,0.55),
1339 vec3(-1.0, 0.4,0.2),
1340 vec3(-0.6,-1.4,0.7),
1341 0.6,tmp);
1342 if( pres2.x<pres.x ) { pres=pres2; occ_id=tmp; occ_id.y+=40.0;}
1343
1344 pres2 = sdHair(qos,vec3( 0.4, 0.7,0.4),
1345 vec3( 1.0, 0.5,0.45),
1346 vec3( 0.4,-1.45,0.55),
1347 -0.2,tmp);
1348 if( pres2.x<pres.x ) { pres=pres2; occ_id=tmp; occ_id.y+=80.0;}
1349
1350
1351 pres.x *= 0.8;
1352 if( pres.x<res.x )
1353 {
1354 res = vec4( pres.x, occ_id.y, 0.0, occ_id.x );
1355 uvw = pres.yzw;
1356 outMat = 4.0;
1357 }
1358 }
1359
1360 // hoodie
1361 vec4 tmp = sdHoodie( opos );
1362 if( tmp.x<res.x )
1363 {
1364 res.x = tmp.x;
1365 outMat = 3.0;
1366 uvw = tmp.yzw;
1367 }
1368
1369 return res;
1370}
1371
1372// SDF of the girl again, but with extra high frequency
1373// modeling detail. While the previous one is used for
1374// raymarching and shadowing, this one is used for normal
1375// computation. This separation is conceptually equivalent
1376// to decoupling detail from base geometry with "normal
1377// maps", but done in 3D and with SDFs, which is way
1378// simpler and can be done corretly (something rarely seen
1379// in 3D engines) without any complexity.
1380vec4 mapD( in vec3 pos, in float time )
1381{
1382 float matID;
1383 vec3 uvw;
1384 vec4 h = map(pos, time, matID, uvw);
1385
1386 if( matID<1.5 ) // skin
1387 {
1388 // pores
1389 float d = fbm1(iChannel0,120.0*uvw);
1390 h.x += 0.0015*d*d;
1391 }
1392 else if( matID>3.5 && matID<4.5 ) // hair
1393 {
1394 // some random displacement to evoke hairs
1395 float te = texture( iChannel2,vec2( 0.25*atan(uvw.x,uvw.y),8.0*uvw.z) ).x;
1396 h.x -= 0.02*te;
1397 }
1398 return h;
1399}
1400
1401// Computes the normal of the girl's surface (the gradient
1402// of the SDF). The implementation is weird because of the
1403// technicalities of the WebGL API that forces us to do
1404// some trick to prevent code unrolling. More info here:
1405//
1406// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm
1407//
1408vec3 calcNormal( in vec3 pos, in float time )
1409{
1410 const float eps = 0.001;
1411#if 0
1412 vec2 e = vec2(1.0,-1.0)*0.5773;
1413 return normalize( e.xyy*map( pos + e.xyy*eps,time,kk ).x +
1414 e.yyx*map( pos + e.yyx*eps,time,kk ).x +
1415 e.yxy*map( pos + e.yxy*eps,time,kk ).x +
1416 e.xxx*map( pos + e.xxx*eps,time,kk ).x );
1417#else
1418 vec4 n = vec4(0.0);
1419 for( int i=ZERO; i<4; i++ )
1420 {
1421 vec4 s = vec4(pos, 0.0);
1422 float kk; vec3 kk2;
1423 s[i] += eps;
1424 n[i] = mapD(s.xyz, time).x;
1425 //if( n.x+n.y+n.z+n.w>100.0 ) break;
1426 }
1427 return normalize(n.xyz-n.w);
1428#endif
1429}
1430
1431// Compute soft shadows for a given light, with a single
1432// ray insead of using montecarlo integration or shadowmap
1433// blurring. More info here:
1434//
1435// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm
1436//
1437float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax, in float time, float k )
1438{
1439 // first things first - let's do a bounding volume test
1440 vec2 sph = iCylinderY( ro, rd, 1.5 );
1441 //vec2 sph = iConeY(ro-vec3(-0.05,3.7,0.35),rd,0.08);
1442 tmax = min(tmax,sph.y);
1443
1444 // raymarch and track penumbra
1445 float res = 1.0;
1446 float t = mint;
1447 for( int i=0; i<128; i++ )
1448 {
1449 float kk; vec3 kk2;
1450 float h = map( ro + rd*t, time, kk, kk2 ).x;
1451 res = min( res, k*h/t );
1452 t += clamp( h, 0.005, 0.1 );
1453 if( res<0.002 || t>tmax ) break;
1454 }
1455 return max( res, 0.0 );
1456}
1457
1458// Computes convexity for our girl SDF, which can be used
1459// to approximate ambient occlusion. More info here:
1460//
1461// https://iquilezles.org/www/material/nvscene2008/rwwtt.pdf
1462//
1463float calcOcclusion( in vec3 pos, in vec3 nor, in float time )
1464{
1465 float kk; vec3 kk2;
1466 float ao = 0.0;
1467 float off = textureLod(iChannel3,gl_FragCoord.xy/256.0,0.0).x;
1468 vec4 k = vec4(0.7012912,0.3941462,0.8294585,0.109841)+off;
1469 for( int i=ZERO; i<16; i++ )
1470 {
1471 k = fract(k + H4);
1472 vec3 ap = normalize(-1.0+2.0*k.xyz);
1473 float h = k.w*0.1;
1474 ap = (nor+ap)*h;
1475 float d = map( pos+ap, time, kk, kk2 ).x;
1476 ao += max(0.0,h-d);
1477 if( ao>16.0 ) break;
1478 }
1479 ao /= 16.0;
1480 return clamp( 1.0-ao*24.0, 0.0, 1.0 );
1481}
1482
1483// Computes the intersection point between our girl SDF and
1484// a ray (coming form the camera in this case). It's a
1485// traditional and basic/uncomplicated SDF raymarcher. More
1486// info here:
1487//
1488// https://iquilezles.org/www/material/nvscene2008/rwwtt.pdf
1489//
1490vec2 intersect( in vec3 ro, in vec3 rd, in float tmax, in float time, out vec3 cma, out vec3 uvw )
1491{
1492 cma = vec3(0.0);
1493 uvw = vec3(0.0);
1494 float matID = -1.0;
1495
1496 float t = 1.0;
1497
1498 // bounding volume test first
1499 vec2 sph = iCylinderY( ro, rd, 1.5 );
1500 //vec2 sph = iConeY(ro-vec3(-0.05,3.7,0.35),rd,0.08);
1501 if( sph.y<0.0 ) return vec2(-1.0);
1502
1503 // clip raymarch space to bonding volume
1504 tmax = min(tmax,sph.y);
1505 t = max(1.0, sph.x);
1506
1507 // raymarch
1508 for( int i=0; i<256; i++ )
1509 {
1510 vec3 pos = ro + t*rd;
1511
1512 float tmp;
1513 vec4 h = map(pos,time,tmp,uvw);
1514 if( h.x<0.001 )
1515 {
1516 cma = h.yzw;
1517 matID = tmp;
1518 break;
1519 }
1520 t += h.x*0.95;
1521 if( t>tmax ) break;
1522 }
1523
1524 return vec2(t,matID);
1525}
1526
1527// This is a replacement for a traditional dot(N,L) diffuse
1528// lobe (called N.L in the code) that fakes some subsurface
1529// scattering (transmision of light thorugh the skin that
1530// surfaces as a red glow)
1531//
1532vec3 sdif( float ndl, float ir )
1533{
1534 float pndl = clamp( ndl, 0.0, 1.0 );
1535 float nndl = clamp(-ndl, 0.0, 1.0 );
1536 return vec3(pndl) + vec3(1.0,0.1,0.01)*0.7*pow(clamp(ir*0.75-nndl,0.0,1.0),2.0);
1537}
1538
1539// Animates the eye central position (not the actual random
1540// darts). It's carefuly synched with the head motion, to
1541// make the eyes anticipate the head turn (without this
1542// anticipation, the eyes and the head are disconnected and
1543// it all looks like a zombie/animatronic)
1544//
1545float animEye( in float time )
1546{
1547 const float w = 6.1;
1548 float t = mod(time-0.31,w*1.0);
1549
1550 float q = fract((time-0.31)/(2.0*w));
1551 float s = (q > 0.5) ? 1.0 : 0.0;
1552 return (t<0.15)?1.0-s:s;
1553}
1554
1555// Renders the girl. It finds the ray-girl intersection
1556// point, computes the normal at the intersection point,
1557// computes the ambient occlusion approximation, does per
1558// material setup (color, specularity, subsurface
1559// coefficient and paints some fake occlusion), and finally
1560// does the lighting computations.
1561//
1562// Lighting is not based on pathtracing. Instead the bounce
1563// light occlusion signals are created manually (placed
1564// and sized by hand). The subsurface scattering in the
1565// nose area is also painted by hand. There's not much
1566// attention to the physicall correctness of the light
1567// response and materials, but generally all signal do
1568// follow physically based rendering practices.
1569//
1570vec3 renderGirl( in vec2 p, in vec3 ro, in vec3 rd, in float tmax, in vec3 col, in float time )
1571{
1572 // --------------------------
1573 // find ray-girl intersection
1574 // --------------------------
1575 vec3 cma, uvw;
1576 vec2 tm = intersect( ro, rd, tmax, time, cma, uvw );
1577
1578 // --------------------------
1579 // shading/lighting
1580 // --------------------------
1581 if( tm.y>0.0 )
1582 {
1583 vec3 pos = ro + tm.x*rd;
1584 vec3 nor = calcNormal(pos, time);
1585
1586 float ks = 1.0;
1587 float se = 16.0;
1588 float tinterShadow = 0.0;
1589 float sss = 0.0;
1590 float focc = 1.0;
1591 //float frsha = 1.0;
1592
1593 // --------------------------
1594 // material
1595 // --------------------------
1596 if( tm.y<1.5 ) // skin
1597 {
1598 vec3 qos = vec3(abs(uvw.x),uvw.yz);
1599
1600 // base skin color
1601 col = mix(vec3(0.225,0.15,0.12),
1602 vec3(0.24,0.1,0.066),
1603 smoothstep(0.4 ,0.0,length( qos.xy-vec2(0.42,-0.3)))+
1604 smoothstep(0.15,0.0,length((qos.xy-vec2(0,-0.29))/vec2(1.4,1))));
1605
1606 // fix that ugly highlight
1607 col -= 0.03*smoothstep(0.13,0.0,length((qos.xy-vec2(0,-0.49))/vec2(2,1)));
1608
1609 // lips
1610 col = mix(col,vec3(0.14,0.06,0.1),cma.x*step(-0.7,qos.y));
1611
1612 // eyelashes
1613 col = mix(col,vec3(0.04,0.02,0.02)*0.6,0.9*cma.y);
1614
1615 // fake skin drag
1616 uvw.y += 0.025*animData.x*smoothstep(0.3,0.1,length(uvw-vec3(0.0,0.1,1.0)));
1617 uvw.y -= 0.005*animData.y*smoothstep(0.09,0.0,abs(length((uvw.xy-vec2(0.0,-0.38))/vec2(2.5,1.0))-0.12));
1618
1619 // freckles
1620 vec2 ti = floor(9.0+uvw.xy/0.04);
1621 vec2 uv = fract(uvw.xy/0.04)-0.5;
1622 float te = fract(111.0*sin(1111.0*ti.x+1331.0*ti.y));
1623 te = smoothstep(0.9,1.0,te)*exp(-dot(uv,uv)*24.0);
1624 col *= mix(vec3(1.1),vec3(0.8,0.6,0.4), te);
1625
1626 // texture for specular
1627 ks = 0.5 + 4.0*texture(iChannel3,uvw.xy*1.1).x;
1628 se = 12.0;
1629 ks *= 0.5;
1630 tinterShadow = 1.0;
1631 sss = 1.0;
1632 ks *= 1.0 + cma.x;
1633
1634 // black top
1635 col *= 1.0-smoothstep(0.48,0.51,uvw.y);
1636
1637 // makeup
1638 float d2 = sdEllipsoid(qos-vec3(0.25,-0.03,0.43),vec3(0.37,0.42,0.4));
1639 col = mix(col,vec3(0.06,0.024,0.06),1.0 - smoothstep(0.0,0.03,d2));
1640
1641 // eyebrows
1642 {
1643 #if 0
1644 // youtube video version
1645 vec4 be = sdBezier( qos, vec3(0.165+0.01*animData.x,0.105-0.02*animData.x,0.89),
1646 vec3(0.37,0.18-0.005*animData.x,0.82+0.005*animData.x),
1647 vec3(0.53,0.15,0.69) );
1648 float ra = 0.005 + 0.015*sqrt(be.y);
1649 #else
1650 // fixed version
1651 vec4 be = sdBezier( qos, vec3(0.16+0.01*animData.x,0.11-0.02*animData.x,0.89),
1652 vec3(0.37,0.18-0.005*animData.x,0.82+0.005*animData.x),
1653 vec3(0.53,0.15,0.69) );
1654 float ra = 0.005 + 0.01*sqrt(1.0-be.y);
1655 #endif
1656 float dd = 1.0+0.05*(0.7*sin((sin(qos.x*3.0)/3.0-0.5*qos.y)*350.0)+
1657 0.3*sin((qos.x-0.8*qos.y)*250.0+1.0));
1658 float d = be.x - ra*dd;
1659 float mask = 1.0-smoothstep(-0.005,0.01,d);
1660 col = mix(col,vec3(0.04,0.02,0.02),mask*dd );
1661 }
1662
1663 // fake occlusion
1664 focc = 0.2+0.8*pow(1.0-smoothstep(-0.4,1.0,uvw.y),2.0);
1665 focc *= 0.5+0.5*smoothstep(-1.5,-0.75,uvw.y);
1666 focc *= 1.0-smoothstep(0.4,0.75,abs(uvw.x));
1667 focc *= 1.0-0.4*smoothstep(0.2,0.5,uvw.y);
1668
1669 focc *= 1.0-smoothstep(1.0,1.3,1.7*uvw.y-uvw.x);
1670
1671 //frsha = 0.0;
1672 }
1673 else if( tm.y<2.5 ) // eye
1674 {
1675 // The eyes are fake in that they aren't 3D. Instead I simply
1676 // stamp a 2D mathematical drawing of an iris and pupil. That
1677 // includes the highlight and occlusion in the eyesballs.
1678
1679 sss = 1.0;
1680
1681 vec3 qos = vec3(abs(uvw.x),uvw.yz);
1682 float ss = sign(uvw.x);
1683
1684 // iris animation
1685 float dt = floor(time*1.1);
1686 float ft = fract(time*1.1);
1687 vec2 da0 = sin(1.7*(dt+0.0)) + sin(2.3*(dt+0.0)+vec2(1.0,2.0));
1688 vec2 da1 = sin(1.7*(dt+1.0)) + sin(2.3*(dt+1.0)+vec2(1.0,2.0));
1689 vec2 da = mix(da0,da1,smoothstep(0.9,1.0,ft));
1690
1691 float gg = animEye(time);
1692 da *= 1.0+0.5*gg;
1693 qos.yz = rot(qos.yz,da.y*0.004-0.01);
1694 qos.xz = rot(qos.xz,da.x*0.004*ss-gg*ss*(0.03-step(0.0,ss)*0.014)+0.02);
1695
1696 vec3 eos = qos-vec3(0.31,-0.055 - 0.03*animData.x,0.45);
1697
1698 // iris
1699 float r = length(eos.xy)+0.005;
1700 float a = atan(eos.y,ss*eos.x);
1701 vec3 iris = vec3(0.09,0.0315,0.0135);
1702 iris += iris*3.0*(1.0-smoothstep(0.0,1.0, abs((a+3.14159)-2.5) ));
1703 iris *= 0.35+0.7*texture(iChannel2,vec2(r,a/6.2831)).x;
1704 // base color
1705 col = vec3(0.42);
1706 col *= 0.1+0.9*smoothstep(0.10,0.114,r);
1707 col = mix( col, iris, 1.0-smoothstep(0.095,0.10,r) );
1708 col *= smoothstep(0.05,0.07,r);
1709
1710 // fake occlusion backed in
1711 float edis = length((vec2(abs(uvw.x),uvw.y)-vec2(0.31,-0.07))/vec2(1.3,1.0));
1712 col *= mix( vec3(1.0), vec3(0.4,0.2,0.1), linearstep(0.07,0.16,edis) );
1713
1714 // fake highlight
1715 qos = vec3(abs(uvw.x),uvw.yz);
1716 col += (0.5-gg*0.3)*(1.0-smoothstep(0.0,0.02,length(qos.xy-vec2(0.29-0.05*ss,0.0))));
1717
1718 se = 128.0;
1719
1720 // fake occlusion
1721 focc = 0.2+0.8*pow(1.0-smoothstep(-0.4,1.0,uvw.y),2.0);
1722 focc *= 1.0-linearstep(0.10,0.17,edis);
1723 //frsha = 0.0;
1724 }
1725 else if( tm.y<3.5 )// hoodie
1726 {
1727 sss = 0.0;
1728 col = vec3(0.81*texture(iChannel0,uvw*6.0).x);
1729 ks *= 2.0;
1730
1731 // logo
1732 if( abs(uvw.x)<0.66 )
1733 {
1734 float par = length(uvw.yz-vec2(-1.05,0.65));
1735 col *= mix(vec3(1.0),vec3(0.6,0.2,0.8)*0.7,1.0-smoothstep(0.1,0.11,par));
1736 col *= smoothstep(0.005,0.010,abs(par-0.105));
1737 }
1738
1739 // fake occlusion
1740 focc = mix(1.0,
1741 0.03+0.97*smoothstep(-0.15,1.7,uvw.z),
1742 smoothstep(-1.6,-1.3,uvw.y)*(1.0-clamp(dot(nor.xz,normalize(uvw.xz)),0.0,1.0))
1743 );
1744
1745 //frsha = mix(1.0,
1746 // clamp(dot(nor.xz,normalize(uvw.xz)),0.0,1.0),
1747 // smoothstep(-1.6,-1.3,uvw.y)
1748 // );
1749 //frsha *= smoothstep(0.85,1.0,length(uvw-vec3(0.0,-1.0,0.0)));
1750 }
1751 else if( tm.y<4.5 )// hair
1752 {
1753 sss = 0.0;
1754 col = (sin(cma.x)>0.7) ? vec3(0.03,0.01,0.05)*1.5 :
1755 vec3(0.04,0.02,0.01)*0.4;
1756 ks *= 0.75 + cma.z*18.0;
1757 float te = texture( iChannel2,vec2( 0.25*atan(uvw.x,uvw.y),8.0*uvw.z) ).x;
1758 col *= 2.0*te;
1759 ks *= 1.5*te;
1760
1761 // fake occlusion
1762 focc = 1.0-smoothstep(-0.40, 0.8, uvw.y);
1763 focc *= 1.0-0.95*smoothstep(-1.20,-0.2,-uvw.z);
1764 focc *= 0.5+cma.z*12.0;
1765 //frsha = 1.0-smoothstep(-1.3,-0.8,uvw.y);
1766 //frsha *= 1.0-smoothstep(-1.20,-0.2,-uvw.z);
1767 }
1768 else if( tm.y<5.5 )// teeth
1769 {
1770 sss = 1.0;
1771 col = vec3(0.3);
1772 ks *= 1.5;
1773 //frsha = 0.0;
1774 }
1775
1776 float fre = clamp(1.0+dot(nor,rd),0.0,1.0);
1777 float occ = focc*calcOcclusion( pos, nor, time );
1778
1779 // --------------------------
1780 // lighting. just four lights
1781 // --------------------------
1782 vec3 lin = vec3(0.0);
1783
1784 // fake sss
1785 float nma = 0.0;
1786 if( tm.y<1.5 )
1787 {
1788 nma = 1.0-smoothstep(0.0,0.12,length((uvw.xy-vec2(0.0,-0.37))/vec2(2.4,0.7)));
1789 }
1790
1791 //vec3 lig = normalize(vec3(0.5,0.4,0.6));
1792 vec3 lig = vec3(0.57,0.46,0.68);
1793 vec3 hal = normalize(lig-rd);
1794 float dif = clamp( dot(nor,lig), 0.0, 1.0 );
1795 //float sha = 0.0; if( dif>0.001 ) sha=calcSoftshadow( pos+nor*0.002, lig, 0.0001, 2.0, time, 5.0 );
1796 float sha = calcSoftshadow( pos+nor*0.002, lig, 0.0001, 2.0, time, 5.0 );
1797 float spe = 2.0*ks*pow(clamp(dot(nor,hal),0.0,1.0),se)*dif*sha*(0.04+0.96*pow(clamp(1.0-dot(hal,-rd),0.0,1.0),5.0));
1798
1799 // fake sss for key light
1800 vec3 cocc = mix(vec3(occ),
1801 vec3(0.1+0.9*occ,0.9*occ+0.1*occ*occ,0.8*occ+0.2*occ*occ),
1802 tinterShadow);
1803 cocc = mix( cocc, vec3(1,0.3,0.0), nma);
1804 sha = mix(sha,max(sha,0.3),nma);
1805
1806 vec3 amb = cocc*(0.55 + 0.45*nor.y);
1807 float bou = clamp(0.3-0.7*nor.x, 0.0, 1.0 );
1808
1809 lin += vec3(0.65,1.05,2.0)*amb*1.15;
1810 lin += 1.50*vec3(1.60,1.40,1.2)*sdif(dot(nor,lig),0.5+0.3*nma+0.2*(1.0-occ)*tinterShadow) * mix(vec3(sha),vec3(sha,0.2*sha+0.7*sha*sha,0.2*sha+0.7*sha*sha),tinterShadow);
1811 lin += vec3(1.00,0.30,0.1)*sss*fre*0.6*(0.5+0.5*dif*sha*amb)*(0.1+0.9*focc);
1812 lin += 0.35*vec3(4.00,2.00,1.0)*bou*occ*col;
1813
1814 col = lin*col + spe + fre*fre*fre*0.1*occ;
1815
1816 // overall
1817 col *= 1.1;
1818 }
1819
1820 return col;
1821}
1822
1823// Animates the head turn. This is my first time animating
1824// and I am aware I'm in uncanny/animatronic land. But I
1825// have to start somwhere!
1826//
1827float animTurn( in float time )
1828{
1829 const float w = 6.1;
1830 float t = mod(time,w*2.0);
1831
1832 vec3 p = (t<w) ? vec3(0.0,0.0,1.0) : vec3(w,1.0,-1.0);
1833 return p.y + p.z*expSustainedImpulse(t-p.x,1.0,10.0);
1834}
1835
1836// Animates the eye blinks. Blinks are motivated by head
1837// turns (again, in an attempt tp avoid animatronic and
1838// zoombie feel), but also there are random blinks. This
1839// same funcion is called with some delay and extra
1840// smmoothness to get the blink of the eyes be followed by
1841// the face muscles around the face.
1842//
1843float animBlink( in float time, in float smo )
1844{
1845 // head-turn motivated blink
1846 const float w = 6.1;
1847 float t = mod(time-0.31,w*1.0);
1848 float blink = smoothstep(0.0,0.1,t) - smoothstep(0.18,0.4,t);
1849
1850 // regular blink
1851 float tt = mod(1.0+time,3.0);
1852 blink = max(blink,smoothstep(0.0,0.07+0.07*smo,tt)-smoothstep(0.1+0.04*smo,0.35+0.3*smo,tt));
1853
1854 // keep that eye alive always
1855 float blinkBase = 0.04*(0.5+0.5*sin(time));
1856 blink = mix( blinkBase, 1.0, blink );
1857
1858 // base pose is a bit down
1859 float down = 0.15;
1860 return down+(1.0-down)*blink;
1861}
1862
1863// The main rendering entry point. Basically it does some
1864// setup, creating the ray that will explore the 3D scene
1865// in search of the girl for each pixel, computes the
1866// animation variables (blink, mouth and head movements),
1867// does the rendering of the girl if it finds her under
1868// the current pixel, and finally does gamme correction
1869// and some minimal color processing and vignetting to the
1870// image.
1871//
1872void mainImage( out vec4 fragColor, in vec2 fragCoord )
1873{
1874 // render
1875 vec3 tot = vec3(0.0);
1876
1877 #if AA>1
1878 for( int m=ZERO; m<AA; m++ )
1879 for( int n=ZERO; n<AA; n++ )
1880 {
1881 // pixel coordinates
1882 vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5;
1883 vec2 p = (-iResolution.xy + 2.0*(fragCoord+o))/iResolution.y;
1884 float d = 0.5*sin(fragCoord.x*147.0)*sin(fragCoord.y*131.0);
1885 float time = iTime - 0.5*(1.0/24.0)*(float(m*AA+n)+d)/float(AA*AA-1);
1886 #else
1887 vec2 p = (-iResolution.xy + 2.0*fragCoord)/iResolution.y;
1888 float time = iTime;
1889 #endif
1890
1891 time += 2.0;
1892
1893 // camera movement
1894 vec3 ro; float fl;
1895 mat3 ca = calcCamera( time, ro, fl );
1896 vec3 rd = ca * normalize( vec3((p-vec2(-0.52,0.12))/1.1,fl));
1897
1898 // animation (blink, face follow up, mouth)
1899 float turn = animTurn( time );
1900 animData.x = animBlink(time,0.0);
1901 animData.y = animBlink(time-0.02,1.0);
1902 animData.z = -0.25 + 0.2*(1.0-turn)*smoothstep(-0.3,0.9,sin(time*1.1)) + 0.05*cos(time*2.7);
1903
1904 // animation (head orientation)
1905 animHead = vec3( sin(time*0.5), sin(time*0.3), -cos(time*0.2) );
1906 animHead = animHead*animHead*animHead;
1907 animHead.x = -0.025*animHead.x + 0.2*(0.7+0.3*turn);
1908 animHead.y = 0.1 + 0.02*animHead.y*animHead.y*animHead.y;
1909 animHead.z = -0.03*(0.5 + 0.5*animHead.z) - (1.0-turn)*0.05;
1910
1911 // rendering
1912 vec4 tmp = texelFetch(iChannel1,ivec2(fragCoord),0);
1913 vec3 col = tmp.xyz;
1914 float tmin = tmp.w;
1915
1916 if( p.x*1.4+p.y<0.8 && -p.x*4.5+p.y<6.5 && p.x<0.48)
1917 col = renderGirl(p,ro,rd,tmin,col,time);
1918 //else col=vec3(0,1,0);
1919
1920 // gamma
1921 col = pow( col, vec3(0.4545) );
1922 tot += col;
1923 #if AA>1
1924 }
1925 tot /= float(AA*AA);
1926 #endif
1927
1928 // compress
1929 tot = 3.8*tot/(3.0+dot(tot,vec3(0.333)));
1930
1931 // vignette
1932 vec2 q = fragCoord/iResolution.xy;
1933 tot *= 0.5 + 0.5*pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.15);
1934
1935 // grade
1936 tot = tot*vec3(1.02,1.00,0.99)+vec3(0.0,0.0,0.045);
1937
1938 fragColor = vec4( tot, 1.0 );
1939}