このページでは、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); } }
シェーダーのアニメーション化
同様の手法で、iTime
と iDuration
のユニフォームを受け取るように変更することで、シェーダーをアニメーション化できます。シェーダーは、これらの値を使用して色の三角波を作成し、グラデーション値を前後に繰り返します。
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"+ "}";
カスタムビューのソースコードから、ValueAnimator
が 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()); } });
複雑なオブジェクトのペイント
背景を埋めるためにシェーダーを描画する必要はありません。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);
テキストを隅からではなく中心軸から回転させるため、テキストの境界を取得し、preTranslate
と postTranslate
を使用して行列を変更し、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)
}
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);
グリッド エフェクト。ボタンの上には混在するが、フローティング アクション ボタンの下に混在する(View
階層が異なるため)。