Diffuse: 如何做一个类似于 Apple Music 的假流体壁纸

 • 

其实 Diffuse 最后做出来的效果和 Apple Music 的 Live Lyrics 效果不太像,因为后期我也不管苹果的效果如何了,完全按照自己的喜好来调的。而且 Apple 的方案比我的要 versatile 很多,这个词可能比较在描述软件的时候有特定的内涵,导致我也不知道中文要怎么描述了,反正和鲁棒差不多,但是更多的是指对不同的输入都有类似的效果,因为我的方案在纯色封面时效果会特别差,输出的图像没有对比度,Apple Music 的效果会好一些,所以我怀疑他们对图片是有在做预处理,或者压根没有对封面做模糊而是直接根据封面提取出一个色盘后做了渐变。
Diffuse 的基本思路就是对专辑封面先模糊再扭曲。一开始模糊用的是全分辨率高斯模糊,一个纵向 pass 一个横向 pass。后来发现太费资源了,于是改为了马老师(MartinRGB)推荐的 Kawase Blur。有关 Kawase Blur 的详情可以看 Intel 的这个博客,我是在 LibGDX 内实现的,这段从哪来的也忘了,估计是某个 ShaderToy(

// kawaseBlur.frag

#ifdef GL_ES
precision mediump float;
#endif

uniform sampler2D u_texture;
uniform int u_level;
uniform vec2 u_resolution;

varying vec4 v_color;
varying vec2 v_texCoord;

vec4 reSample(sampler2D texture, vec2 res, in int d, in vec2 uv)
{
    vec2 step1 = (vec2(d) + 0.5) / res;
    vec4 color = vec4(0.0);
    color += texture2D(texture, uv + step1) / float(4);
    color += texture2D(texture,  uv - step1) / float(4);
    vec2 step2 = step1;
    step2.x = -step2.x;
    color += texture2D(texture, uv + step2) / float(4);
    color += texture2D(texture,  uv - step2) / float(4);
    return color;
}


void main() {
    gl_FragColor = reSample(u_texture, u_resolution, u_level, v_texCoord);
}
// kawaseBlur.vert

uniform mat4 u_projTrans;

attribute vec4 a_position;
attribute vec2 a_texCoord0;
attribute vec4 a_color;

varying vec4 v_color;
varying vec2 v_texCoord;

void main() {
    gl_Position = u_projTrans * a_position;
    v_texCoord = a_texCoord0;
    v_color = a_color;
}

关于 kernels (u_level) 我选的是 0,1,1,2,2,3,3,4,用了两个 192*192 像素的贴图 squareBufferA 和 squareBufferB 互相渲染,效果还不错,性能在高通 8xx 上都没遇到太大的问题。

blurShader.begin();
blurShader.setUniform2fv("u_resolution", new float[]{squareSize,squareSize}, 0, 2); // squareSize 就是 192
blurShader.end();
batch.setShader(blurShader);
int[] levels = new int[] {0,1,1,2,2,3,3,4};
for (int i = 0; i < levels.length; i++) {
    int level = levels[i] * 2;
    boolean swap = i % 2 == 0;
    blurShader.begin();
    blurShader.setUniformi("u_level", level);
    blurShader.end();
    batch.begin();
    if (swap) {
        squareBufferB.begin();
    } else {
        squareBufferA.begin();
    }
    Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    batch.draw(swap ? squareBufferA.getColorBufferTexture() : squareBufferB.getColorBufferTexture(), 0, 0, width, height, 0, 0, 1, 1);
    batch.flush();
    if (swap) {
        squareBufferB.end();
    } else {
        squareBufferA.end();
    }
    batch.end();
}

blur

然后就是扭曲的过程了。这个部分用的是 Domain Wrapping,基本上就是照着 iquilezles 来了,最后得到的 vec2 作为 uv 去 sample 之前模糊后的专辑图

noise

然后就可以放大分辨率输出到屏幕上了,就这么简单(