Menggunakan AGSL di aplikasi Android

Halaman ini membahas dasar-dasar AGSL dan berbagai cara untuk menggunakan AGSL di aplikasi Android.

Shader AGSL sederhana

Kode shader Anda dipanggil untuk setiap piksel yang digambar, dan menampilkan warna yang harus digunakan untuk menggambar piksel. Shader yang sangat sederhana adalah shader yang selalu menampilkan satu warna; contoh ini menggunakan warna merah. Shader ditentukan di dalam 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" +
   "}";

Langkah berikutnya adalah membuat objek RuntimeShader yang diinisialisasi dengan string shader Anda. Tindakan ini juga akan mengompilasi shader.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

RuntimeShader dapat digunakan di mana pun yang dapat dilakukan shader Android standar. Misalnya, Anda dapat menggunakannya untuk menggambar ke dalam View kustom menggunakan 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
   }
}

Tindakan ini akan menggambar View berwarna merah. Anda dapat menggunakan uniform untuk meneruskan parameter warna ke shader yang akan digambar. Pertama, tambahkan warna uniform ke shader:

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

Kemudian, panggil setColorUniform dari View kustom untuk meneruskan warna yang diinginkan ke shader AGSL.

Kotlin

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

Java

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

Sekarang, Anda mendapatkan View hijau; warna View dikontrol menggunakan parameter dari kode dalam View kustom, bukan disematkan dalam shader.

Sebagai gantinya, Anda dapat membuat efek gradien warna. Pertama-tama, Anda harus mengubah shader untuk menerima resolusi View sebagai 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" +
   "}";

Menggambar gradien

Shader ini melakukan sesuatu yang sedikit mewah. Untuk setiap piksel, kode ini membuat vektor float2 yang berisi koordinat x dan y yang dibagi dengan resolusi, yang akan menghasilkan nilai antara nol dan satu. Kemudian vektor yang diskalakan tersebut digunakan untuk membuat komponen merah dan hijau dari warna yang ditampilkan.

Anda meneruskan resolusi View ke uniform shader AGSL dengan memanggil 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);
   }
}
Gradien merah dan Hijau
Gradien merah dan hijau

Menganimasikan shader

Anda dapat menggunakan teknik serupa untuk menganimasikan shader dengan mengubahnya untuk menerima seragam iTime dan iDuration. Shader akan menggunakan nilai ini untuk membuat gelombang segitiga untuk warna, yang menyebabkannya berganti-ganti nilai gradien.

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

Dari kode sumber tampilan kustom, ValueAnimator akan mengupdate seragam 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());
   }
});
Gradien animasi merah dan Hijau
Gradien animasi Merah dan Hijau

Melukis objek kompleks

Anda tidak perlu menggambar shader untuk mengisi latar belakang. shader dapat digunakan di tempat mana pun yang menerima objek Paint, seperti 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);
Teks gradasi animasi merah dan Hijau
Teks gradasi animasi Merah dan Hijau

Transformasi Shading dan Canvas

Anda dapat menerapkan transformasi Canvas tambahan pada teks berarsir, misalnya rotasi. Di ValueAnimator, Anda dapat memperbarui matriks untuk rotasi 3D menggunakan class android.graphics.Camera bawaan.

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

Karena Anda ingin memutar teks dari sumbu tengah, bukan dari sudut, dapatkan batas teks, lalu gunakan preTranslate dan postTranslate untuk mengubah matriks guna menerjemahkan teks sehingga 0,0 menjadi pusat rotasi tanpa mengubah posisi teks yang digambar di layar.

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();
Teks gradasi animasi berputar Merah dan Hijau
Teks animasi gradien berputar Merah dan Hijau

Menggunakan RuntimeShader dengan Jetpack Compose

Penggunaan RuntimeShader akan jauh lebih mudah jika Anda merender UI menggunakan Jetpack Compose. Memulai dengan shader gradien yang sama dari sebelumnya:

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

Anda dapat menerapkan shader tersebut ke ShaderBrush. Kemudian, gunakan ShaderBrush sebagai parameter untuk perintah gambar dalam cakupan gambar 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)
}
Lingkaran gradien Compose AGSL
Lingkaran gradasi merah dan hijau

Menggunakan RuntimeShader dengan RenderEffect

Anda dapat menggunakan RenderEffect untuk menerapkan RuntimeShader ke View dan semua tampilan turunan. Ini lebih mahal daripada menggambar View kustom. tetapi memungkinkan Anda dengan mudah membuat efek yang menggabungkan apa yang awalnya digambar menggunakan createRuntimeShaderEffect.

Kotlin

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

Java

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

Parameter kedua adalah nama seragam shader yang dapat Anda eval dengan parameter koordinat (seperti yang diteruskan dalam fragCoord) untuk mendapatkan warna asli RenderNode (Tampilan dan tampilan turunannya), yang memungkinkan Anda melakukan segala jenis efek.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Tombol petak digabungkan
Petak AGSL digabungkan di atas tombol

Efek petak tercampur di atas tombol, tetapi di bawah tombol tindakan mengambang (karena berada dalam hierarki View yang berbeda).