Android アプリでの AGSL の使用

このページでは、AGSL の基本と、Android アプリで AGSL を使用するさまざまな方法について説明します。

シンプルな AGSL シェーダー

シェーダーのコードは、描画されるピクセルごとに呼び出され、ピクセルを描画する色を返します。常に単一の色を返す非常にシンプルなシェーダーです。この例では、赤を使用しています。シェーダーは、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" +
   "}";

次のステップでは、シェーダー文字列で初期化される RuntimeShader オブジェクトを作成します。これにより、シェーダーもコンパイルされます。

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

RuntimeShader は、標準の Android シェーダーを使用できる場所で使用できます。たとえば、Canvas を使用してカスタム View に描画できます。

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
   }
}

これにより、赤色の View が描画されます。uniform を使用して、描画するシェーダーに色パラメータを渡すことができます。まず、シェーダーに色 uniform を追加します。

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

次に、カスタム View から setColorUniform を呼び出して、目的の色を AGSL シェーダーに渡します。

Kotlin

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

Java

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

これで、緑色の View が得られます。View の色は、シェーダーに埋め込まれるのではなく、カスタム View のコードのパラメータを使用して制御されます。

代わりに、カラー グラデーション効果を作成できます。まず、View 解像度を入力として受け入れるようにシェーダーを変更する必要があります。

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

グラデーションの描画

このシェーダーは、特別な処理を行います。ピクセルごとに、x 座標と y 座標を解像度で割った float2 ベクトルが作成されます。これにより、0 ~ 1 の値が作成されます。スケーリングされたベクトルを使用して、戻り色の赤と緑のコンポーネントを作成します。

setFloatUniform を呼び出して、View の解像度を AGSL シェーダー uniform に渡します。

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);
   }
}
赤と緑のグラデーション
赤と緑のグラデーション

シェーダーのアニメーション化

同様の手法で、iTimeiDuration のユニフォームを受け取るように変更することで、シェーダーをアニメーション化できます。シェーダーは、これらの値を使用して色の三角波を作成し、グラデーション値を前後に繰り返します。

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

カスタムビューのソースコードから、ValueAnimatoriTime ユニフォームを更新します。

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());
   }
});
赤と緑のアニメーション グラデーション
赤と緑のアニメーション付きグラデーション

複雑なオブジェクトのペイント

背景を埋めるためにシェーダーを描画する必要はありません。drawText などの Paint オブジェクトを受け入れる任意の場所で使用できます。

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);
赤と緑のアニメーション付きグラデーション テキスト
赤と緑のアニメーション化グラデーション テキスト

シェーディング変換とキャンバス変換

色付きのテキストには、回転などの Canvas 変換を追加で適用できます。ValueAnimator では、組み込みの android.graphics.Camera クラスを使用して 3D 回転のマトリックスを更新できます。

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

テキストを隅からではなく中心軸から回転させるため、テキストの境界を取得し、preTranslatepostTranslate を使用して行列を変更し、0,0 が画面上でテキストが描画される位置を変更せずに回転の中心となるようにテキストを変換します。

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();
赤と緑の回転アニメーション付きグラデーション テキスト
赤と緑の回転アニメーション付きグラデーション テキスト

Jetpack Compose で RuntimeShader を使用する

Jetpack Compose を使用して UI をレンダリングしている場合は、RuntimeShader を簡単に使用できます。前と同じグラデーション シェーダーから始めます。

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

このシェーダーは ShaderBrush に適用できます。次に、Canvas の描画スコープ内で、描画コマンドのパラメータとして ShaderBrush を使用します。

// 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)
}
AGSL Compose のグラデーション 円
赤と緑のグラデーションの円

RenderEffect とともに RuntimeShader を使用する

RenderEffect を使用して、RuntimeShader を親の View ビューとすべての子ビューに適用できます。これは、カスタムの View を描画するよりもコストがかかります。ただし、createRuntimeShaderEffect を使用して元の描画内容を組み込む効果を簡単に作成できます。

Kotlin

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

Java

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

2 つ目のパラメータはシェーダー ユニフォームの名前です。これに eval で座標パラメータ(fragCoord で渡されたものなど)を指定して、RenderNode(ビューとその子ビュー)の元の色を取得し、あらゆる種類のエフェクトを実行できます。

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
グリッドをボタンにブレンドしました
ボタンに統合された AGSL グリッド

グリッド エフェクト。ボタンの上には混在するが、フローティング アクション ボタンの下に混在する(View 階層が異なるため)。