在 Android 應用程式中使用 AGSL

本頁面說明 AGSL 基本概念,以及在 Android 應用程式中使用 AGSL 的不同方式。

簡易的 AGSL 著色器

每個繪製的像素都會呼叫著色器程式碼,並傳回像素應繪製的顏色。極為簡單的著色器是一律傳回單一顏色的著色器;本範例使用紅色。著色器是在 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" +
   "}";

下一步是建立使用著色器字串初始化的 RuntimeShader 物件。這也會編譯著色器。

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

RuntimeShader 可用於標準 Android 著色器的任何位置。舉例來說,您可以使用 Canvas 以此方式繪製成自訂 View 中。

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
   }
}

這會繪製紅色的 View。您可以使用 uniform 將顏色參數傳遞至要繪製的著色器。首先,新增顏色 uniform 至著色器:

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" +
   "}";

然後,從自訂 View 呼叫 setColorUniform,將所需顏色傳遞給 AGSL 著色器。

Kotlin

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

Java

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

現在,您取得綠色的 View;系統會使用自訂 View 中程式碼的參數控制 View 顏色,而不是嵌入著色器中。

也可以改為建立色彩漸層效果。您必須先變更著色器,接受 View 解析度做為輸入內容:

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" +
   "}";

繪製漸層

這個著色器可以稍微修正。每個像素都會建立一個 float2 向量,其中包含 x 和 y 座標除以解析度相除的值,建立介於 0 到 1 之間的值。然後使用該縮放向量建構傳回顏色的紅色和綠色元件。

您可以透過呼叫 setFloatUniformView 的解析度傳遞至 AGSL 著色器 uniform

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);
   }
}
紅色和綠色漸層
紅色和綠色漸層

為著色器加入動畫效果

您可以使用類似的技巧為著色器加入動畫效果,並將其修改為接收 iTimeiDuration 制服。著色器會使用這些值建立顏色的三角形波浪,讓顏色在梯度值之間來回循環。

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"+
   "}";

在自訂檢視原始碼中,ValueAnimator 會更新 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());
   }
});
紅色和綠色動畫漸層
紅色和綠色動畫漸層

繪製複雜物體

您不必繪製著色器來填滿背景;此功能可用於任何接受 Paint 物件的位置,例如 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);
紅色和綠色動畫漸層文字
紅色和綠色動畫漸層文字

陰影和畫布轉換

您可以對陰影文字套用額外的 Canvas 轉換,例如旋轉。在 ValueAnimator 中,您可以使用內建的 android.graphics.Camera 類別更新 3D 旋轉的矩陣。

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);

由於您要從中心軸 (而非角落旋轉文字) 旋轉文字,請取得文字邊界,然後使用 preTranslatepostTranslate 變更矩陣翻譯文字,讓旋轉的中心位置變成 0,0,不必改變文字繪製在螢幕上的位置。

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();
紅色和綠色旋轉的動畫漸層文字
紅色和綠色旋轉的動畫漸層文字

將 RuntimeShader 與 Jetpack Compose 搭配使用

使用 Jetpack Compose 轉譯 UI 時,可以更輕鬆地使用 RuntimeShader。從先前相同的漸層著色器開始:

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

您可以將該著色器套用至 ShaderBrush。然後,在 Canvas 的繪圖範圍內,將 ShaderBrush 做為繪圖指令的參數使用。

// 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)
}
AGSL Compose 漸層圓形
紅色和綠色漸層圓圈

搭配 RenderEffect 使用 RuntimeShader

您可以使用 RenderEffectRuntimeShader 套用至父項 View 所有子項檢視畫面。這比繪製自訂 View 更為昂貴。但您可以輕鬆建立「作用」,結合原本使用 createRuntimeShaderEffect 繪製的內容。

Kotlin

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

Java

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

第二個參數是著色器統一的名稱,您可以使用座標參數 (例如 fragCoord 中傳遞) 來 eval 取得 RenderNode (View 及其子項檢視畫面) 的原始色彩,以便執行各種效果。

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
格線混合按鈕
AGSL 格線疊加在按鈕上

混合在按鈕上但位於懸浮動作按鈕下方的格線效果 (因為形狀位於不同的 View 階層)。