Como usar a AGSL no seu app Android

Esta página aborda os conceitos básicos da AGSL e diferentes maneiras de usá-la no seu app Android.

Um sombreador simples da AGSL

O código do sombreador é chamado para cada pixel desenhado e retorna a cor com que o pixel precisa ser pintado. Um sombreador extremamente simples é aquele que sempre retorna uma única cor. Neste exemplo, usamos vermelho. O sombreador é definido dentro de um 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" +
   "}";

A próxima etapa é criar um objeto RuntimeShader inicializado com sua string de sombreador. Isso também compila o sombreador.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Seu RuntimeShader pode ser usado em qualquer lugar que um sombreador padrão do Android. Por exemplo, você pode usá-lo para desenhar em uma View personalizada usando um 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
   }
}

Isso desenha um View vermelho. Você pode usar um uniform para transmitir um parâmetro de cor ao sombreador que será desenhado. Primeiro, adicione a cor uniform ao 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" +
   "}";

Em seguida, chame setColorUniform no View personalizado para transmitir a cor desejada para o sombreador da AGSL.

Kotlin

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

Java

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

Agora, você recebe um View verde. A cor do View é controlada usando um parâmetro do código no seu View personalizado em vez de ser incorporada ao sombreador.

Em vez disso, você pode criar um efeito de gradiente de cor. Primeiro, você precisa mudar o sombreador para aceitar a resolução 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" +
   "}";

Como desenhar o gradiente

Este sombreador faz algo um pouco sofisticado. Para cada pixel, ele cria um vetor float2 que contém as coordenadas x e y divididas pela resolução, o que criará um valor entre zero e um. Em seguida, ele usa esse vetor dimensionado para criar os componentes vermelhos e verdes da cor de retorno.

A resolução do View é transmitida para um uniform de sombreador da AGSL chamando 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 vermelho e verde
Gradiente de vermelho e verde

Como animar o sombreador

Você pode usar uma técnica semelhante para animar o sombreador modificando-o para receber uniformes iTime e iDuration. O sombreador vai usar esses valores para criar uma onda triangular para as cores, fazendo com que elas alternem entre os valores do 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"+
   "}";

No código-fonte da visualização personalizada, um ValueAnimator atualiza o 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());
   }
});
Gradiente animado vermelho e verde
Gradiente animado vermelho e verde

Pintar objetos complexos

Não é necessário desenhar o sombreador para preencher o plano de fundo. Ele pode ser usado em qualquer lugar que aceite um 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);
Texto em gradiente animado de vermelho e verde
Texto de gradiente animado vermelho e verde

Transformações de sombreamento e tela

É possível aplicar outras transformações Canvas ao texto sombreado, como rotação. No ValueAnimator, é possível atualizar uma matriz para rotações 3D usando a classe 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);

Como você quer girar o texto a partir do eixo central em vez do canto, receba os limites do texto e use preTranslate e postTranslate para mudar a matriz e traduzir o texto de modo que 0,0 seja o centro da rotação sem mudar a posição em que o texto é desenhado na tela.

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 de gradiente animado em rotação em vermelho e verde
Texto de gradiente animado giratório vermelho e verde

Como usar o RuntimeShader com o Jetpack Compose

É ainda mais fácil usar o RuntimeShader se você estiver renderizando a interface com o Jetpack Compose. Começando com o mesmo sombreador de gradiente 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);
}"""

É possível aplicar esse sombreador a um ShaderBrush. Em seguida, use o ShaderBrush como um parâmetro para os comandos de exibição no escopo de desenho do 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 de gradiente da AGSL Compose
Círculo com gradiente vermelho e verde

Como usar o RuntimeShader com o RenderEffect

Você pode usar RenderEffect para aplicar um RuntimeShader a uma visualização mãe View e todas as visualizações filhas. Isso é mais caro do que desenhar um View personalizado. mas permite que você crie facilmente um efeito que incorpora o que teria sido originalmente desenhado usando createRuntimeShaderEffect.

Kotlin

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

Java

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

O segundo parâmetro é o nome de um uniforme de sombreador que você pode usar eval com um parâmetro de coordenadas (como o transmitido em fragCoord) para conseguir a cor original de RenderNode (a visualização e as visualizações filhas), permitindo que você execute todos os tipos de efeitos.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Botão Grade mesclada sobre
Botão da AGSL misturado sobre o botão

Um efeito de grade misturado com um botão, mas abaixo de um botão de ação flutuante, já que está em uma hierarquia de View diferente.