本頁面說明 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 之間的值。然後使用該縮放向量建構傳回顏色的紅色和綠色元件。
您可以透過呼叫 setFloatUniform
將 View
的解析度傳遞至 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); } }
為著色器加入動畫效果
您可以使用類似的技巧為著色器加入動畫效果,並將其修改為接收 iTime
和 iDuration
制服。著色器會使用這些值建立顏色的三角形波浪,讓顏色在梯度值之間來回循環。
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);
由於您要從中心軸 (而非角落旋轉文字) 旋轉文字,請取得文字邊界,然後使用 preTranslate
和 postTranslate
變更矩陣翻譯文字,讓旋轉的中心位置變成 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)
}
搭配 RenderEffect 使用 RuntimeShader
您可以使用 RenderEffect
將 RuntimeShader
套用至父項 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);
混合在按鈕上但位於懸浮動作按鈕下方的格線效果 (因為形狀位於不同的 View
階層)。