Utilizzare AGSL nell'app per Android

Questa pagina illustra le nozioni di base di AGSL e i vari modi per utilizzarlo in Android dell'app.

Un semplice Shar AGSL

Il codice dello shaker viene chiamato per ogni pixel disegnato e restituisce il colore del pixel deve essere dipinto. Uno Shar estremamente semplice è un tipo che ritorna sempre un solo colore; in questo esempio viene usato il rosso. Lo streamr è definito all'interno di un String.

KotlinJava
private const val COLOR_SHADER_SRC =
   
"""half4 main(float2 fragCoord) {
      return half4(1,0,0,1);
   }"""

private static final String COLOR_SHADER_SRC =
   
"half4 main(float2 fragCoord) {\n" +
     
"return half4(1,0,0,1);\n" +
   
"}";

Il passaggio successivo consiste nel creare una RuntimeShader inizializzato con la stringa shaker. In questo modo viene compilato anche lo shaker.

KotlinJava
val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)
RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Il tuo RuntimeShader può essere utilizzato ovunque possa fare uno shaker Android standard. Come ad esempio, puoi usarlo per disegnare in un elemento View personalizzato utilizzando Canvas

KotlinJava
val paint = Paint()
paint
.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas
?.let {
      canvas
.drawPaint(paint) // fill the Canvas with the shader
   
}
}
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
   
}
}

In questo modo viene tracciata una View rossa. Puoi utilizzare un uniform per passare un parametro di colore a lo shaker da disegnare. Innanzitutto, aggiungi il colore uniform allo shaker:

KotlinJava
private const val COLOR_SHADER_SRC =
"""layout(color) uniform half4 iColor;
   half4 main(float2 fragCoord) {
      return iColor;
   }"""

private static final String COLOR_SHADER_SRC =
   
"layout(color) uniform half4 iColor;\n"+
     
"half4 main(float2 fragCoord) {\n" +
     
"return iColor;\n" +
   
"}";

Poi, chiama setColorUniform dal tuo View personalizzato per trasmettere il colore desiderato nello streamr AGSL.

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

Ora, ottieni un View verde; il colore View viene controllato utilizzando un del codice nel tuo asset View personalizzato anziché essere incorporato nel Shar.

In alternativa, puoi creare un effetto a gradiente di colore. Devi prima modificare lo shaker accetti la risoluzione View come input:

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

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

Disegno del gradiente

Questo Shar fa qualcosa di leggermente particolare. Per ogni pixel, viene creato un float2 vettore contenente le coordinate x e y divise per la risoluzione, creerà un valore compreso tra zero e uno. Quindi utilizza il vettore scalato i componenti rosso e verde del colore restituito.

Puoi passare la risoluzione di View in uno shaker AGSL uniform chiamando setFloatUniform.

KotlinJava
val paint = Paint()
paint
.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas
?.let {
      fixedColorShader
.setFloatUniform("iResolution", width.toFloat(), height.toFloat())
      canvas
.drawPaint(paint)
   
}
}
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 rosso e verde
Sfumatura rosso e verde

Animazione dello shaker

Puoi utilizzare una tecnica simile per animare lo shaker modificandolo in modo da ricevere le uniformi di iTime e iDuration. Lo shaker userà questi valori per creare un'onda triangolare per i colori, che li fa scorrere avanti e indietro attraverso i valori del gradiente.

KotlinJava
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);
   }
"""

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

Nel codice sorgente della visualizzazione personalizzata, ValueAnimator aggiorna la uniforme da iTime.

KotlinJava
// 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()
// 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 animato rosso e verde
Sfumatura animato rosso e verde

Pittura di oggetti complessi

Non è necessario disegnare lo shaker per riempire lo sfondo; può essere utilizzate in qualsiasi luogo che accetti Oggetto Paint, ad esempio drawText.

KotlinJava
canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint
)
canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint
);
Testo gradiente animato rosso e verde
Testo a gradiente animato rosso e verde

Trasformazioni di ombreggiatura e canvas

Puoi applicare ulteriori trasformazioni Canvas al testo ombreggiato, ad esempio la rotazione. In ValueAnimator, puoi aggiornare una matrice per le rotazioni 3D usando i modelli android.graphics.Camera.

KotlinJava
// in the ValueAnimator
camera
.rotate(0.0f, animation.animatedValue as Float / DURATION * 360f, 0.0f)
// in the ValueAnimator
camera
.rotate(0.0f, (Float)animation.getAnimatedValue() / DURATION * 360f, 0.0f);

Poiché vuoi ruotare il testo dall'asse centrale anziché dall'angolo, ottenere i limiti di testo e quindi usare preTranslate e postTranslate per modificare per tradurre il testo in modo che 0,0 sia il centro della rotazione senza cambiare la posizione in cui il testo viene disegnato sullo schermo.

KotlinJava
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()
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();
Testo gradiente animato rotante rosso e verde
Testo gradiente animato rosso e verde a rotazione

Utilizzo di RuntimeShader con Jetpack Compose

È ancora più facile usare RuntimeShader se esegui il rendering dell'UI con Jetpack Compose. A partire dallo stesso Shader gradiente prima del:

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

Puoi applicare lo shaker a una ShaderBrush Tu poi usa ShaderBrush come parametro per i comandi di disegno all'interno delle Ambito di disegno di 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)
}
Cerchio gradiente AGSL Compose
Cerchio sfumato rosso e verde

Utilizzo di RuntimeShader con RenderEffect

Puoi utilizzare RenderEffect per applicare uno RuntimeShader a un genitore View e tutte le viste secondarie. È più costoso che disegnare un View personalizzato. ma consente di creare facilmente un effetto che incorpora ciò che sono state originariamente disegnate createRuntimeShaderEffect

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

Il secondo parametro è il nome di un'uniforme shaker che puoi eval con un (come quello passato in fragCoord) per ottenere il colore originale del RenderNode (la Vista e le relative schede secondarie di visualizzazione), consentendoti di eseguire qualsiasi tipo di effetto.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Pulsante Griglia combinata sopra
Pulsante AGSL combinato con griglia

Effetto griglia combinato su un pulsante, ma sotto un pulsante di azione mobile (poiché si trova in una gerarchia View diversa).