test Keenan Crane's style for rendering edge and shade [uts-001D]

Adapted from :

#define EDGE_WIDTH 0.12
#define RAYMARCH_ITERATIONS 35
#define SHADOW_ITERATIONS 40
#define SHADOW_STEP 1.0
#define SHADOW_SMOOTHNESS 256.0
#define SHADOW_DARKNESS 0.75

// Distance functions from iquilezles.org
float fSubtraction(float a, float b) {return max(-a,b);}
float fIntersection(float d1, float d2) {return max(d1,d2);}
void fUnion(inout float d1, float d2) {d1 = min(d1,d2);}
float pSphere(vec3 p, float s) {return length(p)-s;}
float pRoundBox(vec3 p, vec3 b, float r) {return length(max(abs(p)-b,0.0))-r;}
float pTorus(vec3 p, vec2 t) {vec2 q = vec2(length(p.xz)-t.x,p.y); return length(q)-t.y;}
float pTorus2(vec3 p, vec2 t) {vec2 q = vec2(length(p.xy)-t.x,p.z); return length(q)-t.y;}
float pCapsule(vec3 p, vec3 a, vec3 b, float r) {vec3 pa = p - a, ba = b - a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return length( pa - ba*h ) - r;}

float mySDF(vec3 p) {
  float x = p.x;
  float y = p.y;
  float z = p.z;

  float x2 = pow(x, 2.);
  float y2 = pow(y, 2.);
  float z2 = pow(z, 2.);

  float x3 = pow(x, 3.);
  float y3 = pow(y, 3.);
  float z3 = pow(z, 3.);

  float x4 = pow(x, 4.);
  float y4 = pow(y, 4.);
  float z4 = pow(z, 4.);

  // return (y-x2)*(z-x3);

  // return 5.*(z2+y3-y4-x2*y2);

  // return (y2-x2-z2);

  return (x2-z2*y2+y3);
}

float distf(vec3 p)
{
  float d = 100000.0;

  fUnion(d, pRoundBox(vec3(0,0,10) + p, vec3(21,21,1), 1.0));
  fUnion(d, pSphere(vec3(10,10,0) + p, 8.0));
  fUnion(d, pSphere(vec3(16,0,4) + p, 4.0));
  fUnion(d, pCapsule(p, vec3(10,10,12), vec3(15,15,-6.5), 1.5));
  fUnion(d, pCapsule(p, vec3(10,10,12), vec3(5,15,-6.5), 1.5));
  fUnion(d, pCapsule(p, vec3(10,10,12), vec3(10,5,-6.5), 1.5));
  fUnion(d, pTorus(vec3(15,-15,0) + p, vec2(6,2)));
  fUnion(d, pTorus2(vec3(10,-15,0) + p, vec2(6,2)));
  fUnion(d, pRoundBox(vec3(-10,10,-2) + p, vec3(1,1,9), 1.0));
  fUnion(d, pRoundBox(vec3(-10,10,-4) + p, vec3(0.5,6,0.5), 1.0));
  fUnion(d, pRoundBox(vec3(-10,10,2) + p, vec3(6,0.5,0.5), 1.0));

  // d = mySDF(p);
  // d = fIntersection(d, pSphere(p,15.0));

  return d;
}


vec3 grad(vec3 p)
{
  const float eps = 0.01;
  float m;
    vec3 d_distf = vec3( (distf(vec3(p.x-eps,p.y,p.z)) - distf(vec3(p.x+eps,p.y,p.z))),
                   (distf(vec3(p.x,p.y-eps,p.z)) - distf(vec3(p.x,p.y+eps,p.z))),
                   (distf(vec3(p.x,p.y,p.z-eps)) - distf(vec3(p.x,p.y,p.z+eps)))
         );
    return d_distf / (2.*eps);
}

vec3 grad2(vec3 p)
{
  const float eps = 0.01;
  float m;
    vec3 d_grad = vec3( (grad(vec3(p.x-eps,p.y,p.z)) - grad(vec3(p.x+eps,p.y,p.z))).x,
                   (grad(vec3(p.x,p.y-eps,p.z)) - grad(vec3(p.x,p.y+eps,p.z))).y,
                   (grad(vec3(p.x,p.y,p.z-eps)) - grad(vec3(p.x,p.y,p.z+eps)).z)
         );
    return d_grad / (2.*eps);
}

vec3 normal(vec3 p)
{
    return normalize(grad(p));
}


vec4 raymarch(vec3 from, vec3 increment)
{
  const float maxDist = 200.0;
  const float minDist = 0.001;
  const int maxIter = RAYMARCH_ITERATIONS;

  float dist = 0.0;

  float lastDistEval = 1e10;
  float edge = 0.0;

  for(int i = 0; i < maxIter; i++) {
    vec3 pos = (from + increment * dist);
    float distEval = distf(pos);

    if (lastDistEval < EDGE_WIDTH && distEval > lastDistEval + 0.001) {
      edge = 1.0;
    }


    if (distEval < minDist) {
      break;
    }

    dist += distEval;
    if (distEval < lastDistEval) lastDistEval = distEval;
  }

  float mat = 1.0;
  if (dist >= maxDist) mat = 0.0;

  return vec4(dist, mat, edge, 0);
}

float shadow(vec3 from, vec3 increment)
{
  const float minDist = 1.0;

  float res = 1.0;
  float t = 1.0;
  for(int i = 0; i < SHADOW_ITERATIONS; i++) {
    float h = distf(from + increment * t);
    if(h < minDist)
      return 0.0;

      res = min(res, SHADOW_SMOOTHNESS * h / t);
      t += SHADOW_STEP;
    }
    return res;
}

float rand(float x)
{
  return fract(sin(x) * 43758.5453);
}

float triangle(float x)
{
  return abs(1.0 - mod(abs(x), 2.0)) * 2.0 - 1.0;
}

// Camera localized normal
vec3 campos, camup;
vec3 localNormal(vec3 p, vec3 rd) {
    vec3 n = normal(p), ln;
    vec3 side = cross(campos, camup);
    return vec3(dot(n,  side), dot(n,  camup), dot(n,  -rd));
}

float time;
vec4 getPixel(vec2 p, vec3 from, vec3 increment, vec3 light)
{
  vec4 c = raymarch(from, increment);
  vec3 hitPos = from + increment * c.x;
  vec3 normalDir = normal(hitPos);


  float diffuse = 1.0 + min(0.0, dot(normalDir, -light));
  float inshadow = (1.0 - shadow(hitPos, -light)) * SHADOW_DARKNESS;

  // diffuse = max(diffuse, inshadow);

  // if it's not edge, and no contact
  if (c.z != 1.0 && c.y == 0.0) return vec4(0.96, 0.94, 0.87,1);

  float low = 0.05;
  float high = 0.95;

  diffuse = diffuse > high ? 1.0 : (diffuse > low ? low : diffuse);

  vec4 mCol = mix(vec4(vec3(0.78, 0.76, 0.78) * 1.15,1), vec4(0.70, 0.68, 0.75,1), diffuse);

  float gridStep = 1.0;

  // optional chess style grid
  // mCol = mix(mCol,vec4(0.,0.,0.,1.0),0.1*mod(floor(hitPos.x/gridStep)+floor(hitPos.y/gridStep)+floor(hitPos.z/gridStep),2.));

  float dt = dot(vec3(0., 0., 1.), normalDir);

  float eps_high = 0.02;
  float eps_low = 0.00000; //1;

  // vec3 n = vec4(normalDir, 0.).xyz;
  vec3 n = localNormal(hitPos, increment); // grad(hitPos); // localNormal(hitPos, increment);
  vec3 ez = vec3(0.,0.,0.1);

  // https://www.shadertoy.com/view/fsGXzc

  const float MAX_DIST = 200.0;
  float depth = distance(from, hitPos); // /MAX_DIST;

    // I've mostly just copied and pasted Evan's code.
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Compute curvature
	vec3 dx = dFdx(n);
	vec3 dy = dFdy(n);
	vec3 xneg = n - dx;
	vec3 xpos = n + dx;
	vec3 yneg = n - dy;
	vec3 ypos = n + dy;
	float curvature = (cross(xneg, xpos).y - cross(yneg, ypos).x) * 4.0 / (depth);

  // curvature debug
  // return vec4(vec3(1,0.9,0.8) * 0.7 * (abs(curvature) * 500.),1);
  ;
  vec4 curve_color = vec4(mix(vec3(1,0.9,0.8) * 0.5, vec3(1.), 0.5), 1.); // * length(grad(hitPos).xy) //  * abs(curvature) * 200.

  float curvature_eps = 0.0001;
  if(c.z != 1. && abs(curvature) > curvature_eps && length(grad(hitPos).z) > eps_low && ( length(grad(hitPos).z) < eps_high * abs(curvature) * 100. ) ) {
      return curve_color;
  }

  if(c.z != 1. && abs(curvature) > curvature_eps && length(grad(hitPos).y) > eps_low && ( length(grad(hitPos).y) < eps_high * abs(curvature) * 100.) ) {
      return curve_color;
  }

  if(c.z != 1. && abs(curvature) > curvature_eps && length(grad(hitPos).x) > eps_low && ( length(grad(hitPos).x) < eps_high * abs(curvature) * 100.) ) {
      return curve_color;
  }

  if(c.z != 1. && length(grad(hitPos).z) > eps_low && ( length(grad(hitPos).z) < eps_high) ) {
      // return vec4(mix(vec3(1,0.9,0.8) * 0.5, vec3(1.), 0.5), 1.); // * (abs(curvature) * 1000.),1);
  }

  // .z is edge
  mCol = mix(mCol,vec4(vec3(1,0.9,0.8) * 0.5,1),c.z);

  return mCol;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  time = floor(iTime * 16.0) / 16.0;
  // pixel position
  vec2 q = fragCoord.xy / iResolution.xy;
  vec2 p = -1.0+2.0*q;
  p.x *= -iResolution.x/iResolution.y;

  // mouse
  vec2 mo = iMouse.xy/iResolution.xy;
  vec2 m = iMouse.xy / iResolution.xy;
  if (iMouse.x == 0.0 && iMouse.y == 0.0) {
    m = vec2(time * 0.06 + 1.67, 0.78);
  }
  m = -1.0 + 2.0 * m;
  m *= vec2(4.0,-0.75);
  m.y += 0.75;

  // camera position
  float dist = 65.0;
  vec3 ta = vec3(0,0,0);
  vec3 ro = vec3(cos(m.x) * cos(m.y) * dist, sin(m.x) * cos(m.y) * dist, sin(m.y) * dist);
  vec3 light = vec3(cos(m.x - 2.27) * 50.0, sin(m.x - 2.27) * 50.0, -20.0);

  // camera direction
  vec3 cw = normalize( ta-ro );
  vec3 cp = vec3( 0.0, 0.0, 1.0 );
  vec3 cu = normalize( cross(cw,cp) );
  vec3 cv = normalize( cross(cu,cw) );
  vec3 rd = normalize( p.x*cu + p.y*cv + 2.5*cw );
  campos = -cw, camup = cv;

  // calculate color
  vec4 col = getPixel(p, ro, rd, normalize(light));
  col = pow(col, vec4(1.0 / 2.2));
  col = col * 1.8 - 0.8;
  fragColor = col;
}

Color extracted with the help of .

See also:

- toon shader
- GLSL Fragment Shader: Sobel Edge Detection
- Cross hatching WebGL shader
- Penumbra Maps: Approximate Soft Shadows in Real-Time
- stackgl/glsl-lighting-walkthrough