Sử dụng AGSL trong ứng dụng Android của bạn

Trang này trình bày kiến thức cơ bản về AGSL và các cách sử dụng AGSL trong ứng dụng Android.

Chương trình đổ bóng AGSL đơn giản

Mã chương trình đổ bóng của bạn được gọi cho mỗi pixel được vẽ và trả về màu mà pixel sẽ được vẽ. Chương trình đổ bóng cực kỳ đơn giản là chương trình luôn trả về một màu duy nhất; ví dụ này sử dụng màu đỏ. Chương trình đổ bóng được xác định bên trong String.

Kotlin

private const val COLOR_SHADER_SRC =
   """half4 main(float2 fragCoord) {
      return half4(1,0,0,1);
   }"""

Java

private static final String COLOR_SHADER_SRC =
   "half4 main(float2 fragCoord) {\n" +
      "return half4(1,0,0,1);\n" +
   "}";

Bước tiếp theo là tạo một đối tượng RuntimeShader được khởi chạy bằng chuỗi chương trình đổ bóng. Thao tác này cũng biên dịch chương trình đổ bóng.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Bạn có thể dùng RuntimeShader ở mọi nơi mà chương trình đổ bóng Android tiêu chuẩn có thể. Ví dụ: bạn có thể sử dụng đối tượng này để vẽ vào View tuỳ chỉnh bằng cách sử dụng Canvas.

Kotlin

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      canvas.drawPaint(paint) // fill the Canvas with the shader
   }
}

Java

Paint paint = new Paint();
paint.setShader(fixedColorShader);
public void onDrawForeground(@Nullable Canvas canvas) {
   if (canvas != null) {
      canvas.drawPaint(paint); // fill the Canvas with the shader
   }
}

Thao tác này sẽ vẽ một View màu đỏ. Bạn có thể sử dụng uniform để truyền một tham số màu vào chương trình đổ bóng cần vẽ. Trước tiên, hãy thêm màu uniform vào chương trình đổ bóng:

Kotlin

private const val COLOR_SHADER_SRC =
"""layout(color) uniform half4 iColor;
   half4 main(float2 fragCoord) {
      return iColor;
   }"""

Java

private static final String COLOR_SHADER_SRC =
   "layout(color) uniform half4 iColor;\n"+
      "half4 main(float2 fragCoord) {\n" +
      "return iColor;\n" +
   "}";

Sau đó, hãy gọi setColorUniform từ View tuỳ chỉnh để truyền màu mong muốn vào chương trình đổ bóng AGSL.

Kotlin

fixedColorShader.setColorUniform("iColor", Color.GREEN )

Java

fixedColorShader.setColorUniform("iColor", Color.GREEN );

Giờ đây, bạn sẽ nhận được View màu xanh lục; màu View được kiểm soát bằng cách sử dụng một tham số từ mã trong View tuỳ chỉnh thay vì được nhúng trong chương trình đổ bóng.

Thay vào đó, bạn có thể tạo hiệu ứng chuyển màu. Trước tiên, bạn cần thay đổi chương trình đổ bóng để chấp nhận độ phân giải View làm dữ liệu đầu vào:

Kotlin

private const val COLOR_SHADER_SRC =
"""uniform float2 iResolution;
   half4 main(float2 fragCoord) {
      float2 scaled = fragCoord/iResolution.xy;
      return half4(scaled, 0, 1);
   }"""

Java

private static final String COLOR_SHADER_SRC =
   "uniform float2 iResolution;\n" +
      "half4 main(float2 fragCoord) {\n" +
      "float2 scaled = fragCoord/iResolution.xy;\n" +
      "return half4(scaled, 0, 1);\n" +
   "}";

Vẽ dải chuyển màu

Chương trình đổ bóng này có vẻ hơi lạ. Đối với mỗi pixel, vectơ này sẽ tạo một vectơ float2 chứa các toạ độ x và y chia cho độ phân giải. Khi đó, vectơ này sẽ tạo ra một giá trị từ 0 đến 1. Sau đó, phương thức này sử dụng vectơ được điều chỉnh theo tỷ lệ đó để tạo các thành phần màu đỏ và màu xanh lục của màu trả về.

Bạn truyền độ phân giải của View vào uniform chương trình đổ bóng AGSL bằng cách gọi setFloatUniform.

Kotlin

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      fixedColorShader.setFloatUniform("iResolution", width.toFloat(), height.toFloat())
      canvas.drawPaint(paint)
   }
}

Java

Paint paint = new Paint();
paint.setShader(fixedColorShader);
public void onDrawForeground(@Nullable Canvas canvas) {
   if (canvas != null) {
      fixedColorShader.setFloatUniform("iResolution", (float)getWidth(), (float()getHeight()));
      canvas.drawPaint(paint);
   }
}
Chuyển màu đỏ và xanh lục
Chuyển màu đỏ và xanh lục

Tạo ảnh động cho chương trình đổ bóng

Bạn có thể sử dụng một kỹ thuật tương tự để tạo ảnh động cho chương trình đổ bóng bằng cách sửa đổi để nhận được đồng nhất iTimeiDuration. Chương trình đổ bóng sẽ sử dụng các giá trị này để tạo sóng tam giác cho các màu, khiến các màu đó chuyển đổi qua lại giữa các giá trị chuyển màu.

Kotlin

private const val DURATION = 4000f
private const val COLOR_SHADER_SRC = """
   uniform float2 iResolution;
   uniform float iTime;
   uniform float iDuration;
   half4 main(in float2 fragCoord) {
      float2 scaled = abs(1.0-mod(fragCoord/iResolution.xy+iTime/(iDuration/2.0),2.0));
      return half4(scaled, 0, 1.0);
   }
"""

Java

private static final float DURATION = 4000f;
private static final String COLOR_SHADER_SRC =
   "uniform float2 iResolution;\n"+
   "uniform float iTime;\n"+
   "uniform float iDuration;\n"+
   "half4 main(in float2 fragCoord) {\n"+
      "float2 scaled = abs(1.0-mod(fragCoord/iResolution.xy+iTime/(iDuration/2.0),2.0));\n"+
      "return half4(scaled, 0, 1.0);\n"+
   "}";

Từ mã nguồn khung hiển thị tuỳ chỉnh, ValueAnimator sẽ cập nhật đồng nhất iTime.

Kotlin

// declare the ValueAnimator
private val shaderAnimator = ValueAnimator.ofFloat(0f, DURATION)

// use it to animate the time uniform
shaderAnimator.duration = DURATION.toLong()
shaderAnimator.repeatCount = ValueAnimator.INFINITE
shaderAnimator.repeatMode = ValueAnimator.RESTART
shaderAnimator.interpolator = LinearInterpolator()

animatedShader.setFloatUniform("iDuration", DURATION )
shaderAnimator.addUpdateListener { animation ->
    animatedShader.setFloatUniform("iTime", animation.animatedValue as Float )
}
shaderAnimator.start()

Java

// declare the ValueAnimator
private final ValueAnimator shaderAnimator = ValueAnimator.ofFloat(0f, DURATION);

// use it to animate the time uniform
shaderAnimator.setDuration((long)DURATION);
shaderAnimator.setRepeatCount(ValueAnimator.INFINITE);
shaderAnimator.setRepeatMode(ValueAnimator.RESTART);
shaderAnimator.setInterpolator(new LinearInterpolator());

animatedShader.setFloatUniform("iDuration", DURATION );
shaderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   public final void onAnimationUpdate(ValueAnimator animation) {
      animatedShader.setFloatUniform("iTime", (float)animation.getAnimatedValue());
   }
});
Ảnh động chuyển màu đỏ và xanh lục
Ảnh động chuyển màu đỏ và xanh lục

Vẽ các vật thể phức tạp

Bạn không cần phải vẽ chương trình đổ bóng để tô nền; bạn có thể dùng chương trình này ở bất kỳ nơi nào chấp nhận đối tượng Paint, chẳng hạn như drawText.

Kotlin

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint)

Java

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint);
Văn bản chuyển màu động màu đỏ và màu xanh lục
Văn bản chuyển màu động màu đỏ và màu xanh lục

Chuyển đổi hiệu ứng đổ bóng và Canvas

Bạn có thể áp dụng các phép biến đổi Canvas khác trên văn bản được tô bóng, chẳng hạn như xoay. Trong ValueAnimator, bạn có thể cập nhật ma trận cho chế độ xoay 3D bằng cách sử dụng lớp android.graphics.Camera tích hợp sẵn.

Kotlin

// in the ValueAnimator
camera.rotate(0.0f, animation.animatedValue as Float / DURATION * 360f, 0.0f)

Java

// in the ValueAnimator
camera.rotate(0.0f, (Float)animation.getAnimatedValue() / DURATION * 360f, 0.0f);

Vì bạn muốn xoay văn bản từ trục giữa thay vì từ góc, hãy lấy ranh giới văn bản, sau đó sử dụng preTranslatepostTranslate để thay đổi ma trận nhằm dịch văn bản sao cho 0,0 là tâm xoay mà không thay đổi vị trí văn bản được vẽ trên màn hình.

Kotlin

linearColorPaint.getTextBounds(ANIMATED_TEXT, 0, ANIMATED_TEXT.length, bounds)
camera.getMatrix(rotationMatrix)
val centerX = (bounds.width().toFloat())/2
val centerY = (bounds.height().toFloat())/2
rotationMatrix.preTranslate(-centerX, -centerY)
rotationMatrix.postTranslate(centerX, centerY)
canvas.save()
canvas.concat(rotationMatrix)
canvas.drawText(ANIMATED_TEXT, 0f, 0f + bounds.height(), paint)
canvas.restore()

Java

linearColorPaint.getTextBounds(ANIMATED_TEXT, 0, ANIMATED_TEXT.length(), bounds);
camera.getMatrix(rotationMatrix);
float centerX = (float)bounds.width()/2.0f;
float centerY = (float)bounds.height()/2.0f;
rotationMatrix.preTranslate(-centerX, -centerY);
rotationMatrix.postTranslate(centerX, centerY);
canvas.save();
canvas.concat(rotationMatrix);
canvas.drawText(ANIMATED_TEXT, 0f, 0f + bounds.height(), paint);
canvas.restore();
Văn bản chuyển màu động xoay màu đỏ và màu xanh lục
Văn bản chuyển màu động xoay màu đỏ và màu xanh lục

Sử dụng RuntimeShader với Jetpack Compose

Nếu bạn kết xuất giao diện người dùng bằng Jetpack Compose, thì việc sử dụng RuntimeShader sẽ còn dễ dàng hơn nữa. Bắt đầu bằng cùng một chương trình đổ bóng chuyển màu từ trước:

private const val COLOR_SHADER_SRC =
    """uniform float2 iResolution;
   half4 main(float2 fragCoord) {
   float2 scaled = fragCoord/iResolution.xy;
   return half4(scaled, 0, 1);
}"""

Bạn có thể áp dụng chương trình đổ bóng đó cho ShaderBrush. Sau đó, bạn sẽ dùng ShaderBrush làm tham số cho các lệnh vẽ trong phạm vi vẽ của Canvas.

// created as top level constants
val colorShader = RuntimeShader(COLOR_SHADER_SRC)
val shaderBrush = ShaderBrush(colorShader)

Canvas(
   modifier = Modifier.fillMaxSize()
) {
   colorShader.setFloatUniform("iResolution",
   size.width, size.height)
   drawCircle(brush = shaderBrush)
}
Vòng tròn chuyển màu trong AGSL Compose
Vòng tròn chuyển màu đỏ và xanh lục

Sử dụng RuntimeShader với RenderEffect

Bạn có thể sử dụng RenderEffect để áp dụng RuntimeShader cho View mẹ tất cả các khung hiển thị con. Thao tác này tốn kém hơn so với việc vẽ một View tuỳ chỉnh. nhưng cho phép bạn dễ dàng tạo hiệu ứng kết hợp những gì ban đầu đã được vẽ bằng createRuntimeShaderEffect.

Kotlin

view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))

Java

view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));

Tham số thứ hai là tên của đồng nhất trong chương trình đổ bóng mà bạn có thể eval bằng tham số toạ độ (chẳng hạn như được truyền vào fragCoord) để lấy màu gốc của RenderNode (Khung hiển thị và các khung hiển thị con), cho phép bạn thực hiện mọi loại hiệu ứng.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Nút kết hợp lưới trên
Nút kết hợp lưới AGSL

Hiệu ứng lưới được kết hợp trên một nút, nhưng ở bên dưới một nút hành động nổi (vì nút này nằm trong một hệ phân cấp View khác).