I wanted to create an organic-looking SDF scene in a single, fully procedural, fragment shader. The scene is modelled for this specific camera viewpoint and lighting setup.
Please change AA (line 24) to 1 if this shader is running slow.
Log in to post a comment.
#version 300 es
precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform int iFrame;
in vec2 vScreen;
out vec4 fragColor;
// [SH18] Woman. Created by Reinder Nijhoff 2018
// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// @reindernijhoff
//
// https://www.shadertoy.com/view/4tdcWS
//
// I wanted to create an organic-looking SDF scene in a single, fully procedural,
// fragment shader. The scene is modelled for this specific camera viewpoint and
// lighting setup.
//
// Please change AA (line 13) to 1 if this shader is running slow.
//
#define AA 2
#define FLOOR 0.
#define BODY 1.
#define HAIR 2.
//
// Hash functions by Dave Hoskins:
//
// https://www.shadertoy.com/view/4djSRW
//
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 443.8975);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
vec3 hash33(vec3 p3) {
p3 = fract(p3 * vec3(443.897, 441.423, 437.195));
p3 += dot(p3, p3.yxz + 19.19);
return fract((p3.xxy + p3.yxx)*p3.zyx);
}
float noise(in vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f*f*(3. -2.*f);
return mix(mix(hash12(i + vec2(0, 0)),
hash12(i + vec2(1, 0)), u.x),
mix(hash12(i + vec2(0, 1)),
hash12(i + vec2(1, 1)), u.x), u.y);
}
//
// SDF framework by Inigo Quilez:
//
// https://www.shadertoy.com/view/Xds3zN
//
vec2 boxIntersect(in vec3 ro, in vec3 rd, in vec3 rad) {
vec3 m = 1./rd;
vec3 n = m*ro;
vec3 k = abs(m)*rad;
vec3 t1 = -n - k;
vec3 t2 = -n + k;
float tN = max(max(t1.x, t1.y), t1.z);
float tF = min(min(t2.x, t2.y), t2.z);
if(tN > tF || tF < .0) return vec2(-1);
return vec2(tN, tF);
}
float smin(float a, float b, float k) {
float h = clamp(.5 + .5*(b - a)/k, .0, 1.);
return mix(b, a, h) - k * h * (1. - h);
}
float udRoundBox(vec3 p, vec3 b, float r) {
return length(max(abs(p)-b, .0)) -r;
}
float sdCapsuleF(vec3 p, vec3 a, vec3 b, const float r0, const float r1, const float f) {
vec3 d = b -a;
float h = length(d);
d = normalize(d);
float t=dot(p-a, d);
float th = t/h;
return distance(a+clamp(t,0.,h)*d, p)-mix(r0, r1, th) *
max(0., 1.+f-f*4.*abs(th-.5)*abs(th -.5));
}
float sdCapsule(vec3 p, vec3 a, vec3 b, const float r0, const float r1) {
vec3 d = b -a;
float h = length(d);
d = normalize(d);
float t=clamp(dot(p-a, d), 0., h);
return distance(a+t*d, p) -mix(r0, r1, t/h);
}
float mapHand(in vec3 p) {
float sph = length(p) - .1;
if (sph > .1) return sph; // bounding sphere
const float s = 1.175;
float d = udRoundBox(p, vec3(.0175/s + p.y * (.25/s), .035/s + p.x * (.2/s), 0.), .01);
d = smin(d, min(sdCapsule(p, vec3(.025, .0475, 0)/s, vec3(.028, .08, .02)/s, .01/s, .0075/s),
sdCapsule(p, vec3(.028, .08, .02)/s, vec3(.03, 0.1, .06)/s, .0075/s, .007/s)), .0057);
d = smin(d, min(sdCapsule(p, vec3(.01, .0425, 0)/s, vec3(.008, .07, .025)/s, .009/s, .0075/s),
sdCapsule(p, vec3(.008, .07, .025)/s, vec3(.008, .085, .065)/s, .0075/s, .007/s)), .0057);
d = smin(d, min(sdCapsule(p, vec3(-.01, .04, 0)/s, vec3(-.012, .065, .028)/s, .009/s, .0075/s),
sdCapsule(p, vec3(-.012, .065, .028)/s, vec3(-.012, .07, .055)/s, .0075/s, .007/s)), .0057);
d = smin(d, min(sdCapsule(p, vec3(-.025, .035, 0)/s, vec3(-.027, .058, .03)/s, .009/s, .0075/s),
sdCapsule(p, vec3(-.027, .058, .03)/s, vec3(-.028, .06, .05)/s, .0075/s, .007/s)), .0057);
return d;
}
vec2 map(in vec3 pos) {
const float f0 = .075;
const float f1 = .2;
const float f2 = .275;
vec3 ph = pos;
if (pos.x < 0.) {
ph += vec3(.11, -.135, .2);
ph = mat3(-0.8674127459526062, -0.49060970544815063, 0.08304927498102188, 0.22917310893535614, -0.5420454144477844, -0.8084964156150818, 0.4416726529598236, -0.6822674870491028, 0.5826116800308228) * ph;
} else {
ph.x = -ph.x;
ph += vec3(.075, -.09, .125);
ph = mat3(-0.6703562140464783, -0.7417424321174622, 0.020991835743188858, 0.36215442419052124, -0.3517296612262726, -0.8632093667984009, 0.6476624608039856, -0.5710554718971252, 0.5044094920158386) * ph;
}
float dh = mapHand(ph);
// right arm
float d = sdCapsuleF(pos, vec3(0.13, 0.535, -.036), vec3(.09, 0.292, -0.1), .035, .025, f1);
d = smin(d, sdCapsuleF(pos, vec3(.08, 0.29, -0.1), vec3(-.09, 0.15, -0.17), .03, .02, f0), .0051);
if (pos.x < 0.) d = smin(d, dh, .015);
// left arm
float d1 = sdCapsuleF(pos, vec3(-0.12, 0.56, .02), vec3(-0.11, 0.325, -.045), .035, .025, f1);
d1 = smin(d1, sdCapsuleF(pos, vec3(-0.11, 0.315, -.05), vec3(.07, .08, -0.11), .024, .022, f2), .005);
if (pos.x > 0.) d1 = smin(d1, dh, .015);
d = min(d1, d);
// body
vec3 bp1 = pos;
bp1 += vec3(0, -.44, -.027);
bp1 = mat3(0.9761762022972107, 0.033977385610342026, 0.2143024057149887, -0.07553963363170624, 0.9790945649147034, 0.18885889649391174, -0.20340539515018463, -0.20054790377616882, 0.9583353996276855) * bp1;
float db = udRoundBox(bp1, vec3(.07 + bp1.y*.3, 0.135 -abs(bp1.x)*0.2, 0.), .04);
vec3 bp2 = pos;
bp2 += vec3(-.032, -.235, -.06);
bp2 = mat3(0.8958174586296082, -0.37155669927597046, 0.24383758008480072, 0.3379548490047455, 0.9258314967155457, 0.16918234527111053, -0.28861331939697266, -0.0691504031419754, 0.9549453258514404) * bp2;
db = smin(db, udRoundBox(bp2, vec3(.065 - bp2.y*.25, 0.1, .02 -bp2.y*.13), .04), .03);
db = smin(db, sdCapsule(pos, vec3(0.11, 0.5, -.032), vec3(.05, 0.52, -.015), .04, .035), .01);
db = smin(db, sdCapsule(pos, vec3(.01, 0.4, -.01), vec3(.01, 0.7, .0), .045, .04), .02);
vec3 bp3 = pos;
bp3 += vec3(-.005, -.48, .018);
bp3 = mat3(0.9800665974617004, 0.05107402056455612, 0.19199204444885254, 0, 0.9663899540901184, -0.2570805549621582, -0.19866932928562164, 0.2519560754299164, 0.9471265077590942) * bp3;
db = smin(db, udRoundBox(bp3, vec3(.056 + bp3.y*.23 , .06, 0.), .04), .01);
d = smin(d, db, .01);
// right leg
float d2 = sdCapsuleF(pos, vec3(0.152, 0.15, .05), vec3(-.03, 0.43, -.08), .071, .055, f2);
d2 = smin(d2, sdCapsuleF(pos, vec3(0.14, .08, .05), vec3(-.01, 0.23, -.02), .05, .02, f1), .075);
d = min(d, d2);
float d3 = sdCapsuleF(pos, vec3(-.03, 0.43, -.084), vec3(.055, .04, -.04), .053, .02, f0);
d3 = smin(d3, sdCapsuleF(pos, vec3(-.0, 0.35, -.05), vec3(.025, 0.2, -.03), .04, .02, f2), .05);
d = min(d, d3);
// left leg
d = min(d, sdCapsuleF(pos, vec3(-.02, 0.12, 0.1), vec3(-0.145, .08, -0.17), .07, .055, f2));
float d4 = sdCapsuleF(pos, vec3(-0.145, .08, -0.17), vec3(0.205, .02, -0.09), .05, .0185, f0);
d4 = smin(d4, sdCapsuleF(pos, vec3(-.05, .085, -0.145), vec3(.05, .03, -.09), .035, .03, f2), .0075);
// right feet
float d6 = distance(pos, vec3(.0, .0, -0.1)) -.1; // bounding sphere
if(d6 < 0.1) {
d = min(d, sdCapsule(pos, vec3(.03, .03, -.08), vec3(.031, .01, -0.146), .015, .005));
d = min(d, sdCapsule(pos, vec3(.02, .03, -.08), vec3(.018, .01, -0.1505), .015, .006));
d = min(d, sdCapsule(pos, vec3(.00, .03, -.08), vec3(.005, .01, -0.1525), .015, .007));
d = min(d, sdCapsule(pos, vec3(-.01, .03, -.08), vec3(-.014, .01, -0.1575), .015, .01));
} else {
d = min(d6, d);
}
// left feet
float d5 = distance(pos, vec3(0.25, .025, -0.1)) -.12; // bounding sphere
if(d5 < 0.1) {
d5 = sdCapsule(pos, vec3(0.2, .035, -.075), vec3(0.3, .01, -.09), .035, .02);
d5 = smin(d5, sdCapsule(pos, vec3(0.31, .035, -.0975), vec3(0.1, .01, -0.10), .015, .02), .02);
d5 = smin(d5, sdCapsule(pos, vec3(0.31, .035, -.0975), vec3(0.355, .034, -0.10), .015, .01), .005);
d5 = min(d5, sdCapsule(pos, vec3(0.31, .022, -.0875), vec3(0.335, .022, -.09), .02, .01));
}
d4 = smin(d4, d5, .025);
d = min(d, d4);
// hair
vec3 hp = pos;
hp.x += smoothstep(.55, .45, pos.y)*.035;
hp.z *= 1.9 - .8 * pos.y;
hp.yz -= 2.*pos.x*pos.x;
float h = sdCapsule(hp, vec3(.0, 0.725, -.02), vec3(-.02, 0.415, .0), .094, .085);
h = smin(h, sdCapsule(hp, vec3(.0, 0.725, -.02), vec3(.06, 0.705, -.05), .085, .095), .02);
h = max(-(pos.y - abs(fract(pos.x*90.) -.5)*0.1 -.14 - smoothstep(-0.2, 0.1, pos.x)*.5), h);
return (h < d) ? vec2(h, HAIR) : vec2(d, BODY);
}
float calcSoftshadow(in vec3 ro, in vec3 rd, in float mint, in float tmax) {
float res = 1.;
float t = mint;
for(int i=0; i<14; i++) {
float h = map(ro + rd*t).x;
res = min(res, 8.*h/t);
t += max(h, .02);
if(res<.005 || t>tmax) break;
}
return clamp(res,0.,1.);
}
vec3 calcNormal(in vec3 pos) {
vec2 e = vec2(1,-1)*.00005;
return normalize(e.xyy*map(pos + e.xyy).x +
e.yyx*map(pos + e.yyx).x +
e.yxy*map(pos + e.yxy).x +
e.xxx*map(pos + e.xxx).x);
}
float calcAO(in vec3 pos, in vec3 nor) {
float occ = 0.;
float sca = 1.;
for(int i=0; i<5; i++) {
float hr = .005 + .12*float(i)/4.;
vec3 aopos = nor * hr + pos;
float dd = min(aopos.y, map(aopos).x);
occ += -(dd -hr)*sca;
sca *= .95;
}
return clamp(1. - 3.*occ, 0., 1.);
}
float render(in vec3 ro, in vec3 rd, in vec2 uv) {
// cast ray
float planeIntersect = abs(-ro.y/rd.y);
vec2 box = boxIntersect(ro, rd, vec3(.37, 1, .3));
float t = box.x;
float tmax = min(box.y, planeIntersect);
float m = FLOOR;
if (t > 0.) {
for(int i=0; i<40; i++) {
float precis = .0004*t;
vec2 res = map(ro+rd*t);
m = res.y;
if(abs(res.x) < precis || t > tmax) break;
t += res.x;
}
}
if(t>=tmax || t<0.) {
t = rd.y < 0. ? planeIntersect : 1000.;
m = FLOOR;
}
// shade scene
float col = 0.;
if(t < 10.) {
vec3 pos = ro + t*rd;
vec3 nor = vec3(0, 1, 0);
if (m < FLOOR + .5) {
col = .03;
} else {
col = .5;
nor = calcNormal(pos);
}
if (m > BODY + .5) {
col = 0.;
}
nor = normalize(nor + (hash33(pos) -.5) * .1);
vec3 ligp = vec3(5., 5., -.5);
vec3 lig = -normalize(pos - ligp);
float dif = clamp(dot(nor, lig), 0., 1.);
float bac = clamp(dot(nor, normalize(vec3(-.2, .5, -.02))), .0, 1.0)
*clamp(1.-pos.y,0.,1.);
if(m > FLOOR) bac *= clamp(-10.*pos.z+.4,0.,1.);
float occ, sha;
if (pos.x > .4 || pos.z > 1.) {
occ = sha = 1.;
} else {
occ = calcAO(pos, nor);
sha = calcSoftshadow(pos, lig, .01, 1.5);
}
col *= 1.30*dif*sha*(.25+.75*occ) + .5*bac*occ;
if (m > BODY + .5) {
// totally fake hair lighting
vec3 ref = reflect(rd, nor);
vec3 hal = normalize(normalize(vec3(-.1, .5, .35)) -rd);
vec2 hv = mix(vec2(pos.x*60. -pos.z*35., 0.),
vec2(-pos.x*50. -pos.z*50., 0.),
smoothstep(.0, .01, -dot(pos, normalize(vec3(-1., .15, .45)))));
float n = noise(hv*20. + vec2(2. + 10.*sin(pos.y*20.+.4), 2.5));
n = smoothstep(.4, 1., n);
float nd = noise(hv*40.+ vec2(2. + 10.*sin(pos.y*20.+.4), .5));
n *= nd * nd;
col += n * pow(max(0., dot(ref, hal)), 8.);
col += .03 * n * max(0., dot(ref, hal)) * smoothstep(.75, .5, pos.y);
}
col *= smoothstep(.985, 1., dot(normalize(vec3(0.7, 0.58, -.05)), lig));
}
return clamp(col, 0., 1.);
}
mat3 setCamera(in vec3 ro, in vec3 ta) {
vec3 cw = normalize(ta -ro);
vec3 cu = normalize(cross(cw, vec3(0,1,0)));
vec3 cv = normalize(cross(cu, cw));
return mat3(cu, cv, cw);
}
void main() {
float angle = -.18+ .18*sin(iTime*0.5);
float tot = 0.;
for(int m=0; m<AA + min(0,iFrame); m++)
for(int n=0; n<AA + min(0,iFrame); n++) {
vec2 o = vec2(float(m), float(n))/float(AA) - .5;
vec2 p = vScreen + 2.*o/iResolution.y;
p.x -= .2;
vec3 ro = vec3(2.9*sin(angle) , .65, -2.9*cos(angle));
vec3 ta = vec3(0., 0.45, 0.);
mat3 ca = setCamera(ro, ta);
vec3 rd = ca * normalize(vec3(p.xy, 5));
float col = render(ro, rd, p);
tot += pow(col, .4545);
}
tot /= float(AA*AA);
tot += .075 * hash12(vScreen);
tot *= 1.35;
fragColor = vec4(min(tot*vec3(1, .97, .92), 1.), 1.);
}