Auf dieser Seite werden die Grundlagen von AGSL und verschiedene Möglichkeiten zur Verwendung von AGSL in Ihrer Android-App beschrieben.
Ein einfacher AGSL-Shader
Ihr Shader-Code wird für jedes gezeichnete Pixel aufgerufen und gibt die Farbe zurück, mit der das Pixel gezeichnet werden soll. Ein extrem einfacher Shader gibt immer eine einzelne Farbe zurück. In diesem Beispiel wird Rot verwendet. Der Shader wird innerhalb eines String
definiert.
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" + "}";
Im nächsten Schritt erstellen Sie ein RuntimeShader
-Objekt, das mit Ihrem Shader-String initialisiert wird. Dadurch wird auch der Shader kompiliert.
Kotlin
val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)
Java
RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);
RuntimeShader
kann überall verwendet werden, wo ein Standard-Android-Shader es kann. Sie können es beispielsweise verwenden, um mit einem Canvas
ein benutzerdefiniertes View
zu zeichnen.
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 } }
Dadurch wird ein rotes View
gezeichnet. Mit einem uniform
können Sie einen Farbparameter an den zu zeichnenden Shader übergeben. Fügen Sie dem Shader zuerst die Farbe uniform
hinzu:
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" + "}";
Rufen Sie dann setColorUniform
aus Ihrer benutzerdefinierten View
auf, um die gewünschte Farbe an den AGSL-Shader zu übergeben.
Kotlin
fixedColorShader.setColorUniform("iColor", Color.GREEN )
Java
fixedColorShader.setColorUniform("iColor", Color.GREEN );
Jetzt erhalten Sie ein grünes View
. Die Farbe View
wird mit einem Parameter aus dem Code in Ihrer benutzerdefinierten View
gesteuert, anstatt in den Shader eingebettet zu werden.
Sie können stattdessen einen Farbverlaufseffekt erstellen. Sie müssen zuerst den Shader ändern, um die View
-Auflösung als Eingabe zu akzeptieren:
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" + "}";
Farbverlauf zeichnen
Dieser Shader funktioniert etwas Raffiniertes. Für jedes Pixel wird ein float2
-Vektor erstellt, der die x- und y-Koordinaten geteilt durch die Auflösung enthält. Dadurch ergibt sich ein Wert zwischen null und eins. Anschließend werden mit diesem skalierten Vektor die roten und grünen Komponenten der Rückgabefarbe erstellt.
Sie übergeben die Auflösung von View
an einen AGSL-Shader uniform
, indem Sie setFloatUniform
aufrufen.
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); } }
Shader animieren
Mit einem ähnlichen Verfahren können Sie den Shader animieren, indem Sie ihn so ändern, dass er iTime
- und iDuration
-Uniformen empfängt. Der Shader verwendet diese Werte, um eine dreieckige Welle für die Farben zu erstellen, wodurch sie zwischen ihren Farbverlaufswerten hin und her wechseln.
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"+ "}";
Im Quellcode der benutzerdefinierten Ansicht wird die iTime
-Einheit durch einen ValueAnimator
aktualisiert.
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()); } });
Komplexe Objekte malen
Sie müssen den Shader nicht zeichnen, um den Hintergrund zu füllen. Er kann überall verwendet werden, wo ein Paint
-Objekt akzeptiert wird, z. B. 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);
Shading- und Canvas-Transformationen
Sie können weitere Canvas
-Transformationen auf den schattierten Text anwenden, z. B. die Rotation. In der ValueAnimator
können Sie mithilfe der integrierten Klasse android.graphics.Camera
eine Matrix für 3D-Rotationen aktualisieren.
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);
Da Sie den Text von der Mittelachse aus drehen möchten, rufen Sie die Textgrenzen ab und verwenden Sie dann preTranslate
und postTranslate
, um die Matrix so zu verschieben, dass 0,0 der Mittelpunkt der Drehung ist, ohne die Position zu ändern, an der der Text auf dem Bildschirm gezeichnet wird.
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 mit Jetpack Compose verwenden
Die Verwendung von RuntimeShader
ist noch einfacher, wenn Sie Ihre UI mit Jetpack Compose rendern. Beginnend mit demselben Gradienten-Shader wie zuvor:
private const val COLOR_SHADER_SRC =
"""uniform float2 iResolution;
half4 main(float2 fragCoord) {
float2 scaled = fragCoord/iResolution.xy;
return half4(scaled, 0, 1);
}"""
Sie können diesen Shader auf einen ShaderBrush
anwenden. Anschließend verwenden Sie ShaderBrush
als Parameter für die Zeichenbefehle im Zeichenbereich von 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)
}
RuntimeShader mit RenderEffect verwenden
Mit RenderEffect
können Sie RuntimeShader
auf ein übergeordnetes Element View
und alle untergeordneten Ansichten anwenden. Das ist teurer als das Zeichnen einer benutzerdefinierten View
. Sie können damit aber ganz einfach einen Effekt erzeugen, der berücksichtigt, was ursprünglich mit createRuntimeShaderEffect
gezeichnet worden wäre.
Kotlin
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))
Java
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));
Der zweite Parameter ist der Name einer Shader-Einheit. Sie können sie mit einem Koordinatenparameter (z. B. dem in fragCoord übergebenen) eval
, um die ursprüngliche Farbe des RenderNode
(der Ansicht und ihrer untergeordneten Ansichten) zu erhalten. Damit können Sie verschiedene Effekte anwenden.
uniform shader background; // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Ein Rastereffekt, der über einer Schaltfläche, aber unter einer unverankerten Aktionsschaltfläche liegt (da sie sich in einer anderen View
-Hierarchie befindet).