Como usar a AGSL no seu app Android

Esta página aborda os conceitos básicos da AGSL e diferentes maneiras de usar a AGSL no Android app.

Um sombreador simples da AGSL

O código do shader é chamado para cada pixel desenhado e retorna a cor do pixel deve ser pintada. Um sombreador extremamente simples é aquele que sempre retorna uma única cor; neste exemplo usa 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 RuntimeShader. inicializado com a string de sombreador. Isso também compila o sombreador.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

O RuntimeShader pode ser usado em qualquer lugar em que um sombreador padrão do Android possa. Como exemplo, ele pode ser usado para desenhar em uma View personalizada usando uma 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 uma View vermelha. Você pode usar um uniform para transmitir um parâmetro de cor ao o sombreador a 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. no sombreador da AGSL.

Kotlin

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

Java

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

Agora, você recebe uma View verde. a cor View é controlada usando um do código no View personalizado em vez de ser incorporado na sombreador.

Em vez disso, você pode criar um efeito de gradiente de cor. Primeiro, você precisará alterar 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" +
   "}";

Desenhar o gradiente

Esse shader é um pouco sofisticado. Para cada pixel, ele cria um float2. vetor que contém as coordenadas x e y divididas pela resolução, que vai criar um valor entre zero e um. Em seguida, ele usa esse vetor dimensionado construa os componentes vermelho e verde da cor de retorno.

Transmita a resolução de View para um shader uniform 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 vermelho e verde
Gradiente vermelho e verde

Como animar o sombreador

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

No código-fonte da visualização personalizada, O 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. pode ser usada 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 gradiente animado em vermelho e verde
Texto gradiente animado em vermelho e verde

Transformações de sombreamento e tela

É possível aplicar outras transformações Canvas ao texto sombreado, como a rotação de chaves. No ValueAnimator, é possível atualizar uma matriz para rotações 3D usando o classe 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 você deseja girar o texto a partir do eixo central e não do canto, obter os limites de texto e, em seguida, usar preTranslate e postTranslate para alterar a matriz para traduzir o texto de modo que 0,0 seja o centro da rotação sem alterando 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 gradiente animado girando em vermelho e verde
Texto gradiente animado em vermelho e verde em rotação

Como usar o RuntimeShader com o Jetpack Compose

Fica ainda mais fácil usar RuntimeShader se você renderiza a interface usando Jetpack Compose. Começando com o mesmo sombreador de gradiente antes de:

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 shader a um ShaderBrush Você Em seguida, use o ShaderBrush como um parâmetro para os comandos de desenho no Escopo do desenho 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 em gradiente da AGSL Compose
Círculo gradiente vermelho e verde

Como usar o RuntimeShader com RenderEffect

Você pode usar RenderEffect para aplicar uma RuntimeShader como pai View e todas as visualizações filhas. Isso é mais caro do que desenhar uma View personalizada. mas permite que você crie facilmente um efeito que incorpora o que teria originalmente desenhada 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 shader que pode ser eval com uma Coordenado do parâmetro (como o passado em fragCoord) para obter a cor original da RenderNode (a visualização e as filhas dela) de visualização), permitindo que você realize 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 misturada sobre"
Botão da AGSL mesclado na grade

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