Cómo usar AGSL en tu app para Android

En esta página, se describen los conceptos básicos de AGSL y las diferentes formas de usar AGSL en tu dispositivo Android .

Un sombreador AGSL simple

Se llama al código del sombreador para cada píxel dibujado y se muestra el color del píxel con el que se debe pintar. Un sombreador extremadamente simple es aquel que siempre devuelve un solo color; este ejemplo usa rojo. El sombreador se define dentro de 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" +
   "}";

El siguiente paso es crear un RuntimeShader inicializado con la cadena del sombreador. Esto también compila el sombreador.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

RuntimeShader se puede usar en cualquier lugar que pueda usar un sombreador estándar de Android. Como Por ejemplo, puedes usarlo para dibujar en un View personalizado con 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
   }
}

Esto dibuja un View rojo. Puedes usar un uniform para pasar un parámetro de color a el sombreador que se dibujará. Primero, agrega el color uniform al sombreador:

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

Luego, llama a setColorUniform desde tu View personalizado para pasar el color deseado. al sombreador AGSL.

Kotlin

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

Java

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

Ahora, obtienes un elemento View verde; el color View se controla con un del código de tu View personalizado en lugar de estar incorporado en el sombreador.

En su lugar, puedes crear un efecto de gradiente de color. Primero deberás cambiar el sombreador para aceptar la resolución View como entrada:

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

Cómo dibujar el gradiente

Este sombreador hace algo un poco elaborado. Para cada píxel, crea un float2. vector que contiene las coordenadas x e y divididas por la resolución, que creará un valor entre cero y uno. Luego, usa ese vector ajustado para construir los componentes rojo y verde del color de retorno.

Para pasar la resolución de View a un sombreador uniform de AGSL, llama a 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);
   }
}
Gradiente de rojo y verde
gradiente de rojo y verde

Cómo animar el sombreador

Puedes usar una técnica similar para animar el sombreador modificándolo para recibir uniformes iTime y iDuration. El sombreador usará estos valores para crear un onda triangular para los colores, lo que hace que alternan entre sus valores de gradiente.

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

Desde el código fuente de la vista personalizada, se crea ValueAnimator actualiza la iTime uniforme.

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());
   }
});
Gradiente animado de rojo y verde
gradiente animado en rojo y verde

Pintar objetos complejos

No es necesario que dibujes el sombreador para llenar el fondo. puede ser usarse en cualquier lugar que acepte una Paint, como 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);
Texto con gradiente animado en rojo y verde
Texto con gradiente animado en rojo y verde

Transformaciones de sombreado y lienzo

Puedes aplicar transformaciones Canvas adicionales al texto sombreado, como el siguiente ejemplo: y la rotación de claves. En ValueAnimator, puedes actualizar una matriz para rotaciones 3D. con la función 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);

Como quieres rotar el texto desde el eje central en lugar de hacerlo desde la esquina, obtén los límites del texto y, luego, usa preTranslate y postTranslate para modificar el para traducir el texto de modo que 0,0 sea el centro de la rotación sin cambiar la posición en la que se dibuja el texto en la pantalla.

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();
Texto en gradiente animado giratorio en rojo y verde
Texto animado con gradiente giratorio de rojo y verde

Cómo usar RuntimeShader con Jetpack Compose

Es aún más fácil usar RuntimeShader si renderizas tu IU con Jetpack Compose Comenzando con el mismo sombreador de gradientes de antes del:

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

Puedes aplicar ese sombreador a un ShaderBrush Tú Luego, usa ShaderBrush como parámetro para los comandos de dibujo dentro de tu Alcance de dibujo de 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)
}
Círculo gradiente de AGSL Compose
Círculo con gradiente de rojo y verde
.

Cómo usar RuntimeShader con RenderEffect

Puedes usar RenderEffect para aplicar un RuntimeShader a un elemento superior View y todas las vistas secundarias. Esto es más costoso que dibujar un View personalizado. pero te permite crear fácilmente un efecto que incorpore lo que habría originalmente se dibujó con createRuntimeShaderEffect

Kotlin

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

Java

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

El segundo parámetro es el nombre de un uniforme de sombreador que puedes eval con un parámetro de coordenadas (como el que se pasó en fragCoord) para obtener el color original de los RenderNode (la vista y su elemento secundario vistas), lo que te permite realizar todo tipo de efectos.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Cuadrícula combinada sobre botón
Botón de cuadrícula AGSL combinada
.

Un efecto de cuadrícula sobre un botón, pero debajo de un botón de acción flotante (ya que está en una jerarquía de View diferente).