شرح Three.js: كيفية عرض عناصر ثلاثية الأبعاد داخل المتصفح
مقدمة إلى مكتبة Three.js
إذا كنت تفكر في بناء لعبة أو تجربة بصرية تفاعلية باستخدام JavaScript، فغالباً ستجد مكتبة Three.js من أول الخيارات التي تستحق التعلم. هذه المكتبة تتيح لك إنشاء رسومات ثلاثية الأبعاد داخل المتصفح بسهولة أكبر بكثير من التعامل المباشر مع WebGL.
ورغم أن WebGL هو المحرك الأساسي الذي تعتمد عليه المكتبة في الخلفية، فإن استخدامه بشكل مباشر قد يكون معقداً ومنخفض المستوى. هنا تأتي قوة Three.js، إذ تمنحك واجهات جاهزة لبناء المشاهد والإضاءة والكاميرات والمجسمات بطريقة عملية وسريعة.
في هذا الدليل، سنبني مثالاً بسيطاً لعرض صندوق ثلاثي الأبعاد، ومن خلاله سنتعرف على الأساسيات المهمة: إنشاء المشهد، إضافة المجسم، ضبط الخامة، إعداد الإضاءة، اختيار الكاميرا، ثم عرض النتيجة داخل صفحة الويب.

إنشاء المشهد في Three.js
المشهد أو Scene هو الحاوية الأساسية التي نضع بداخلها كل عناصر العالم ثلاثي الأبعاد، مثل المجسمات والإضاءة والكاميرا من ناحية منطقية. كما يمكن تخصيص بعض خصائصه، مثل لون الخلفية.
إذا لم تحدد لون الخلفية، فسيكون اللون الافتراضي هو الأسود. ويمكنك ضبطه بشكل صريح كما يلي:
import * as THREE from "three";
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // اختياري، الأسود هو الافتراضي
هذه الخطوة تبدو بسيطة، لكنها جوهرية، لأن أي عنصر تريد عرضه لاحقاً يجب أن يوجد داخل scene.
إضافة مجسم ثلاثي الأبعاد: العلاقة بين Geometry وMaterial
في Three.js، لا يكفي إنشاء شكل هندسي وحده. لكي يظهر العنصر على الشاشة، تحتاج عادة إلى دمج الشكل الهندسي Geometry مع الخامة Material داخل كائن واحد يسمى Mesh.
// Add a cube to the scene
const geometry = new THREE.BoxGeometry(3, 1, 3); // width, height, depth
const material = new THREE.MeshLambertMaterial({ color: 0xfb8e00 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0); // اختياري، هذه هي القيمة الافتراضية
scene.add(mesh);
في المثال السابق:
BoxGeometryينشئ صندوقاً بأبعاد محددة.MeshLambertMaterialيحدد مظهر المجسم واستجابته للضوء.Meshيدمج الاثنين في عنصر قابل للإضافة إلى المشهد.
ما المقصود بـ Geometry؟
الـ Geometry هو الوصف الهندسي للشكل الذي تريد رسمه. قد يكون صندوقاً أو كرة أو أسطوانة أو سطحاً مستوياً. بعض الأشكال تكون جاهزة داخل المكتبة، وهو ما يجعل البدء سريعاً ومناسباً حتى للمبتدئين.
يُعد BoxGeometry من أبسط الأشكال الجاهزة، ويكفي أن تمرر له العرض والارتفاع والعمق. وعلى الرغم من بساطته، فإن الصناديق يمكن أن تكون أساساً لبناء تصاميم ألعاب كاملة، خصوصاً في الأساليب البصرية البسيطة أو الهندسية.
ومن الأشكال الشائعة الأخرى:
PlaneGeometryللأسطح المستوية.CylinderGeometryللأسطوانات.SphereGeometryللكرات.IcosahedronGeometryللأشكال متعددة الأوجه.

كيف تعمل الخامات Material؟
الخامة أو Material تحدد مظهر السطح: اللون، الشفافية، الملمس، وطريقة تفاعل المجسم مع الضوء. في مثالنا سنستخدم اللون فقط، لكن الإمكانات الفعلية أوسع بكثير.
الفرق الأساسي بين أنواع الخامات في Three.js هو أسلوب تعاملها مع الإضاءة:
MeshBasicMaterial: لا يتأثر بالإضاءة نهائياً، ويعرض اللون بشكل ثابت.MeshLambertMaterial: يتفاعل مع الضوء بشكل بسيط وعملي، ويعد مناسباً لعدد كبير من المشاهد.MeshPhongMaterial: يقدم دقة أعلى في الإضاءة واللمعان، لكنه يستهلك موارد أكبر.
إذا استخدمت MeshBasicMaterial، فقد يكون ذلك مناسباً للعناصر البسيطة أو الرسومات غير الواقعية. أما إذا أردت عمقاً بصرياً أفضل، فغالباً ستفضّل MeshLambertMaterial أو MeshPhongMaterial.

تحديد موضع المجسم ودورانِه
بعد إنشاء mesh، يمكنك تغيير موقعه داخل المشهد، وكذلك ضبط دورانه حول المحاور الثلاثة. هذه القيم ستكون لاحقاً الأساس لأي حركة أو تحريك Animation تضيفه إلى المشهد.
الموقع يُضبط بوحداتك الخاصة داخل العالم ثلاثي الأبعاد. لا يشترط أن تكون الوحدات صغيرة أو كبيرة، لكن الأهم هو الالتزام بمقياس موحد داخل المشروع.
أما الدوران فيُقاس بوحدة radians وليس بالدرجات. إذا كانت لديك قيمة بالدرجات، فبإمكانك تحويلها إلى راديان وفق العلاقة المعتادة: القسمة على 180 ثم الضرب في PI.

إعداد الإضاءة داخل المشهد
إذا كنت تستخدم خامة مثل MeshLambertMaterial أو MeshPhongMaterial، فلن يظهر المجسم بصورة صحيحة دون وجود إضاءة. أما في حالة MeshBasicMaterial فلن تحتاج إلى ضوء، لأن اللون يُعرض كما هو.
سنبدأ بإضافة إضاءة محيطية AmbientLight:
// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
هذا النوع من الإضاءة ينتشر في كل الاتجاهات ويمنح العناصر إضاءة أساسية متوازنة. عادة يُستخدم اللون الأبيض، بينما تمثل القيمة الثانية شدة الإضاءة، وغالباً تكون بين 0 و1.

إضافة إضاءة اتجاهية DirectionalLight
للحصول على ظلال وتوزيع إضاءة أوضح، يمكننا إضافة إضاءة اتجاهية. هذا النوع يشبه ضوء الشمس، حيث تأتي الأشعة بشكل متوازٍ من اتجاه محدد.
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
dirLight.position.set(10, 20, 0); // x, y, z
scene.add(dirLight);
رغم وجود خاصية position، فإن الفكرة هنا لا تتعلق بنقطة ضوء صغيرة بقدر ما تتعلق بالاتجاه العام لسقوط الأشعة. عندما تكون قيمة المحور Y أعلى، فهذا يعني أن الضوء يأتي من الأعلى، لذلك يتلقى الجزء العلوي من الصندوق إضاءة أكبر.
ولأننا حرّكنا الضوء أيضاً على المحور X، فسيحصل أحد الجانبين على إضاءة إضافية. بينما عدم تغيير المحور Z يعني أن الواجهة الأمامية لن تستفيد كثيراً من هذا المصدر وحده، وهنا تظهر أهمية الإضاءة المحيطية في منع المشهد من الغرق في الظلام.
وتوجد أنواع أخرى من الإضاءة قد تحتاجها لاحقاً:
PointLight: مناسب لمحاكاة المصابيح التي تبعث الضوء من نقطة في كل الاتجاهات.SpotLight: مفيد لمحاكاة الأضواء المركزة مثل مصابيح السيارات أو المسرح.
اختيار الكاميرا المناسبة في Three.js
بعد تجهيز المجسمات والإضاءة، نحتاج إلى كاميرا تحدد كيف سيرى المستخدم المشهد. النوعان الأشهر هما:
- الكاميرا المنظورية
PerspectiveCamera. - الكاميرا المتوازية
OrthographicCamera.

ما الفرق بين PerspectiveCamera وOrthographicCamera؟
الكاميرا المنظورية تحاكي الرؤية البشرية: كلما ابتعد الجسم ظهر أصغر، وكلما اقترب ظهر أكبر. لذلك فهي شائعة جداً في الألعاب والتطبيقات الواقعية.
أما الكاميرا المتوازية فتُبقي الأحجام ثابتة تقريباً بغض النظر عن المسافة، ما يجعلها مناسبة للتصاميم الهندسية أو المشاهد ذات الطابع البسيط والدقيق.
في كلتا الحالتين، يوجد ما يسمى مجال الرؤية الحجمي أو view frustum، وهو النطاق الذي سيتم إسقاطه على الشاشة. أي عنصر خارج هذا النطاق لن يظهر.

إعداد كاميرا منظور PerspectiveCamera
// Perspective camera
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.PerspectiveCamera(
45, // field of view in degrees
aspect, // aspect ratio
1, // near plane
100 // far plane
);
عند إنشاء هذا النوع من الكاميرات، ستحدد:
- زاوية الرؤية
field of view. - نسبة العرض إلى الارتفاع
aspect ratio. - المسافة القريبة
near. - المسافة البعيدة
far.
أي عنصر أقرب من قيمة near أو أبعد من قيمة far لن يتم عرضه.
إعداد كاميرا متوازية OrthographicCamera
// Orthographic camera
const width = 10;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
width / -2, // left
width / 2, // right
height / 2, // top
height / -2, // bottom
1, // near
100 // far
);
في هذا النوع لا يجري الإسقاط نحو نقطة واحدة، بل على خطوط متوازية، ولهذا لا يظهر التشوه المنظوري المعروف في الكاميرات التقليدية.
بعد إنشاء الكاميرا، يجب تحديد موضعها واتجاهها:
camera.position.set(4, 4, 4);
camera.lookAt(0, 0, 0);
الأمر lookAt() يجعل الكاميرا تنظر نحو نقطة محددة، وفي هذا المثال تنظر إلى مركز المشهد عند الإحداثيات 0, 0, 0.
عرض المشهد داخل المتصفح باستخدام WebGLRenderer
بعد إعداد المشهد والمجسمات والإضاءة والكاميرا، نصل إلى المرحلة الأخيرة: التصيير أو Rendering. هذه المهمة ينفذها الكائن WebGLRenderer، وهو المسؤول عن رسم المشهد داخل عنصر canvas في الصفحة.
في هذا المثال الكامل، سنجمع كل الخطوات معاً:
import * as THREE from "three";
// Scene
const scene = new THREE.Scene();
// Add a cube to the scene
const geometry = new THREE.BoxGeometry(3, 1, 3); // width, height, depth
const material = new THREE.MeshLambertMaterial({ color: 0xfb8e00 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
scene.add(mesh);
// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(10, 20, 0); // x, y, z
scene.add(directionalLight);
// Camera
const width = 10;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
width / -2, // left
width / 2, // right
height / 2, // top
height / -2, // bottom
1, // near
100 // far
);
camera.position.set(4, 4, 4);
camera.lookAt(0, 0, 0);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
// Add it to HTML
document.body.appendChild(renderer.domElement);
في السطر الأخير نضيف عنصر canvas الناتج إلى الصفحة، وبذلك يصبح المشهد مرئياً داخل المتصفح. ورغم أن عدد الخطوات قد يبدو كبيراً من أجل صندوق واحد فقط، فإن معظم هذا الإعداد يُكتب مرة واحدة ثم يُعاد استخدامه في بقية المشروع.
نصائح عملية لتطوير مشروعك بعد هذا المثال
بعد نجاحك في عرض مجسم ثلاثي الأبعاد بسيط، يمكنك الانتقال إلى مستويات أكثر تقدماً، مثل:
- إضافة حركة عبر تحديث خصائص
positionأوrotation. - التعامل مع أحداث المستخدم مثل النقر أو السحب.
- إنشاء حلقة رسم باستخدام
requestAnimationFrame(). - تحميل نماذج جاهزة بدلاً من الاكتفاء بالأشكال الأساسية.
- دمج فيزياء الألعاب عبر مكتبات مثل
Cannon.js.
هذه الخطوات تنقل مشروعك من مجرد عرض ثابت إلى تجربة تفاعلية متكاملة يمكن تطويرها كلعبة أو أداة عرض منتجات أو مشروع تعليمي.
الخلاصة التقنية
تُعد Three.js خياراً ممتازاً لأي مطور ويب يريد دخول عالم الرسوميات ثلاثية الأبعاد دون الغوص المباشر في تعقيدات WebGL. من الناحية التقنية، فهم العلاقة بين scene وmesh وmaterial وcamera وrenderer هو الأساس الحقيقي لأي مشروع ناجح. وإذا أتقنت هذه اللبنات الأولى، فسيصبح بناء مشاهد أكثر تعقيداً أمراً طبيعياً وقابلاً للتوسع بكفاءة.