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