















WebGL is becoming quite popular these days as it allows us to create unique interactive graphics for the web. You might have seen the recent text distortion effects using Blotter.js or the animated WebGL lines created with the THREE.MeshLine library. Today you’ll see how to quickly create an interactive “fake” 3D effect for images with plain WebGL.

If you use Facebook, you might have seen the update of 3D photos for the news feed and VR. With special phone cameras that capture the distance between the subject in the foreground and the background, 3D photos bring scenes to life with depth and movement. We can recreate this kind of effect with any photo, some image editing and a little bit of coding.

Usually, these kind of effects would rely on either Three.js or Pixi.js, the powerful libraries that come with many useful features and simplifications when coding. Today we won’t use any libraries but go with the native WebGL API.

So let’s dig in.

Getting started

So, for this effect we’ll go with the native WebGL API. A great place to help you get started with WebGL is webglfundamentals.org. WebGL is usually being berated for its verboseness. And there is a reason for that. The foundation of all fullcreen shader effects (even if they are 2D) is some sort of plane or mesh, or so called quad, which is stretched over the whole screen. So, speaking of being verbose, while we would simply write THREE.PlaneGeometry(1,1) in three.js which creates the 1×1 plane, here is what we need in plain WebGL:

let vertices = new Float32Array([ -1, -1, 1, -1, -1, 1, 1, 1, ]) let buffer = gl.createBuffer(); gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );

Now that we have our plane, we can apply vertex and fragment shaders to it.

Preparing the image

For our effect to work, we need to create a depth map of the image. The main principle for building a depth map is that we’ve got to separate some parts of the image depending on their Z position, i.e. being far or close, hence isolate the foreground from the background.

For that, we can open the image in Photoshop and paint gray areas over the original photo in the following way:

This image shows some mountains where you can see that the closer the objects are to the camera, the brighter the area is painted in the depth map. Let’s see in the next section why this kind of shading makes sense.

Shaders

The rendering logic is mostly happening in shaders. As described in the MDN web docs:

A shader is a program, written using the OpenGL ES Shading Language (GLSL), that takes information about the vertices that make up a shape and generates the data needed to render the pixels onto the screen: namely, the positions of the pixels and their colors. There are two shader functions run when drawing WebGL content: the vertex shader and the fragment shader.

A great resource to learn more about shaders is The Book Of Shaders.

The vertex shader will not do much; it just shows the vertices:

attribute vec2 position; void main() { gl_Position = vec4( position, 0, 1 ); }

The most interesting part will happen in a fragment shader. Let’s load the two images there:

void main(){ vec4 depth = texture2D(depthImage, uv); gl_FragColor = texture2D(originalImage, uv); // just showing original photo }

Remember, the depth map image is black and white. For shaders, color is just a number: 1 is white and 0 is pitch black. The uv variable is a two dimensional map storing information on which pixel to show. With these two things we can use the depth information to move the pixels of the original photo a little bit.

Let’s start with a mouse movement:

vec4 depth = texture2D(depthImage, uv); gl_FragColor = texture2D(originalImage, uv + mouse);

Here is how it looks like:

Now let’s add the depth:

vec4 depth = texture2D(depthImage, uv); gl_FragColor = texture2D(originalImage, uv + mouse*depth.r);

And here we are:

Because the texture is black and white, we can just take the red channel ( depth.r ), and multiply it to the mouse position value on the screen. That means, the brighter the pixel is, the more it will move with the mouse. On the other hand, dark pixels will just stay in place. It’s so simple, yet, it results in such a nice 3D illusion of an image.

Of course, shaders are capable of doing all kinds of other crazy things, but I hope you like this small experiment of “faking” a 3D movement. Let me know what you think about it, and I hope to see your creations with this!

References and Credits