שימוש ב-AGSL באפליקציה ל-Android

בדף הזה נסביר את העקרונות הבסיסיים של AGSL ודרכים שונות לשימוש ב-AGSL ב-Android. אפליקציה.

תוכנת הצללה פשוטה של AGSL

המערכת מפעילה את קוד ההצללה לכל פיקסל שצויר ומחזיר את הצבע של הפיקסל צריך לצייר עם. כלי ההצללה (shader) פשוט מאוד הוא כזה שתמיד מחזיר צבע אחד; בדוגמה הזו נעשה שימוש באדום. תוכנת ההצללה מוגדרת בתוך 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 שאותחל במחרוזת של תוכנת ההצללה (shader). הפעולה הזו גם מבצעת הידור של תוכנת ההצללה.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

אפשר להשתמש ב-RuntimeShader בכל מקום שבו ניתן להשתמש בכלי ההצללה הרגיל של Android. בתור לדוגמה, אפשר להשתמש בו כדי לשרטט לתוך View בהתאמה אישית באמצעות 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
   }
}

תיקח 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" +
   "}";

לאחר מכן, קוראים לפונקציה setColorUniform ממכשיר View בהתאמה אישית כדי להעביר את הצבע הרצוי לתוכנת ההצללה (shader) של 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" +
   "}";

שרטוט של שיפוע

תוכנת ההצללה הזו פועלת בצורה קצת מתוחכמת. לכל פיקסל נוצרת float2 שמכיל את הקואורדינטות של x ו-y חלקי הרזולוציה, תיצור ערך בין אפס לאחד. לאחר מכן היא משתמשת בווקטור המותאם אישית כדי לבנות את הרכיבים האדומים והירוקים של הצבע המוחזר.

מעבירים את הרזולוציה של View ל-shader של AGSL uniform על ידי קריאה 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);
   }
}
אדום וירוק זוהר
הדרגתיות של אדום וירוק

אנימציה של תוכנת ההצללה

אפשר להשתמש בשיטה דומה כדי להוסיף אנימציה לכלי ההצללה על ידי שינוי שלו לקבלת מדים של 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());
   }
});
אנימציה הדרגתית של צבעי אדום וירוק
הדרגתיות של אנימציה בצבעי אדום וירוק

ציור אובייקטים מורכבים

אין צורך לצייר את תוכנת ההצללה כדי למלא את הרקע. יכול להיות בשימוש בכל מקום שמקבל 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 נוספות על הטקסט המוצלל, כמו בסבב. ב-ValueAnimator אפשר לעדכן מטריצה לסיבובים בתלת-ממד באמצעות מנוע החיפוש המובנה כיתה android.graphics.Camera.

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

אפילו קל יותר להשתמש ב-RuntimeShader אם מתבצע רינדור של ממשק המשתמש באמצעות Jetpack Compose. מתחיל עם אותו צבעי הצללה הדרגתיים מ- before:

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)
}
מעגל הדרגתי של כתיבה ב-AGSL
עיגול הדרגתי של צבעי אדום וירוק

שימוש ב-RuntimeShader עם Render לחשבונות

אפשר להשתמש RenderEffect כדי להחיל RuntimeShader להורה View וגם כל הצפיות לילדים. פעולה זו יקרת יותר משרטוט View בהתאמה אישית. אבל היא מאפשרת ליצור בקלות אפקט שמשלב את מה במקור הוא שורטט באמצעות createRuntimeShaderEffect

Kotlin

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

Java

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

הפרמטר השני הוא השם של מדית הצללה שאפשר eval באמצעות coordinate (למשל, העברה ב-fragCoord) כדי לקבל את הצבע המקורי של RenderNode (התצוגה המפורטת והצאצא שלה ), וכך לבצע כל מיני סוגים של אפקטים.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
לחצן להצגה ומיזוג של הרשת
לחצן רשת מוזגת ב-AGSL

אפקט רשת משולב מעל לחצן, אבל מתחת ללחצן פעולה צף (כי הוא בהיררכיה שונה של View).