2

前言

最近看到一个很酷却又很容易实现的图片伪 3D 效果,效果如下:

QQ20200317-231121-HD.gif

现在让我们来看看如何实现它。

材料准备

我们需要准备的材料有:

  • 一张图片。
  • 上述图片的深度图。

啥是深度图?深度图就是描述原始图片上的每个像素距离屏幕远近的一种图片,一般来说越白就距离屏幕越近,越黑就距离屏幕越远。例如,前言中的示例风景的深度图长这样:

可以看到,远处的山在深度图中偏暗,而近处的水偏亮。

那么,哪里可以搞到深度图呢?这时候就需要打开 Photoshop,拿上你的小画笔,看着原图,发挥自己的想象力,一笔一画描绘出深度图。没错,需要自己画。说实话,对于手残党来说,这应该是所有步骤中最难的部分了。

画深度图有几个小技巧(然并卵,手残党有了再多技巧也没用):

  • 善用渐变
  • 抠出轮廓后再画
  • 画完高斯模糊

动手阶段

有了图片后,我们就可以开始开发了。这里我们需要用到 WebGL。什么是 WebGL?说来话太长,就不说了。

使用原生 WebGL 的话,冗长又琐碎的 API 会让人非常的难受,因此我们选择一个优秀的开源库 regl,regl 封装了基础的 WebGL 操作,能够大大降低 WebGL 的使用门槛。

首先,我们需要加载准备好的图片:

const loadImage = url => {
    return new Promise(resolve => {
        const image = new Image();
        image.crossOrigin = ''
        image.onload = () => resolve(image);
        image.src = url;
    });
};
Promise.all([url, depthMapUrl].map(loadImage)).then(main);

加载完之后初始化我们的功能:

const drawTriangle = regl({
    frag: `
precision mediump float;
varying highp vec2 vTexCoord;
uniform sampler2D texture;
uniform sampler2D depthMap;
uniform float x;
uniform float y;
void main() {
    vec2 texCoord = (vTexCoord + 1.0) / 2.0 * vec2(1, 1);
    vec4 depth = texture2D(depthMap, texCoord);
    gl_FragColor = texture2D(texture, texCoord + vec2(x, -y) * depth.r);
}`,

    vert: `
precision mediump float;
attribute vec2 position;
varying highp vec2 vTexCoord;
void main() {
    gl_Position = vec4(position, 0, 1);
    vTexCoord = position;
}`,

    attributes: {
        position: [-1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1]
    },

    uniforms: {
        texture: regl.texture()({ data: image, flipY: true }),
        depthMap: regl.texture({ data: depthMapImage, flipY: true }),
        x: regl.prop('x'),
        y: regl.prop('y')
    },

    count: 6
});

let x = 0;
let y = 0;
document.body.onmousemove = e => {
    x = (e.pageX - window.innerWidth / 2) / 10000;
    y = (e.pageY - window.innerHeight / 2) / 10000;
};

regl.frame(() => {
    regl.clear({
        color: [0, 0, 0, 0],
        depth: 1
    });
    drawTriangle({ x, y });
});

要实现伪 3d 效果的关键代码在片元着色器中:

// 获取当前纹理坐标的深度信息
vec4 depth = texture2D(depthMap, texCoord);
// 基于深度信息(用蓝或绿通道也行)与鼠标位置计算出得新纹理坐标
gl_FragColor = texture2D(texture, texCoord + vec2(x, y) * depth.r);

原理解析

我们通过深度图中的黑白关系能够得到当前纹理坐标距离屏幕的远近,距离屏幕近就越白,depth.r 就越大,那么它移动的位置也越大,反之则反。还是以前面的风景图为例,远处的山偏移少,而近处的人偏移大,这种层次感就产生了伪 3d 效果。

最后,附上本次的 Demo(不用怀疑,效果不好一定是因为深度图画的不好)。

参考

How To Create A Fake 3D Image Effect With WebGL


花生杀手
432 声望3 粉丝

人矮就要多修图