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);
}