Trang này trình bày kiến thức cơ bản về AGSL và các cách sử dụng AGSL trong ứng dụng Android.
Chương trình đổ bóng AGSL đơn giản
Mã chương trình đổ bóng của bạn được gọi cho mỗi pixel được vẽ và trả về màu mà pixel sẽ được vẽ. Chương trình đổ bóng cực kỳ đơn giản là chương trình luôn trả về một màu duy nhất; ví dụ này sử dụng màu đỏ. Chương trình đổ bóng được xác định bên trong 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" + "}";
Bước tiếp theo là tạo một đối tượng RuntimeShader
được khởi chạy bằng chuỗi chương trình đổ bóng. Thao tác này cũng biên dịch chương trình đổ bóng.
Kotlin
val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)
Java
RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);
Bạn có thể dùng RuntimeShader
ở mọi nơi mà chương trình đổ bóng Android tiêu chuẩn có thể. Ví dụ: bạn có thể sử dụng đối tượng này để vẽ vào View
tuỳ chỉnh bằng cách sử dụng 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 } }
Thao tác này sẽ vẽ một View
màu đỏ. Bạn có thể sử dụng uniform
để truyền một tham số màu vào chương trình đổ bóng cần vẽ. Trước tiên, hãy thêm màu uniform
vào chương trình đổ bóng:
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" + "}";
Sau đó, hãy gọi setColorUniform
từ View
tuỳ chỉnh để truyền màu mong muốn vào chương trình đổ bóng AGSL.
Kotlin
fixedColorShader.setColorUniform("iColor", Color.GREEN )
Java
fixedColorShader.setColorUniform("iColor", Color.GREEN );
Giờ đây, bạn sẽ nhận được View
màu xanh lục; màu View
được kiểm soát bằng cách sử dụng một tham số từ mã trong View
tuỳ chỉnh thay vì được nhúng trong chương trình đổ bóng.
Thay vào đó, bạn có thể tạo hiệu ứng chuyển màu. Trước tiên, bạn cần thay đổi chương trình đổ bóng để chấp nhận độ phân giải View
làm dữ liệu đầu vào:
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" + "}";
Vẽ dải chuyển màu
Chương trình đổ bóng này có vẻ hơi lạ. Đối với mỗi pixel, vectơ này sẽ tạo một vectơ float2
chứa các toạ độ x và y chia cho độ phân giải. Khi đó, vectơ này sẽ tạo ra một giá trị từ 0 đến 1. Sau đó, phương thức này sử dụng vectơ được điều chỉnh theo tỷ lệ đó để tạo các thành phần màu đỏ và màu xanh lục của màu trả về.
Bạn truyền độ phân giải của View
vào uniform
chương trình đổ bóng AGSL bằng cách gọi 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); } }
Tạo ảnh động cho chương trình đổ bóng
Bạn có thể sử dụng một kỹ thuật tương tự để tạo ảnh động cho chương trình đổ bóng bằng cách sửa đổi để nhận được đồng nhất iTime
và iDuration
. Chương trình đổ bóng sẽ sử dụng các giá trị này để tạo sóng tam giác cho các màu, khiến các màu đó chuyển đổi qua lại giữa các giá trị chuyển màu.
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"+ "}";
Từ mã nguồn khung hiển thị tuỳ chỉnh, ValueAnimator
sẽ cập nhật đồng nhất 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()); } });
Vẽ các vật thể phức tạp
Bạn không cần phải vẽ chương trình đổ bóng để tô nền; bạn có thể dùng chương trình này ở bất kỳ nơi nào chấp nhận đối tượng Paint
, chẳng hạn như 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);
Chuyển đổi hiệu ứng đổ bóng và Canvas
Bạn có thể áp dụng các phép biến đổi Canvas
khác trên văn bản được tô bóng, chẳng hạn như xoay. Trong ValueAnimator
, bạn có thể cập nhật ma trận cho chế độ xoay 3D bằng cách sử dụng lớp android.graphics.Camera
tích hợp sẵn.
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);
Vì bạn muốn xoay văn bản từ trục giữa thay vì từ góc, hãy lấy ranh giới văn bản, sau đó sử dụng preTranslate
và postTranslate
để thay đổi ma trận nhằm dịch văn bản sao cho 0,0 là tâm xoay mà không thay đổi vị trí văn bản được vẽ trên màn hình.
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();
Sử dụng RuntimeShader với Jetpack Compose
Nếu bạn kết xuất giao diện người dùng bằng Jetpack Compose, thì việc sử dụng RuntimeShader
sẽ còn dễ dàng hơn nữa. Bắt đầu bằng cùng một chương trình đổ bóng chuyển màu từ trư��c:
private const val COLOR_SHADER_SRC =
"""uniform float2 iResolution;
half4 main(float2 fragCoord) {
float2 scaled = fragCoord/iResolution.xy;
return half4(scaled, 0, 1);
}"""
Bạn có thể áp dụng chương trình đổ bóng đó cho ShaderBrush
. Sau đó, bạn sẽ dùng ShaderBrush
làm tham số cho các lệnh vẽ trong phạm vi vẽ của 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)
}
Sử dụng RuntimeShader với RenderEffect
Bạn có thể sử dụng RenderEffect
để áp dụng RuntimeShader
cho View
mẹ và tất cả các khung hiển thị con. Thao tác này tốn kém hơn so với việc vẽ một View
tuỳ chỉnh. nhưng cho phép bạn dễ dàng tạo hiệu ứng kết hợp những gì ban đầu đã được vẽ bằng createRuntimeShaderEffect
.
Kotlin
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))
Java
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));
Tham số thứ hai là tên của đồng nhất trong chương trình đổ bóng mà bạn có thể eval
bằng tham số toạ độ (chẳng hạn như được truyền vào fragCoord) để lấy màu gốc của RenderNode
(Khung hiển thị và các khung hiển thị con), cho phép bạn thực hiện mọi loại hiệu ứng.
uniform shader background; // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Hiệu ứng lưới được kết hợp trên một nút, nhưng ở bên dưới một nút hành động nổi (vì nút này nằm trong một hệ phân cấp View
khác).