Utiliser AGSL dans votre application Android

Cette page présente les principes de base d'AGSL et explique comment l'utiliser dans votre application Android.

Un nuanceur AGSL simple

Le code du nuanceur est appelé pour chaque pixel dessiné et renvoie la couleur avec laquelle le pixel doit être peint. Un nuanceur extrêmement simple est un nuanceur qui renvoie toujours une seule couleur. Cet exemple utilise le rouge. Le nuanceur est défini dans un 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" +
   "}";

L'étape suivante consiste à créer un objet RuntimeShader initialisé avec votre chaîne de nuanceur. Le nuanceur est également compilé.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Votre RuntimeShader peut être utilisé partout où un nuanceur Android standard le permet. Par exemple, vous pouvez l'utiliser pour dessiner dans un View personnalisé à l'aide d'un 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
   }
}

Cela permet d'afficher une View rouge. Vous pouvez utiliser un uniform pour transmettre un paramètre de couleur au nuanceur à dessiner. Tout d'abord, ajoutez la couleur uniform au nuanceur:

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

Appelez ensuite setColorUniform à partir de votre View personnalisé pour transmettre la couleur souhaitée au nuanceur AGSL.

Kotlin

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

Java

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

Vous obtenez maintenant une View verte. La couleur View est contrôlée à l'aide d'un paramètre du code de votre View personnalisée au lieu d'être intégrée dans le nuanceur.

Vous pouvez créer un effet de dégradé de couleurs à la place. Vous devez d'abord modifier le nuanceur pour accepter la résolution View en tant qu'entrée:

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

Dessiner le dégradé

Ce nuanceur est un peu sophistiqué. Pour chaque pixel, il crée un vecteur float2 contenant les coordonnées x et y divisées par la résolution, ce qui crée une valeur comprise entre zéro et un. Il utilise ensuite ce vecteur mis à l'échelle pour construire les composants rouge et vert de la couleur renvoyée.

Vous transmettez la résolution de View à un nuanceur AGSL uniform en appelant 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);
   }
}
Dégradé rouge et vert
Dégradé rouge et vert

Animer le nuanceur

Vous pouvez utiliser une technique similaire pour animer le nuanceur en le modifiant afin de recevoir des variables uniformes iTime et iDuration. Le nuanceur utilisera ces valeurs pour créer une onde triangulaire pour les couleurs, ce qui entraînera leur cycle d'aller et de retour entre leurs valeurs de dégradé.

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

À partir du code source de la vue personnalisée, une ValueAnimator met à jour la variable uniforme 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());
   }
});
Dégradé animé rouge et vert
Dégradé animé rouge et vert

Peindre des objets complexes

Vous n'avez pas besoin de dessiner le nuanceur pour remplir l'arrière-plan. Vous pouvez l'utiliser à n'importe quel endroit qui accepte un objet Paint, tel que 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);
Texte en dégradé animé rouge et vert
Texte en dégradé animé rouge et vert

Ombrage et transformations Canvas

Vous pouvez appliquer des transformations Canvas supplémentaires à votre texte ombré, telles que la rotation. Dans ValueAnimator, vous pouvez mettre à jour une matrice pour les rotations 3D à l'aide de la classe intégrée android.graphics.Camera.

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

Étant donné que vous souhaitez faire pivoter le texte depuis l'axe central plutôt que depuis l'angle, obtenez les limites du texte, puis utilisez preTranslate et postTranslate pour modifier la matrice afin de traduire le texte, de sorte que 0,0 soit le centre de la rotation sans modifier la position du texte dessiné à l'écran.

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();
Texte en dégradé animé rotatif rouge et vert
Texte en dégradé animé rotatif rouge et vert

Utiliser RuntimeShader avec Jetpack Compose

Il est encore plus facile d'utiliser RuntimeShader si vous affichez votre UI à l'aide de Jetpack Compose. Commencer avec le même nuanceur de dégradés de l'exemple précédent:

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

Vous pouvez appliquer ce nuanceur à un ShaderBrush. Vous utiliserez ensuite ShaderBrush comme paramètre des commandes de dessin dans le champ d'application de dessin de votre 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)
}
Cercle en dégradé AGSL Compose
Cercle en dégradé rouge et vert

Utiliser RuntimeShader avec RenderEffect

Vous pouvez utiliser RenderEffect pour appliquer un RuntimeShader à un View parent et à toutes les vues enfants. Cela coûte plus cher que de dessiner un View personnalisé. mais il vous permet de créer facilement un effet qui incorpore ce qui aurait été dessiné à l'origine avec createRuntimeShaderEffect.

Kotlin

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

Java

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

Le deuxième paramètre est le nom d'un nuanceur uniforme que vous pouvez eval avec un paramètre de coordonnée (tel que celui transmis dans fragCoord) pour obtenir la couleur d'origine de RenderNode (la vue et ses vues enfants), ce qui vous permet d'effectuer toutes sortes d'effets.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Bouton "Grille mélangée"
Bouton AGSL mélangé

Effet de grille mélangé à un bouton, mais sous un bouton d'action flottant (puisqu'il se trouve dans une hiérarchie View différente).