本页面介绍了 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
向量,这会创建一个介于零和一之间的值。然后,它使用该缩放向量来构建返回颜色的红色和绿色分量。
您通过调用 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
uniform。着色器将使用这些值创建颜色的三角波,使其在渐变值之间来回循环。
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
uniform。
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()); } });

绘制复杂对象
您不必绘制着色器来填充背景;它可以用在接受 Paint
对象的任何地方,例如 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);

着色和 Canvas 变换
您可以对带阴影的文本应用额外的 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();

将 RuntimeShader 与 Jetpack Compose 结合使用
如果您使用 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
。然后,您可以使用 ShaderBrush
作为 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)
}

将 RuntimeShader 与 RenderEffect 结合使用
您可以使用 RenderEffect
将 RuntimeShader
应用于父 View
*和*所有子 View。这比绘制自定义 View
的开销更大,但它可让您轻松创建一个效果,该效果整合了本来会使用 createRuntimeShaderEffect
绘制的内容。
Kotlin
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))
Java
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));
第二个参数是着色器 uniform 的名称,您可以使用坐标参数(例如传入的 fragCoord)通过它进行 eval
,以获取 RenderNode
(View 及其子 View)的原始颜色,从而让您能够执行各种效果。
uniform shader background; // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);

一个与按钮混合的网格效果,但位于浮动操作按钮下方(因为它位于不同的 View
层次结构中)。