أهلاً بيك يا صديقي المبرمج في عالم الرسوميات المتقدمة! لو انت شغال فلاتر (Flutter) وبتعمل تطبيقات موبايل، أكيد في يوم من الأيام جه اليوزر أو الـ UI/UX Designer وقالك: "بص، أنا عايز الخلفية دي تكون عبارة عن أمواج مياه بتتحرك، أو تدرج ألوان (Gradient) بيتفاعل مع لمسات الصباع، أو تأثير زجاجي (Glassmorphism) فخم زي بتاع أبل". هنا بقى بيبدأ وجع الدماغ الحقيقي؛ لأنك لو حاولت تعمل ده بالـ Widgets العادية، الأداء (Performance) هيقع والتطبيق هيتقتل، وهنا بيجي دور السحر الحقيقي: الـ Shaders وتأثيرات الرسوميات (Graphics Effects).
في المقال ده، هناخد رحلة تفصيلية وعميقة عشان نفهم إزاي نستخدم الـ Shaders في فلاتر، وازاي نكتب كود شيدر بلغة جي إل إس إل (GLSL Shaders) ونشغله جوه تطبيق الفلاتر بتاعنا بكل سهولة وبأعلى أداء ممكن (60fps مرتاح جداً).
Table of contents [Show]
إيه هي الـ Shaders ولينا بنحتاجها في فلاتر؟
زمان، لما كنا بنحب نعمل خلفيات متحركة أو فلاتر معقدة، كنا بنضطر نعتمد على صور متحركة (GIFs) أو فيديوهات، وده طبعا بيزود حجم التطبيق (App Size) وبيستهلك بطارية الموبايل بشكل مرعب. الـ Shaders ببساطة شديدة هي برامج صغيرة بتشتغل مباشرة على كارت الشاشة (GPU) مش على المعالج (CPU). كارت الشاشة مصمم خصمياً عشان يعمل عمليات حسابية رياضية مرعبة في نفس اللحظة (Parallel Processing)، وده بيخلي الـ Shaders ترسم تأثيرات بصرية خيالية وسريعة جداً.
فريق فلاتر من أول الإصدار 3.7 تقريباً، وفر لنا دعم ممتاز جداً للـ Fragment Shaders. ده معناه إننا نقدر نكتب كود بلغة OpenGL Shading Language المعروفة اختصاراً بـ (GLSL)، وندمجه جوه كود الدارت (Dart Code) بتاعنا، ونعرضه كخلفية (Background) أو فلتر (Image Filter) لأي واجهة بنبنيها.
هيكل ملفات الشيدر في مشروع فلاتر
عشان نبدأ نكتب شيدر في فلاتر، لازم نعرف إحنا محتاجين إيه جوه هيكل المشروع (Project Structure) بتاعنا. الموضوع مش صعب، بس محتاج تركيز في كام خطوة:
- هتعمل فولدر جديد في جذر المشروع باسم
shaders. - جوه الفولدر ده، هتكتب ملف الشيدر بتاعك وامتداده هيكون
.frag، مثلاًmy_first_shader.frag. - لازم تروح على ملف
pubspec.yamlوتقول لفلاتر إنك عندك شيدر جديد وعايزه يعمله Asset Compilation بالشكل ده:
flutter:
shaders:
- shaders/my_first_shader.frag
كتابة أول كود GLSL Fragment Shader
لغة الـ GLSL ممكن تبدو مرعبة في الاول للمبرمجين اللي متعودين على Dart أو JavaScript، بس الموضوع بيلخص في لغة C-style بسيطة. هدف الـ Fragment Shader الأساسي هو إنه يحدد لون كل بيكسل (Pixel) على الشاشة بناءً على الإحداثيات بتاعتها (Coordinates).
تعال نشوف مثال بسيط لملف my_first_shader.frag بيعمل تدرج ألوان بيتغير مع الوقت:
#version 460 core
include <flutter/runtime_effect.glsl>
out vec4 fragColor;
uniform vec2 uSize;
uniform float uTime;
void main() {
// حساب الإحداثيات بـ Normalized Coordinates
vec2 st = FlutterFragCoord().xy / uSize;
// دمج الألوان بناءً على الوقت والإحداثيات
vec3 color = vec3(st.x, st.y, sin(uTime));
fragColor = vec4(color, 1.0);
}
في الكود البسيط ده، إحنا استخدمنا دالة FlutterFragCoord() اللي فلاتر بيفرها لنا عشان نعرف مكان البيكسل الحالي، واستخدمنا المتغيرات (Uniforms) زي uSize (حجم الشاشة أو الـ Widget) و uTime (الوقت الحالي عشان نعمل حركة).
تشغيل الشيدر جوه كود Dart وعرضه كخلفية
الخطوة التانية والمهمة هي إزاي ناخد الملف ده ونشغله جوه واجهة فلاتر. عشان نعمل كده، هنحتاج نستخدم FragmentProgram ونتعامل مع الـ CustomPainter. تعال نبص على الكود في لغة دارت (Dart Code):
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class ShaderBackground extends StatefulWidget {
@override
_ShaderBackgroundState createState() => _ShaderBackgroundState();
}
class _ShaderBackgroundState extends State<ShaderBackground> with SingleTickerProviderStateMixin {
ui.FragmentShader? _shader;
late Ticker _ticker;
double _time = 0.0;
@override
void initState() {
super.initState();
_loadShader();
_ticker = createTicker((elapsed) {
setState(() {
_time = elapsed.inMilliseconds / 1000.0;
});
})..start();
}
Future<void><void> _loadShader() async {
final program = await ui.FragmentProgram.fromAsset('shaders/my_first_shader.frag');
setState(() {
_shader = program.fragmentShader();
});
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_shader == null) {
return Center(child: CircularProgressIndicator());
}
return CustomPaint(
painter: ShaderPainter(shader: _shader!, time: _time),
child: Container(),
);
}
}
class ShaderPainter extends CustomPainter {
final ui.FragmentShader shader;
final double time;
ShaderPainter({required this.shader, required this.time});
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setFloat(2, time);
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(covariant ShaderPainter oldDelegate) => true;
}
الحتة دي عظيمة جداً، لأننا ربطنا الـ Ticker بالـ State عشان نبعث متغير الوقت (Time) للـ Shader باستمرار، فكارت الشاشة يرسم فريم جديد كل لحظة، والنتيجة بتكون خلفية متحركة وناعمة جداً بدون أي تهنيج في التطبيق.
تطبيقات عملية وفلاتر متقدمة (Image Filters)
مش بس بنقدر نعمل خلفيات، إحنا كمان نقدر نستخدم الـ Shaders كفلاتر للصور أو الـ Widgets باستخدام الـ ShaderMask أو ImageFiltered. تخيل انك تعمل زرار لما اليوزر يضغط عليه، يحصل تأثير بصرى زي العدسة المكبرة (Magnifying Glass) أو تأثير التموج (Ripple Effect). كل دي حاجات بقت سهلة جداً ومتاحة بفضل دعم فلاتر القوي للـ GLSL.
نصيحة من أخ لتطوير مهاراتك
يا صاحبي، مجال الـ Shaders والـ Graphics Programming في الأول ممكن يحسسك بشوية رهبة، لأن الرياضة و حساب المثلثات فيه بيكونوا هم الأبطال الحقيقيين. نصيحتي ليك: متقلقش لو مفهمتش كل معادلات الـ GLSL من أول يوم. ابدأ بمواقع عظيمة زي Shandertoy، خد أمثال جاهزة وجرب تعدل فيها بنفسك جوه مشروع فلاتر صغير. التجربة والخطأ هم أسرع طريقتين عشان تحترف الموضوع ده وتخلي تطبيقاتك مميزة ومختلفة عن أي حد تاني في السوق. بالتوفيق، واستنى المقالات الجاية!