Utilizzare AGSL nell'app per Android

Questa pagina illustra le nozioni di base di AGSL e i diversi modi per utilizzarlo nella tua app per Android.

Un semplice utente AGSL

Il codice dello ombreggiatore viene richiamato per ogni pixel disegnato e restituisce il colore con cui il pixel deve essere dipinto. Uno ombreggiatore estremamente semplice è quello che restituisce sempre un colore singolo; in questo esempio viene utilizzato il rosso. Lo meshr viene definito all'interno di un elemento 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" +
   "}";

Il passaggio successivo consiste nella creazione di un oggetto RuntimeShader inizializzato con la stringa dello Shader. In questo modo viene compilato anche lo smoothr.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Il tuo RuntimeShader può essere utilizzato ovunque possa essere usato uno screenr Android standard. Ad esempio, puoi utilizzarlo per disegnare in un View personalizzato utilizzando 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
   }
}

Viene disegnata una View rossa. Puoi utilizzare un elemento uniform per passare un parametro color allo Shader da disegnare. Innanzitutto, aggiungi il colore uniform allo mesh:

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

Quindi, chiama setColorUniform dal tuo View personalizzato per passare il colore desiderato allo mesh AGSL.

Kotlin

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

Java

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

Ora ottieni un View verde. Il colore View viene controllato utilizzando un parametro da codice nel tuo View personalizzato invece di essere incorporato nell'shader.

In alternativa, puoi creare un effetto a gradiente di colore. Per prima cosa, devi cambiare lo shadowr per accettare la risoluzione View come input:

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

Disegno del gradiente

Questo meshr fa qualcosa di un po' stravagante. Per ogni pixel, viene creato un vettore float2 contenente le coordinate x e y divise per la risoluzione. In questo modo viene creato un valore compreso tra zero e uno. Poi lo usa per creare le componenti rosse e verdi del colore di ritorno.

Per passare la risoluzione di View a uno mesh AGSL uniform, chiama 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 rosso e verde
Sfumatura rosso e verde

Animazione dello screenr

Puoi utilizzare una tecnica simile per animare lo meshr modificandolo in modo da ricevere le uniformi di iTime e iDuration. Lo Shader utilizzerà questi valori per creare un'onda triangolare per i colori, in modo da farli scorrere avanti e indietro tra i diversi valori di 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"+
   "}";

Dal codice sorgente della visualizzazione personalizzata, un elemento ValueAnimator aggiorna l'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 animato rosso e verde
Gradiente animato rosso e verde

Dipingere oggetti complessi

Non è necessario disegnare lo meshr per riempire lo sfondo; può essere utilizzato in qualsiasi luogo che accetta un oggetto Paint, ad esempio 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);
Testo a gradiente animato rosso e verde
Testo sfumato 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 utilizzando la classe android.graphics.Camera integrata.

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);

Poiché vuoi ruotare il testo dall'asse centrale anziché dall'angolo, recupera i limiti del testo e usa preTranslate e postTranslate per modificare la matrice e tradurre il testo in modo che 0,0 sia il centro della rotazione senza modificare la posizione disegnata sullo schermo.

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

Utilizzo di RuntimeShader con Jetpack Compose

È ancora più facile utilizzare RuntimeShader se esegui il rendering della tua UI con Jetpack Compose. Iniziamo con lo stesso gradiente del precedente:

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 shardr a un ShaderBrush. Potrai quindi usare ShaderBrush come parametro per i comandi di disegno all'interno dell'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 Scrivi
Cerchio gradiente rosso e verde

Utilizzo di RuntimeShader con RenderEffect

Puoi utilizzare RenderEffect per applicare RuntimeShader a un elemento View principale e a tutte le viste secondarie. Questa operazione è più costosa rispetto al disegno di una View personalizzata. ma consente di creare facilmente un effetto che incorpora ciò che sarebbe stato disegnato inizialmente utilizzando createRuntimeShaderEffect.

Kotlin

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

Java

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

Il secondo parametro è il nome di un'uniforme dello ombreggiatore che puoi eval con un parametro di coordinamento (come quello trasmesso in fragCoord) per ottenere il colore originale di RenderNode (la vista e le relative viste secondarie), che ti consente di eseguire tutti i tipi di effetti.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Griglia combinata su pulsante
Griglia AGSL mista sul pulsante

Un effetto griglia mescolato sopra un pulsante, ma sotto un pulsante di azione mobile (dal momento che si trova in una gerarchia View diversa).