En esta página, se abordan los conceptos básicos de AGSL y las diferentes maneras de usarlo en tu app para Android.
Un sombreador AGSL simple
Se llama a tu código de sombreador para cada píxel dibujado y muestra el color con el que se debería pintar el píxel. Un sombreador extremadamente simple es aquel que siempre muestra un solo color; en este ejemplo, se usa el 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 objeto RuntimeShader
inicializado con tu string de sombreador. Esto también compila el sombreador.
Kotlin
val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)
Java
RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);
Se puede usar RuntimeShader
en cualquier lugar donde haya un sombreador estándar de Android. Por ejemplo, puedes usarlo para dibujar en un View
personalizado usando 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 al 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 View
verde; el color View
se controla con un parámetro del código en tu View
personalizado, en lugar de incorporarse en el sombreador.
En su lugar, puedes crear un efecto de gradiente de color. Primero deberás cambiar el sombreador para que acepte 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 vector float2
que contiene las coordenadas x e y divididas por la resolución, lo que creará un valor entre cero y uno. Luego, usa ese vector ajustado para construir los componentes rojo y verde del color que se muestra.
Para pasar la resolución del View
a un sombreador de AGSL uniform
, 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); } }
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 a fin de crear una onda triangular para los colores, lo que hará que fluyan entre los valores de sus gradientes.
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, un ValueAnimator
actualiza el uniforme de 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()); } });
Pinta objetos complejos
No es necesario que dibujes el sombreador para rellenar el fondo; puedes usarlo en cualquier lugar que acepte un objeto 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);
Transformaciones de sombreado y lienzo
Puedes aplicar transformaciones Canvas
adicionales en tu texto sombreado, como la rotación. En ValueAnimator
, puedes actualizar una matriz para rotaciones 3D mediante la clase android.graphics.Camera
integrada.
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);
Dado que deseas rotar el texto desde el eje central en lugar de la esquina, obtén los límites del texto y, luego, usa preTranslate
y postTranslate
para modificar la matriz a fin de traducirlo 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();
Cómo usar RuntimeShader con Jetpack Compose
Es aún más fácil usar RuntimeShader
si renderizas tu IU con Jetpack Compose. Comienza con el mismo sombreador de gradientes de antes:
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
. Luego, usa ShaderBrush
como parámetro para los comandos de dibujo dentro del alcance de dibujo de tu 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ómo usar RuntimeShader con RenderEffect
Puedes usar RenderEffect
para aplicar un RuntimeShader
a un View
superior y a todas las vistas secundarias. Esto es más costoso que dibujar un View
personalizado. pero te permite crear fácilmente un efecto que incorpora lo que se habría dibujado originalmente 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 sombreador uniforme que puedes usar en eval
con un parámetro de coordenadas (como el que se pasó en fragCoord) para obtener el color original de RenderNode
(la vista y sus vistas secundarias), 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);
Efecto de cuadrícula mezclado sobre un botón, pero debajo de un botón de acción flotante (ya que está en una jerarquía View
diferente).