0.00
60.0 fps

Escher's prentententoonstelling

I found this article: ams.org/notices/200304/fea-escher.pdf describing the transformation used by Escher in the droste-picture: de prentententoonstelling (the picture gallery). The source is a mess atm - I will clean up later.

Log in to post a comment.

#version 300 es
precision highp float;

uniform float iTime;
uniform vec2  iResolution;

out vec4 fragColor;


uniform float DeformationScale; // value=1, min=0, max=1, step=0.01
uniform float Zoom; // value=0, min=0, max=1, step=0.01
uniform float AA; // value=1, min=0, max=1, step=1 (No, Yes)

float zoom, deformationScale;

// Escher's prentententoonstelling. Reinder Nijhoff 2013
// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// @reindernijhoff
//
// https://www.shadertoy.com/view/Mdf3zM
//
// Study of the transformation of Escher in 'the prentententoonstelling' 
// ```
// h(w) = w^((2πi + log scale)/(2πi))
// ```
// Distance field functions by Inigo Quilez.
//
// [1] http://www.ams.org/notices/200304/fea-escher.pdf
//

// #define SHADOW

vec2 escherDeformation( in vec2 uv ) {
	
// http://www.ams.org/notices/200304/fea-escher.pdf
// h(w) = w^((2πi + log scale)/(2πi))
	
	float lnr = log(length(uv));
	float th = atan( uv.y, uv.x )+(0.4/256.)*deformationScale;
	float sn = -log(deformationScale)*(1./(2.*3.1415926));
	float l = exp( lnr - th*sn ); 
	
	vec2 ret = vec2( l );
	
	ret.x *= cos( sn*lnr+th );
	ret.y *= sin( sn*lnr+th );
		
	return ret;
}

#define drostescale 256.

vec2 drosteTransformation( in vec2 uv ) {
	for( int i=0; i<2; i++ ) {
		if(any(greaterThan(abs(uv),vec2(1.)))) {
			uv *= (1./drostescale);
		}		
		if(all(lessThan(abs(uv),vec2(1./drostescale)))) {
			uv *= drostescale;
		}
	}
	return uv;
}

float hash( float n )
{
    return fract(sin(n)*43758.5453);
}

float sdPlane( vec3 p ) {
	return p.y+14.+0.05*cos(p.x+iTime*2.);
}

float sdBox( vec3 p, vec3 b ) {
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) +
         length(max(d,0.0));
}

float udBox( vec3 p, vec3 b) {
  return length(max(abs(p)-b,0.0));
}
float sdTriPrism( vec3 p, vec2 h ) {
    vec3 q = abs(p);
    return max(q.x-h.y,max(q.z*0.4+p.y*0.5,-p.y)-h.x*0.5);
}

float sdCylinderXY( vec3 p, vec2 h ) {
  return max( length(p.xy)-h.x, abs(p.z)-h.y );
}
float sdCylinderYZ( vec3 p, vec2 h ) {
  return max( length(p.yz)-h.x, abs(p.x)-h.y );
}
float sdCylinderXZ( vec3 p, vec2 h ) {
  return max( length(p.xz)-h.x, abs(p.y)-h.y );
}


//----------------------------------------------------------------------

float opS( float d1, float d2 ) {
    return max(-d2,d1);
}
float opU( float d1, float d2 ) {
    return min(d2,d1);
}

vec2 opU( vec2 d1, vec2 d2 ) {
	return (d1.x<d2.x) ? d1 : d2;
}

float opI( float d1, float d2 ) {
    return max(d1,d2);
}

//----------------------------------------------------------------------


float objPrentenTentoonstelling( in vec3 pos ) {
	vec3 tpos;// = pos;
	tpos.x = min( abs(pos.x), abs(pos.z) );
	tpos.y = pos.y;
	tpos.z = max( abs(pos.x), abs(pos.z) );
	
	float res = opU(opU(opU(opU(opU(
			opS(opS(opS( // main building
				opS(
					udBox( tpos, vec3( 5.5, 24.0, 5.5 ) ),
					sdBox( vec3(tpos.x, tpos.y-24.0, tpos.z), vec3( 5.25, 0.5, 5.25) ) 
				),
				sdBox( vec3( mod(tpos.x+1.75, 3.5)-1.75, tpos.y-21.5, tpos.z-5.), vec3( 1.,1.,4.) )
			),
				sdBox( vec3( mod(tpos.x+1.75, 3.5)-1.75, tpos.y-15.5, tpos.z-5.), vec3( 1.,2.,4.) )
			),
				sdCylinderXY( vec3( mod(tpos.x+1.75, 3.5)-1.75, tpos.y-17.5, tpos.z-5.), vec2( 1.,4.) )
			),
			opI( // main building windows
				udBox( tpos, vec3( 5.5, 23., 5.5 ) ),
				opU(
					udBox(  vec3( mod(tpos.x+1.75, 3.5)-1.75, tpos.y, tpos.z-5.2), vec3( 0.05, 24., 0.05 ) ),
					udBox(  vec3( tpos.x, mod(tpos.y+0.425, 1.75)-0.875, tpos.z-5.2), vec3( 10.0, 0.05, 0.05 ) )
				)
			)
		),
		opS( // gallery
			opU(opU(opU(		
				opS(opS( 
						udBox( tpos, vec3( 8.375, 8.75, 8.375 ) ),
						sdCylinderXY( vec3( mod(tpos.x, 2.75)-1.375, tpos.y-6.5, tpos.z-8.75), vec2( 1.25,2.75) )
					),
					sdBox( vec3(  mod(tpos.x, 2.75)-1.375, tpos.y-4.5, tpos.z-8.75), vec3( 1.25,2.0,2.75) )			
				),
				udBox(  vec3( mod(tpos.x-8.375/18., 8.375/9.)-8.375/18., tpos.y, tpos.z-8.3), vec3( 0.025, 8.5, 0.025 ) )
			),
				udBox(  vec3( tpos.x, tpos.y-4.3, tpos.z-8.3), vec3( 8.5, 0.025, 0.025 ) ) 
			),
				udBox(  vec3( tpos.x, tpos.y-6.3, tpos.z-8.3), vec3( 8.5, 0.025, 0.025 ) ) 
			),
			opU(opU(opU(
				sdCylinderYZ( vec3( pos.x-8.75, pos.y-6.5, mod(pos.z, 13.75)-6.875), vec2( 1.25,20.) ),
				sdBox( vec3(  pos.x-8.75, pos.y-2.5, mod(pos.z,  13.75)-6.875), vec3( 20.,4.0,1.25) )			
			),
				sdCylinderXY( vec3( mod(pos.x,13.75)-6.875, pos.y-6.5, pos.z-8.75), vec2( 1.25,20.) )
			),
				sdBox( vec3(  mod(pos.x, 13.75)-6.875, pos.y-2.5, pos.z-8.75), vec3( 1.25,4.0,20.) )	
			)
		) ),
			sdTriPrism( vec3(tpos.x, tpos.y-9.3, tpos.z-5.2), vec2(2.0, 10. ) ) // roof
		),
			sdTriPrism( vec3(tpos.x, tpos.y-2.8, tpos.z-5.2), vec2(0.75, 8. ) )
		),
		udBox( tpos, vec3( 6.5, 2.5, 6.5 ) )
	);
	
	return res;
}

float objB1( in vec3 pos ) {
	float res =
		opU(opS(			
			opS(
				udBox( pos, vec3( 20., 30.0, 10. ) ),				
				sdBox( pos+vec3(0., -30., 0.), vec3( 19.75, 1., 9.75 ) )
			),
			sdBox( vec3( mod(pos.x+1.75, 3.5)-1.75, mod(pos.y+3.5, 7.)-2., pos.z-10.), vec3( 1.,1.,4.) )
		),
			opI( // main building windows
				udBox( pos, vec3( 18., 30.0, 10. ) ),
				opU(
					udBox(  vec3( mod(pos.x+1.75, 3.5)-1.75, pos.y, pos.z-9.8), vec3( 0.05, 30., 0.05 ) ),
					udBox(  vec3( pos.x, mod(pos.y+0.425, 1.75)-0.875, pos.z-9.8), vec3( 50.0, 0.05, 0.05 ) )
				)
			)
		);
	return res;	
}

float objB2( in vec3 pos ) {
	vec3 tpos;// = pos;
	tpos.x = min( abs(pos.x), abs(pos.z) );
	tpos.y = pos.y;
	tpos.z = max( abs(pos.x), abs(pos.z) );
	
	float res = opU(
			opS(opS( // main building
				opS(
					udBox( tpos, vec3( 8.75, 31.0, 8.75 ) ),
					sdBox( vec3(tpos.x, tpos.y-31.0, tpos.z), vec3( 8.5, 1.0, 8.5) ) 
				
			),
				sdBox( vec3( mod(tpos.x+1.75, 3.5)-1.75, mod(tpos.y+4.5, 9.)-2.5, tpos.z-5.), vec3( 1.,2.,4.) )
			),
				sdCylinderXY( vec3( mod(tpos.x+1.75, 3.5)-1.75, mod(tpos.y+4.5, 9.)-4.5, tpos.z-5.), vec2( 1.,4.) )
			),
			opI( // main building windows
				udBox( tpos, vec3( 8.75, 31.0, 8.75 ) ),
				opU(
					udBox(  vec3( mod(tpos.x+1.75, 3.5)-1.75, tpos.y, tpos.z-8.45), vec3( 0.05, 31., 0.05 ) ),
					udBox(  vec3( tpos.x, mod(tpos.y+0.425, 1.75)-0.875, tpos.z-8.45), vec3( 10.0, 0.05, 0.05 ) )
				)
			)
		);
	return res;	
}

vec2 map( in vec3 pos ) {
    vec2 res = opU( vec2( sdPlane( pos), 3.0 ),
	                vec2( udBox( pos+vec3(0.0, 9.0, 85.0), vec3( 200., 10.0, 100. ) ), 1. ) );

	res = opU( res, vec2( udBox( pos+vec3(0.0, 20.0, 75.0), vec3( 200., 10.0, 100. ) ), 1. ) );
 	res = opU( res, vec2( udBox( pos+vec3(0.0, 6.5, -15.0), vec3( 200., 10.0, 0.25 ) ), 1. ) );

	res = opU( res, vec2( udBox( pos+vec3( 220.0, 14.0, 0.0), vec3( 100., 10.0, 200. ) ), 1. ) );
		
	res = opU( res, vec2( udBox( (pos+vec3(3.20, -4.95, -5.55)), vec3( 0.55, 0.9, 0.01 ) ), 2. ) );
	res = opU( res, vec2( sdCylinderXZ( vec3(mod(pos.x+8., 16.)-8., pos.y+10., pos.z-24.), vec2( 0.4, 1.5)), 1.) );

	if( pos.z > 20. ) {
		return res;
	}
	
	res = opU( res, vec2( objPrentenTentoonstelling( vec3(mod(pos.x+40.,80.)-40., pos.y, mod(pos.z+40.,80.)-40.) ), 1. ) );
	
	pos += vec3(3.25, -4.60, -5.55);
	res = opU( res, vec2( opI(
		udBox( vec3(mod(pos.x+0.8, 1.6)-0.8, pos.y, pos.z), vec3( 0.7, 0.9, 0.1 ) ),
		udBox( pos-vec3(3.25, -4.60, -5.55), vec3( 5.5, 5.5, 8.5 ) )
		), 4. ) );
	pos -= vec3(3.25, -4.60, -5.55);
	
	pos += vec3( 15.5, 8., 10.);
	res = opU( res, vec2( objB1( vec3(mod(pos.x+27.,54.)-27., pos.y, mod(pos.z+50.,100.)-50.) ), 1. ) );
	pos += vec3( 20.5, -8., 5.);
	res = opU( res, vec2( objB2( vec3(mod(pos.x+23.,46.)-23., pos.y, mod(pos.z+35.,70.)-35.) ), 1. ) );
	pos += vec3( 20., -10., 10.);
	res = opU( res, vec2( objB1( vec3(mod(pos.x+77.,144.)-77., pos.y, mod(pos.z+66.,132.)-66.) ), 1. ) );
		
	return res;
}


// fast castfunctions to detect if droste picture is hit by ray

float fastObjPrentenTentoonstelling( in vec3 pos ) {
	return opU(	udBox(  vec3( pos.x, pos.y-6.3, pos.z-8.3), vec3( 8.5, 0.025, 0.025 ) ),
				udBox(  vec3( mod(pos.x-8.375/18., 8.375/9.)-8.375/18., pos.y, pos.z-8.3), vec3( 0.025, 8.5, 0.025 ) )
	);
}
vec2 fastMap( in vec3 pos ) {
    return opU( vec2( fastObjPrentenTentoonstelling( pos), 1.0 ),
	            vec2( udBox( (pos+vec3(3.30, -4.55, -5.55)), vec3( 0.55, 0.7, 0.01 ) ), 2. ) );
}

vec2 fastCastRay( in vec3 ro, in vec3 rd, in float maxd )
{
	float precis = 0.001;
    float h=precis*2.0;
    float t = 0.0;
    float m = -1.0;
    for( int i=0; i<60; i++ )
    {
		vec2 res = fastMap( ro+rd*t );
		t += res.x;
		if( abs(res.x)<precis || t>maxd ) {
		    return vec2(t, res.y);
		}
    }
    return vec2( t, -1.0 );
}

vec2 castRay( in vec3 ro, in vec3 rd, in float maxd )
{
	float precis = 0.001;
    float h=precis*2.0;
    float t = 0.0;
    for( int i=0; i<60; i++ )
    {
		vec2 res = map( ro+rd*t );
		t += res.x;
		if( abs(res.x)<precis || t>maxd ) {
		    return vec2(t, res.y);
		}
    }
    return vec2( t, -1.0 );
}


float softshadow( in vec3 ro, in vec3 rd, in float mint, in float maxt, in float k )
{
	float res = 1.0;
    float t = 0.1;
    float ph = 0.0;
    for( int i=0; i<32; i++ )
    {
		if( t<maxt )
		{
            float h = map( ro + rd*t ).x;			
                        
            float y = h*h/(2.0*ph);
            float d = sqrt(h*h-y*y);
            res = min( res, 10.0*d/max(0.0,t-y) );
            ph = h;
            
            t += 0.005+h;
		} 
    }
    return clamp( res, 0.0, 1.0 );

}

vec3 calcNormal( in vec3 pos )
{
    vec2 e = vec2(1,-1)*.001;
    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);
}

void getRoAndRd( in vec2 uv, out vec3 ro, out vec3 rd ) {
	ro = vec3( 20.2, 36.0, 47.0  );
	vec3 ta = vec3( -3.1, 4.8,  5.5 );
	
	// camera tx
	vec3 cw = normalize( ta-ro );
	vec3 cp = vec3( 0.0, 1.0, 0.0 );
	vec3 cu = normalize( cross(cw,cp) );
	vec3 cv = normalize( cross(cu,cw) );
	rd = normalize( uv.x*cu + uv.y*cv + cw*zoom);
}

bool hitDrostePicture( vec2 uv ) {
	vec3 ro, rd;
	getRoAndRd( uv, ro, rd );	
	
	vec2 res = fastCastRay(ro,rd,200.0);
	return (res.y == 2. && res.x < 100.0 );
}

vec4 trace( vec2 uv ) {
	vec3 ro, rd;
	getRoAndRd( uv, ro, rd );
	
    vec3 col = vec3(0.);
		
    vec2 res = castRay(ro,rd,400.0);
    float t = res.x;
	float m = res.y;
    if( t < 350. )
    {
        vec3 pos = ro + t*rd;
        vec3 nor = calcNormal( pos );

		col = vec3(0.7);
		if( m == 3. ) col = vec3(0.6,0.71,1.0);
		if( m == 4. ) col = vec3( 1. );
		
		if( m == 1. && all(lessThan(abs(pos), vec3( 5.65, 10., 5.65 ) ) ) ) {
			col = vec3( 0.6 ); // inside gallery
		}
		
		vec3 lig = normalize( vec3(-0.4, 0.4, 0.8) );
		float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 );
        float dif = clamp( dot( nor, lig ), 0.0, 1.0 );

		float sh = 1.0;
#ifdef SHADOW		
		if( dif>0.05 ) { sh = softshadow( pos, lig, 0.1, 30.0, 5.0 ); dif *= (0.8+0.2*sh); }
#endif		
		vec3 brdf = vec3(0.0);
		brdf += 0.80*amb*vec3(0.6,0.71,0.85);
        brdf += 1.30*dif*vec3(1.00,0.90,0.70);

		col = col*brdf;
		
	} else {
		col = 1.2*vec3(0.6,0.71,0.85) - rd.y*0.2*vec3(1.0,0.5,1.0);
	}

	return vec4( clamp(col,0.0,1.0), m );
}


void main() {
    zoom = (pow(Zoom , 2.) * 255. + 1.) *  2.71828;
    deformationScale = pow(DeformationScale, 2.) * 255. + 1.;
    
	vec3 col = vec3(0.);
	
	for (float x=0.; x<=AA; x+=1.) {
    	for (float y=0.; y<=AA; y+=1.) {
        	vec2 uv = (gl_FragCoord.xy + vec2(x, y) * .5) / iResolution.xy;
        	
        	uv = 2.*uv - vec2(1.);
            uv.x *= iResolution.x / iResolution.y;
        	
        	bool band = abs(uv.x) > 1.;
        	
        	// the  gallerymodel is a factor 1./0.7 too high to match Eschers painting, so I cheat :(
        	uv.x *= 0.7;
        	uv = escherDeformation(uv);	
        	uv = drosteTransformation(uv);
        	
        	if( hitDrostePicture(uv) ) uv*=256.;
        	if( hitDrostePicture(uv) ) uv*=256.;
    
        	vec4 tr = trace( uv );
        	
            if(band) {
            	tr.xyz = mix( tr.xyz, vec3(0.), DeformationScale );	
            }
        	
        	col += tr.xyz;
    	}   
	}
	
	
	fragColor = vec4( col / ( (AA+1.) * (AA+1.)), 1.0);
}