blur Sau bước mờ, bước tiếp theo là biến dạng - 99win club
Featured image of post blur Sau bước mờ, bước tiếp theo là biến dạng - 99win club

blur Sau bước mờ, bước tiếp theo là biến dạng - 99win club

Cộng đồng trò chơi trực tuyến hàng đầu Mô tả Dẫn đầu với 99win club - nơi thăng hoa cùng các trò chơi trực tuyến hấp dẫn.

Ngày 1 tháng 5 năm 2020, 13:21 chiều (giờ địa phương -7) Trong khi giải pháp của tôi tỏ ra yếu kém khi áp dụng cho bìa album màu nền phẳng, vì kết quả thường thiếu độ tương phản, thì hiệu ứng của Apple Music vẫn giữ được vẻ đẹp nhất định. Tôi nghi ngờ họ thực hiện các bước tiền xử lý ảnh hoặc thậm chí không dùng mờ ảnh mà chỉ trích xuất bảng màu từ bìa để tạo gradient.

Cơ bản, phương pháp của Diffuse là tiến hành làm mờ và sau đó biến dạng bìa album. Ban đầu, tôi sử dụng b29 club - nổ hũ tài xỉu game bài online Gaussian Blur ở độ phân giải cao, với một lần pass dọc và một lần pass ngang. Tuy nhiên, quá trình này tiêu tốn khá nhiều tài nguyên, do đó tôi chuyển sang Kawase Blur – một kỹ thuật được MartinRGB giới thiệu. Bạn có thể tìm hiểu thêm về Kawase Blur qua bài viết trên blog của Intel. Tôi đã triển khai đoạn mã này trong LibGDX, nhưng không nhớ rõ nguồn gốc, có lẽ lấy từ ShaderToy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 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;
}

Đối với các kernel, tôi chọn chuỗi giá trị là 0, 1, 1, 2, 2, 3, 3, 4. Hai buffer hình chữ nhật squareBufferA và squareBufferB, mỗi cái s6666 đăng nhập có kích thước 192x192 pixel, được sử dụng luân phiên để render lên nhau. Hiệu quả khá ổn và về mặt hiệu suất, trên chip Qualcomm Snapdragon 8xx cũng chưa gặp vấn đề lớn nào:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
blurShader.begin();
blurShader.setUniform2fv("u_resolution", new float[]{squareSize,squareSize}, 0, 2); // squareSize là 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

Sau bước mờ, bước tiếp theo là biến dạng. Tôi sử dụng kỹ thuật Domain Wrapping, chủ yếu dựa trên code mẫu của iquilezles. Cuối cùng, vec2 thu được sẽ được dùng như uv để sample ảnh đã được mờ từ bìa album.

!noise

Cuối cùng, chỉ cần phóng to độ phân giải và render lên màn hình là xong. Cả quá trình nhìn chung khá đơn giản và dễ triển khai!

comments powered by Disqus
Built with Hugo
Theme Stack thiết kế bởi Jimmy