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); } }
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()); } });
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);
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();
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)
}
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);
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).