Last year I read an article describing the mathematical structure of Escher’s Print Gallery. Escher’s Print Gallery (de Prentententoonstelling in Dutch) shows a man standing in an exhibition gallery, viewing a print of a seaport. As his eyes follow the buildings shown on the print clockwise, he discovers among them the very same gallery in which he is standing.

The Droste effect used in the Print Gallery is special: the image is mapped in a spiral. I decided to create a WebGL fragment shader to visualize the mathematics of the article myself. Instead of applying the transformation to a two-dimensional image, I ended up applying the transformation to a 3D model of Escher’s image. My shader toggles between the ‘normal’ Droste effect of an image-in-image-in-image-..  and Escher’s ‘spiral’ Droste effect.

Animation of Escher's Droste effect.

Because the WebGL fragment shader is really heavy, some browsers will crash by running the shader. Therefore I captured the output of the shader in the movie embedded above. You can find the real-time version, with code/maths open to exploring, here: https://www.shadertoy.com/view/Mdf3zM.

How does it work?

First, I started by generating a ‘normal’ Droste image of a 3D model of Escher’s image using a fragment shader. The 3D model contains a small (Mediterranean?) city with different buildings. One of the buildings is the print gallery. In this gallery, you can find a print of the same small city, which gives rise to the Droste effect. The size of the print is 1/256 of the size of an image of the total city.

droste_1

The fragment shader generates the image above by raymarching a distance field of a 3D model of the city for each pixel. You can find an excellent article about raymarching distance fields by Iñigo Quilez here.

When you generate an image by raymarching, you must construct a (3D) ray for each image pixel (just like by ray casting). With the constructed ray you march a distance field to find the first object the ray will hit. Shading this object at the intersection point will give you a colour for each pixel.

To get the Droste image above, first, all pixel coordinates uv are scaled to [-1,1]. If |u| or |v| is bigger than 1, uv is divided by 256.

After that, a ray is constructed for each coordinate uv. If this ray hits the print in the gallery, the original coordinate uv is multiplied by 256:

if( hitDrostePicture(uv) ) uv*=256.;
if( hitDrostePicture(uv) ) uv*=256.;Code language: GLSL (glsl)

The position of the gallery and the view direction are carefully chosen to ensure the print in the gallery is exactly in the centre of the screen (coordinate 0,0). By multiplying the coordinates for rays hitting the print by 256, and constructing new rays for these multiplied coordinates, the Droste image shown above can be generated by raymarching the distance field of the city using the newly constructed rays.

Escher’s domain transformation

Given the setup described, Escher’s ‘spiral’ Droste effect can be replicated by applying a domain transformation to the initial coordinate uv:

h(w) = wα = w^((2πi+log scale)/(2πi))  (w = u + iv)Code language: JavaScript (javascript)

Or,  as coded in glsl in the fragment shader:

vec2 escherDeformation( in vec2 uv ) {
	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;
}Code language: GLSL (glsl)

In the fragment shader, the variable deformationScale automatically changes in time between values 1 and 256, which results in animation between the original Droste image, and the ‘warped’ spiral Droste effect as used by Escher.

Fragment shader

The shader shown is only one fragment shader, rendered using two triangles. So no 3D models, textures or any other external sources are used.

You can find (the full source of) the fragment shader on Shadertoy: https://www.shadertoy.com/view/Mdf3zM.

Similar posts

If you like this post, you may also like one of my other posts:

Escher and the Droste effect
Tagged on: