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