دليل لبناء تأثير عدسة مربعة تتبع المؤشر باستخدام Three.js وGLSL
نشر Codrops شرحاً عملياً لبناء تأثير WebGL يعرض صورة رمادية بملء الشاشة، ويكشف صورة ملوّنة داخل مربع يتبع مؤشر الفأرة بسلاسة. يعتمد التنفيذ على Three.js وGLSL، ويضيف تشوّه عدسة وRGB shift وحركة موجية وضوضاء خفيفة، مع GUI لضبط المعلمات لحظياً.
أبرز النقاط
- التأثير يمزج صورة رمادية بملء الشاشة مع نسخة ملوّنة تظهر فقط داخل مربع يتبع المؤشر.
- يحافظ قناع المربع على أبعاده المتساوية عند تغيير حجم الـ viewport عبر تصحيح نسبة الأبعاد.
- تشوّه العدسة وRGB shift يُطبقان داخل الـ fragment shader على الصورة الملوّنة فقط.
- تُحوّل إحداثيات المؤشر من DOM إلى نظام إحداثيات shader، ويُستخدم `Vector2.lerp` لحركة سلسة متأخرة قليلاً.
- تضيف الخلفية الرمادية موجات وضوضاء متحركة، ويمكن ضبط معظم المعلمات لحظياً عبر GUI.
يعرض درس جديد من Codrops طريقة بناء واجهة تفاعلية تعتمد على WebGL: صورة رمادية تغطي الشاشة، وفوقها مربع ملوّن يتبع حركة المؤشر ويعمل كعدسة تكشف الصورة نفسها بالألوان مع تشوّه بصري خفيف.
يمكن مشاهدة التجربة هنا: https://tympanus.net/Tutorials/PointerSquareLensDistortion/
التنفيذ مبني باستخدام Three.js وGLSL، لكنه لا يحتاج إلى نماذج ثلاثية الأبعاد أو سلسلة post-processing أو render target. معظم النتيجة تُركّب داخل fragment shader عبر مزج صورتين والتحكم في إحداثيات أخذ العينات من الـ textures.
في النتيجة النهائية، تظهر الصورة الرمادية خارج المربع، بينما تظهر النسخة الملونة داخل حدوده. ويضيف الـ shader داخل هذا المربع تأثيراً شبيهاً بـ CC Lens يجعل الصورة تبدو منتفخة من المركز، إلى جانب RGB shift يزداد تدريجياً كلما اقتربت البكسلات من حواف المربع. أما الخلفية الرمادية فتأخذ حركة مستقلة عبر موجات بسيطة وتشويش عشوائي متحرك.
كيف يُبنى التأثير؟
يبدأ الدرس بعرض الصورتين على Plane يغطي مساحة العرض. ولمنع تمدد الصور عند اختلاف أبعادها عن أبعاد الشاشة، يستخدم دالة getCoverUv التي تحافظ على نسبة الأبعاد وتقص الصورة من المنتصف، بطريقة تشبه background-size: cover في CSS.
بعد ذلك، يحوّل الـ shader إحداثيات v_uv إلى نطاق من -1.0 إلى 1.0، ثم ينشئ قناعاً مربعاً باستخدام step. ويصحح القناع بحسب نسبة أبعاد مساحة العرض حتى يبقى مربعاً بصرياً عند تغيير حجم النافذة، بدلاً من أن يتمدد أفقياً أو عمودياً.
يُستخدم القناع لاحقاً في mix للاختيار بين الصورة الرمادية والصورة الملونة:
vec4 finalColor = mix(outsideColor, insideColor, squareMask);
داخل مساحة المربع، يحسب التنفيذ إحداثيات محلية لتطبيق تشوّه العدسة. الفكرة ليست في إعادة رسم الصورة كاملة داخل المربع، بل في حساب فرق الإزاحة الناتج عن تشوّه العدسة وتحويله إلى إزاحة مناسبة لإحداثيات الصورة المعروضة بملء الشاشة. بهذه الطريقة تبقى النسختان الرمادية والملونة متطابقتين في الموضع والحجم، بينما يتشوه الجزء الملون فقط.
ويأتي RGB shift فوق ذلك عبر أخذ عينات منفصلة لقنوات الأحمر والأخضر والأزرق، مع إزاحة كل قناة بحسب اتجاه البكسل من مركز المربع. لذلك لا يظهر انفصال ألوان في المركز، ويصبح أوضح قرب الحواف.
حركة المؤشر والخلفية
لجعل العدسة تتبع المؤشر، تتحول إحداثيات DOM من البكسلات إلى نطاق الـ shader بين -1.0 و1.0، مع عكس محور Y. ثم يستخدم الكود Vector2.lerp لتأخير الحركة قليلاً وإعطاء المربع انتقالاً سلساً بدلاً من الالتصاق الفوري بالمؤشر.
أما الصورة الرمادية في الخلفية، فتأخذ حركة موجية على المحور Y باستخدام sin، إلى جانب إزاحة دقيقة شبيهة بالضوضاء عبر الدالة random3. ويجري تمرير الوقت من performance.now() إلى uniform باسم u_time لتتحرك هذه التأثيرات مع كل إطار.
التطبيق ينظم الإعدادات ضمن أصناف مثل Stage للمشهد والكاميرا والـ renderer، وMesh للـ textures والـ uniforms، وWebgl للحلقة الرسومية والأحداث والواجهة. كما يضيف GUI لتعديل حجم المربع، وقوة العدسة، وإزاحات RGB، وخصائص الموجة والضوضاء، وسرعة استجابة المؤشر أثناء التشغيل.
الدرس مفيد لمن يريد فهم كيفية تفكيك تأثير بصري يبدو معقداً إلى أجزاء shader صغيرة: قناع، تصحيح aspect ratio، مزج textures، إزاحات UV، وuniforms قابلة للضبط.
لماذا يهم؟
يوضح الدرس أسلوباً عملياً لبناء تفاعل بصري متقدم في الويب من دون تعقيد بنية العرض: يمكن تحقيق نتيجة غنية عبر التحكم في UV داخل fragment shader ومزج طبقات textures، مع إبقاء كل عنصر قابلاً للضبط والتطوير.