استفاده کردن از AGSL در برنامه Android

این صفحه اصول AGSL و روش‌های مختلف استفاده از AGSL در برنامه Android را پوشش می‌دهد.

سایه‌زن AGSL ساده

کد سایه‌زن شما برای هر پیکسل ترسیم‌شده فراخوانی می‌شود و رنگی را که پیکسل باید با آن رنگ‌آمیزی شود برمی‌گرداند. سایه‌زن بسیار ساده سایه‌زنی است که همیشه یک رنگ را برمی‌گرداند؛ این نمونه از رنگ قرمز استفاده می‌کند. سایه‌زن در داخل String تعریف شده است.

کاتلین

private const val COLOR_SHADER_SRC =
   """half4 main(float2 fragCoord) {
      return half4(1,0,0,1);
   }"""

جاوا

private static final String COLOR_SHADER_SRC =
   "half4 main(float2 fragCoord) {\n" +
      "return half4(1,0,0,1);\n" +
   "}";

مرحله بعدی ایجاد شیء RuntimeShader است که با رشته سایه‌زن شما مقداردهی اولیه شده است. این کار سایه‌زن را نیز کامپایل می‌کند.

کاتلین

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

جاوا

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

از RuntimeShader شما می‌توان در هر جایی که از سایه‌زن استاندارد Android استفاده می‌شود استفاده کرد. به‌عنوان مثال، می‌توانید از آن برای طراحی در View سفارشی بااستفاده از Canvas استفاده کنید.

کاتلین

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      canvas.drawPaint(paint) // fill the Canvas with the shader
   }
}

جاوا

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 را به سایه‌زن اضافه کنید:

کاتلین

private const val COLOR_SHADER_SRC =
"""layout(color) uniform half4 iColor;
   half4 main(float2 fragCoord) {
      return iColor;
   }"""

جاوا

private static final String COLOR_SHADER_SRC =
   "layout(color) uniform half4 iColor;\n"+
      "half4 main(float2 fragCoord) {\n" +
      "return iColor;\n" +
   "}";

سپس، setColorUniform را از View سفارشی‌تان فراخوانی کنید تا رنگ موردنظر را به سایه‌زن AGSL منتقل کنید.

کاتلین

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

جاوا

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

اکنون، View سبز دریافت می‌کنید؛ رنگ View بااستفاده از پارامتری از کد در View سفارشی شما کنترل می‌شود، به‌جای اینکه در سایه‌زن جاسازی شود.

به‌جای آن می‌توانید جلوه طیف رنگی ایجاد کنید. ابتدا باید سایه‌زن را تغییر دهید تا وضوح View را به‌عنوان ورودی بپذیرد:

کاتلین

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

جاوا

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 تقسیم بر وضوح است، که مقداری بین صفر و یک ایجاد می‌کند. سپس از آن بردار مقیاس‌بندی‌شده برای ساختن مؤلفه‌های قرمز و سبز رنگ برگشتی استفاده می‌کند.

با فراخوانی setFloatUniform، وضوح View را به سایه‌زن AGSL‏ uniform منتقل می‌کنید.

کاتلین

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      fixedColorShader.setFloatUniform("iResolution", width.toFloat(), height.toFloat())
      canvas.drawPaint(paint)
   }
}

جاوا

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 استفاده کنید. سایه‌زن از این مقادیر برای ایجاد موج مثلثی برای رنگ‌ها استفاده می‌کند و باعث می‌شود که آن‌ها در مقادیر گرادیان خود به‌صورت رفت و برگشتی چرخه داشته باشند.

کاتلین

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

جاوا

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 یکنواخت را به‌روز می‌کند.

کاتلین

// 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()

جاوا

// 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.

کاتلین

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint)

جاوا

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint);
نوشتار طیف رنگی پویانمایی‌شده قرمز و سبز
نوشتار مدرج پویانمایی‌شده قرمز و سبز

سایه‌زنی و تبدیل‌های Canvas

می‌توانید تبدیل‌های Canvas بیشتری روی نوشتار سایه‌دار اعمال کنید، مثلاً چرخش. در ValueAnimator، می‌توانید ماتریس را برای چرخش‌های سه‌بعدی بااستفاده از کلاس داخلی android.graphics.Camera به‌روز کنید.

کاتلین

// in the ValueAnimator
camera.rotate(0.0f, animation.animatedValue as Float / DURATION * 360f, 0.0f)

جاوا

// in the ValueAnimator
camera.rotate(0.0f, (Float)animation.getAnimatedValue() / DURATION * 360f, 0.0f);

ازآنجایی‌که می‌خواهید نوشتار را از محور مرکزی بچرخانید نه از گوشه، مرزهای نوشتار را دریافت کنید و سپس از preTranslate و postTranslate برای تغییر ماتریس استفاده کنید تا نوشتار را ترجمه کنید به‌طوری‌که 0,0 مرکز چرخش باشد بدون اینکه موقعیت نوشتار ترسیم‌شده روی صفحه تغییر کند.

کاتلین

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

جاوا

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 پرداز می‌کنید، استفاده از 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)
}
دایره گرادیان AGSL Compose
دایره با طیف رنگ قرمز و سبز

استفاده از RuntimeShader با RenderEffect

می‌توانید از RenderEffect برای اعمال RuntimeShader به View اصلی و همه نماهای فرعی استفاده کنید. این گران‌تر از ترسیم View سفارشی است. اما به شما امکان می‌دهد به‌راحتی جلوه‌ای ایجاد کنید که آنچه را که دراصل بااستفاده از createRuntimeShaderEffect کشیده شده است دربرگیرد.

کاتلین

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

جاوا

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

پارامتر دوم نام یک سایه‌زن یکنواخت است که می‌توانید آن را با یک پارامتر مختصات (مانند fragCoord ارسال‌شده) eval کنید تا رنگ اصلی RenderNode (نما و نماهای فرزند آن) را دریافت کنید، که به شما امکان می‌دهد انواع جلوه‌ها را اجرا کنید.

‫
uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
دکمه ترکیب جدول روی
شبکه AGSL ترکیب‌شده روی دکمه

جلوه شبکه‌ای که روی دکمه ترکیب شده است، اما زیر دکمه کنش شناور قرار دارد (چون در سلسله‌مراتب View متفاوتی قرار دارد).