AGSL in Ihrer Android-App verwenden

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);
   }
}
Rot-Grün-Farbverlauf
Roter und grüner Farbverlauf

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());
   }
});
Animierter Farbverlauf in Rot und Grün
Animierter roter und grüner Farbverlauf

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);
Animierter roter und grüner Farbverlaufstext
Animierter roter und grüner Text mit Farbverlauf

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();
Rotierender animierter Text mit animiertem Farbverlauf in Rot und Grün
Rotierender und grüner animierter Text mit animiertem Farbverlauf

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)
}
Farbverlaufskreis für AGSL Compose
Roter und grüner Kreis mit Farbverlauf

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);
Raster über Schaltfläche eingeblendet
Über die Schaltfläche eingeblendetes AGSL-Raster

Ein Rastereffekt, der über einer Schaltfläche, aber unter einer unverankerten Aktionsschaltfläche liegt (da sie sich in einer anderen View-Hierarchie befindet).