Achieve the 3D depth by canvas 2D abilities

Alex Bidiuk
Site effects under the hood
5 min readMar 29, 2018

Let’s discover the interesting effect on https://www.fotospatzek.at main page.

From the first sight everything looks quite simple:

1. The “circle-cursor” is a mask for the photo and it just “neutralizes” the blur effect.

2. The photo itself is simply tilted in the 3d space by the mouse movement.

But everything is a bit more interesting :)

To visualize effects, like the authors of this resource I will use the library for work with canvas, Pixi.js

For the first item everything is really true. There is a layer1 (sprite1) with the original image, there is a layer2 with the image on which the blur filter is applied and there is a layer3 with a figure circle which is a mask for the first layer.

Here is the code for all we were talking about:

var layer1 = PIXI.Sprite.fromImage(‘https://crossorigin.me/http://www.imageup.ru/img101/3028138/walter_spatzek.jpg');    layer1.width = 300;    layer1.height = 400;    layer1.x = document.body.offsetWidth / 2 — layer1.width;    layer1.y = 0;var layer2 = PIXI.Sprite.fromImage(‘https://crossorigin.me/http://www.imageup.ru/img101/3028138/walter_spatzek.jpg');    layer2.width = 300;    layer2.height = 400;// put the picture in the center of the screen along the X axis    layer2.x = document.body.offsetWidth / 2 — layer2.width / 2;    layer2.y = 0;var mainContainer = new PIXI.Container();    mainContainer.addChild(layer1);var additionalContainer = new PIXI.Container();    additionalContainer.addChild(layer2);var circle = new PIXI.Graphics();    circle.lineStyle(0);    circle.beginFill(0xFF3300, 0);    circle.drawCircle(0, 0,150);    circle.endFill();mainContainer.addChild(circle);additionalContainer.mask = circle;mainContainer.addChild(additionalContainer);// the parameter values for the filter can be found in Pixi.js documentationvar blurFilter = new PIXI.filters.BlurFilter(15,8,0,0);layer1.filters = [blurFilter];

Now the most interesting thing is that for the reproducing the next effect the simple movements in 3D aren’t enough. Take a closer look, with the mouse movement the face becomes three-dimensional and it creates a sense of realism.

For achieving this effect, we are asking for help one interesting thing which is very familiar to all the masters of Photoshop, as well as to people who are involved in 3D modeling, and it is called displacement map filter. For curious ones I leave a reference to a detailed theory: https://en.wikipedia.org/wiki/Displacement_mapping

But for people like me, who are too lazy to read a bunch of text, I’ll try to explain this principle here.

Suppose we have an original picture and we want to distort it in some parts. For doing it we take a picture that will be served as a displacement map and overlap the original image, distorting it according to the tone of the colors in the picture which is served as a displacement map. White color shifts the pattern in one direction, 50% gray (neutral) doesn’t distort it at all, and black shifts the pattern in the opposite direction from the white one. Also, one of the main settings of this filter is an indicator of how strong the offset (scale) is, it can be varied for the X axis and for the Y axis.

The image below shows the effect of the displacement map on the chessboard template. White area was shifted upward, and the black part was shifted downward.

How tone of displacement map image colors impact on original image

Let’s look at the gradients offsets. On the left side there is our displacement map, on the right — the result of displacement:

I hope it’s clear for you and we can continue.

In our case, the displacement filter with the scale = 0 parameter and the following displacement map is applied to both layers:

Displacement map for our demo

This is the silhouette of the face of the original image. You can get it if you blur and turn this image into black and white with the erasing of all unnecessary parts. A displacement map like this will distort parts of the image which will be superimposed on this gradient silhouette. Code:

var displacementFilterTexture = PIXI.Sprite.fromImage(‘https://crossorigin.me/http://www.imageup.ru/img101/3028137/disp_map_hoch.jpg');    displacementFilterTexture.width = 300;    displacementFilterTexture.height = 400;// put the filter texture in the center of the screen along the X axis, where our original image is                        
displacementFilterTexture.x = document.body.offsetWidth / 2 — displacementFilterTexture.width / 2;
displacementFilterTexture.y = 0;var displacementFilter = new PIXI.filters.DisplacementFilter(displacementFilterTexture,0);// add a new filter to the list of filters for both layers
layer1.filters = [blurFilter, displacementFilter];
layer2.filters = [displacementFilter];

So how to get this depth effect? We will try to move the mouse to change the scale value of the displacement filter along the X and Y axes respectively.

Let’s write a small function that will take the max value of the scale, the coordinates of the mouse and will calculate how much the cursor has moved away from the center of the screen (in fact, it is better to consider this distance from the center of the picture, but in my case I will do the same) in order to assign the appropriate scale for the displacement filter. Also put the handlers on cursor movement, where the function will be called. New coordinates will be set for our “cursor-circle”, which correspond to the current mouse coordinates:

app.stage
.on(‘mousemove’, onPointerMove)
.on(‘touchmove’, onPointerMove);
function onPointerMove(eventData) { circle.position.set(eventData.data.global.x, eventData.data.global.y); setTilt(40, eventData.data.global.x, eventData.data.global.y, displacementFilter);}function setTilt(maxTilt, mouseX, mouseY, displacementFilter) { var midpointX = document.body.offsetWidth / 2, midpointY = document.body.offsetHeight / 2, posX = midpointX-mouseX, posY = midpointY-mouseY,// consider the ratio of the current position of the mouse to the center of the screen and multiply by the maximum shift valX = (posX / midpointX) * maxTilt, valY = (posY / midpointY) * maxTilt; displacementFilter.scale.x = valX; displacementFilter.scale.y = valY;}

Voilà! The result is almost the same as on the original resource.

It is worth adding that by playing with the blending modes for displacement filter and applying additional filters of the library Pixi.js, you can receive an interesting result.

Demo link.

Thank you for your attention. I will be glad to receive your feedback.

--

--