شرح Three.js: بناء سيارة ثلاثية الأبعاد بسيطة مع خامات مخصصة

دقائق القراءة: 8

مقدمة عملية إلى بناء سيارة ثلاثية الأبعاد باستخدام Three.js

يُعد إنشاء مشهد ثلاثي الأبعاد داخل المتصفح عبر مكتبة Three.js تجربة ممتعة تشبه تركيب المكعبات؛ إذ يمكنك جمع أشكال بسيطة، وإضافة الإضاءة، وتحديد الكاميرا، ثم ترك مهمة التصيير للمكتبة. في هذا الدليل سنبني نموذج سيارة بسيطاً بتصميم هندسي واضح، ثم نضيف إليه خامات مرسومة برمجياً لإظهار النوافذ بطريقة أنيقة وخفيفة.

سنمر في هذا الشرح على المراحل الأساسية التالية:

  • إعداد المشروع وربط مكتبة Three.js.
  • إنشاء المشهد والإضاءة والكاميرا ووحدة التصيير.
  • بناء السيارة باستخدام أشكال صندوقية بسيطة.
  • إنشاء خامات مخصصة عبر HTML Canvas.
  • تطبيق الخامات على جسم السيارة بشكل صحيح.

مشهد سيارة ثلاثية الأبعاد بسيطة باستخدام Three.js داخل المتصفح

كيفية إعداد مشروع Three.js

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

import * as THREE from "three";

const scene = new THREE.Scene();

يمثل الكائن scene الحاوية الرئيسية التي تضم الأجسام والإضاءة وكل ما سيظهر في المشهد. لاحقاً سنضيف إليه السيارة، لكن قبل ذلك نحتاج إلى تجهيز الإضاءة والكاميرا ووحدة التصيير.

إعداد الإضاءة في مشهد السيارة

للحصول على نتيجة بصرية واضحة، سنستخدم نوعين من الإضاءة:

  • إضاءة محيطية AmbientLight.
  • إضاءة اتجاهية DirectionalLight.

الإضاءة المحيطية تمنح المجسمات إضاءة عامة ومتوازنة، بينما تحاكي الإضاءة الاتجاهية ضوء الشمس القادم من مسافة بعيدة.

const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(200, 500, 300);
scene.add(directionalLight);

اللون هنا مُعرّف بقيمة سداسية مثل 0xffffff التي تمثل اللون الأبيض، أما الشدة فتكون رقماً بين 0 و1. ولأن المصدرين يعملان معاً، فمن المناسب اختيار قيم متوسطة تعطي توازناً في المشهد.

توضيح اتجاه الإضاءة في مشهد ثلاثي الأبعاد لبناء سيارة باستخدام Three.js

عند ضبط موضع الإضاءة الاتجاهية عبر position.set(200, 500, 300) فإننا لا نهتم كثيراً بالقيمة المطلقة، بل بنسبة الاتجاه بين المحاور X وY وZ. وبما أن قيمة Y هي الأكبر، فهذا يعني أن أعلى السيارة سيحصل على أكبر قدر من الإضاءة.

كيفية إعداد الكاميرا في Three.js

يوجد نوعان شائعان من الكاميرات في Three.js:

  • كاميرا منظور PerspectiveCamera.
  • كاميرا متعامدة OrthographicCamera.

في هذا المشروع سنستخدم الكاميرا المتعامدة لأنها تمنح الشكل مظهراً هندسياً نظيفاً ومناسباً للتصميمات البسيطة.

مقارنة بصرية توضح منظور الكاميرا في المشاهد ثلاثية الأبعادشرح مجال الرؤية في الكاميرا المتعامدة داخل Three.js

الكاميرا المتعامدة تعتمد على صندوق رؤية يُسمى view frustum، ويجري إسقاط العناصر الموجودة داخله نحو الشاشة دون تشويه المنظور.

// Setting up camera
const aspectRatio = window.innerWidth / window.innerHeight;
const cameraWidth = 150;
const cameraHeight = cameraWidth / aspectRatio;

const camera = new THREE.OrthographicCamera(
  cameraWidth / -2,
  cameraWidth / 2,
  cameraHeight / 2,
  cameraHeight / -2,
  0,
  1000
);

camera.position.set(200, 200, 200);
camera.lookAt(0, 10, 0);

القيم السابقة تحدد حدود الرؤية اليمنى واليسرى والعليا والسفلية، بالإضافة إلى مستويي القرب والبعد near وfar. بعد ذلك نضبط موضع الكاميرا، ثم نطلب منها النظر نحو النقطة (0, 10, 0)، وهي نقطة قريبة من مركز السيارة ومرتفعة قليلاً عن الأرض.

إعداد وحدة التصيير Renderer

وحدة التصيير مسؤولة عن تحويل المشهد ثلاثي الأبعاد إلى صورة تُعرض داخل المتصفح. في هذا المثال سنستخدم WebGLRenderer.

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
document.body.appendChild(renderer.domElement);

الميزة antialias تساعد على تنعيم الحواف، بينما تقوم الدالة setSize() بتحديد أبعاد مساحة العرض بالبكسل. وأخيراً، يضيف السطر الأخير عنصر canvas الناتج إلى الصفحة.

بناء سيارة بسيطة في Three.js

سننشئ السيارة بأسلوب هندسي مختصر يعتمد على عدة صناديق فقط. هذا النهج ليس مجرد تبسيط تعليمي، بل هو أسلوب فعّال في كثير من الألعاب خفيفة الرسوم، خاصة على الأجهزة المحمولة.

تصميم أولي لسيارة ثلاثية الأبعاد بسيطة مكونة من صناديق في Three.js

إنشاء العجلات بصندوق واحد

بدلاً من إنشاء كل عجلة على حدة، يمكن تمثيل كل زوج من العجلات بصندوق واحد، خصوصاً أن المشهد لا يُعرض من الأسفل.

تمثيل عجلات السيارة بصندوق بسيط في نموذج Three.js

function createWheels() {
  const geometry = new THREE.BoxBufferGeometry(12, 12, 33);
  const material = new THREE.MeshLambertMaterial({ color: 0x333333 });
  const wheel = new THREE.Mesh(geometry, material);
  return wheel;
}

يجمع الكائن Mesh بين الشكل geometry والمادة material. في هذا المثال نستخدم BoxBufferGeometry لإنشاء صندوق بأبعاد محددة، ثم نطبّق عليه مادة من النوع MeshLambertMaterial التي تتفاعل مع الإضاءة بشكل مناسب لهذا النوع من النماذج البسيطة.

أنواع المواد وتأثير الإضاءة على المجسمات ثلاثية الأبعاد في Three.js

بناء الهيكل الرئيسي للسيارة

بعد إنشاء العجلات، سنكوّن جسم السيارة من عدة أجزاء داخل كائن من النوع Group. هذه الحاوية تسهّل نقل السيارة أو تدويرها لاحقاً كوحدة واحدة.

function createCar() {
  const car = new THREE.Group();

  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);

  const frontWheel = createWheels();
  frontWheel.position.y = 6;
  frontWheel.position.x = 18;
  car.add(frontWheel);

  const main = new THREE.Mesh(
    new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({ color: 0x78b14b })
  );
  main.position.y = 12;
  car.add(main);

  const cabin = new THREE.Mesh(
    new THREE.BoxBufferGeometry(33, 12, 24),
    new THREE.MeshLambertMaterial({ color: 0xffffff })
  );
  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);

  return car;
}

const car = createCar();
scene.add(car);
renderer.render(scene, camera);

جميع الأشكال تبدأ افتراضياً من المركز (0, 0, 0)، لذلك نحتاج إلى تعديل المواضع على المحور Y لرفعها فوق الأرض، ثم على المحور X لتوزيع الأجزاء بين الأمام والخلف. بهذه الخطوات نحصل على سيارة بسيطة وواضحة التكوين.

مكونات سيارة ثلاثية الأبعاد مبنية من صناديق بسيطة في Three.js

إضافة خامات إلى السيارة باستخدام Canvas

بعد الانتهاء من المجسم الأساسي، يمكن تحسين شكل المقصورة بإضافة خامات تمثل النوافذ. بدلاً من استخدام صور جاهزة، سننشئ هذه الخامات برمجياً بواسطة HTML Canvas. هذه الطريقة مفيدة لأنها خفيفة، مرنة، وتمنحك تحكماً كاملاً في التفاصيل البصرية.

توضيح خامات النوافذ المرسومة لهيكل السيارة في Three.js

عند التعامل مع مادة المجسم، لا يقتصر الأمر على اللون فقط؛ بل يمكن تمرير خامة عبر الخاصية map. كما يمكن تخصيص مادة مستقلة لكل وجه من أوجه الصندوق الستة.

توزيع الخامات على أوجه الصندوق في Three.js

الفرق بين Three.js وHTML Canvas

من المهم التمييز بين المفهومين:

  • Three.js مكتبة JavaScript مسؤولة عن بناء المشهد ثلاثي الأبعاد والتصيير باستخدام WebGL.
  • HTML Canvas عنصر في الصفحة يمكن الرسم عليه برمجياً عبر JavaScript.

سنستخدم canvas هنا كمساحة رسم مؤقتة لإنشاء خامة، ثم نحولها إلى CanvasTexture داخل Three.js.

رسم خامة الواجهة الأمامية والخلفية

function getCarFrontTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 64;
  canvas.height = 32;

  const context = canvas.getContext("2d");
  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 64, 32);
  context.fillStyle = "#666666";
  context.fillRect(8, 8, 48, 24);

  return new THREE.CanvasTexture(canvas);
}

يبدأ الكود بإنشاء عنصر canvas غير معروض في الصفحة. ثم نحدد أبعاده، وهي تمثل دقة الرسم لا حجمه المرئي الفعلي. بعد ذلك نحصل على سياق الرسم ثنائي الأبعاد عبر getContext("2d")، ونرسم مستطيلاً أبيض للخلفية وآخر رمادياً يمثل الزجاج.

رسم خامة الجوانب

function getCarSideTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 128;
  canvas.height = 32;

  const context = canvas.getContext("2d");
  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 128, 32);
  context.fillStyle = "#666666";
  context.fillRect(10, 8, 38, 24);
  context.fillRect(58, 8, 60, 24);

  return new THREE.CanvasTexture(canvas);
}

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

تطبيق الخامات على مقصورة السيارة

لإظهار النوافذ على المقصورة، سنمرر مصفوفة من المواد بدلاً من مادة واحدة. كل عنصر في المصفوفة سيمثل وجهاً من أوجه الصندوق.

function createCar() {
  const car = new THREE.Group();

  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);

  const frontWheel = createWheels();
  frontWheel.position.y = 6;
  frontWheel.position.x = 18;
  car.add(frontWheel);

  const main = new THREE.Mesh(
    new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({ color: 0xa52523 })
  );
  main.position.y = 12;
  car.add(main);

  const carFrontTexture = getCarFrontTexture();
  const carBackTexture = getCarFrontTexture();
  const carRightSideTexture = getCarSideTexture();
  const carLeftSideTexture = getCarSideTexture();

  carLeftSideTexture.center = new THREE.Vector2(0.5, 0.5);
  carLeftSideTexture.rotation = Math.PI;
  carLeftSideTexture.flipY = false;

  const cabin = new THREE.Mesh(
    new THREE.BoxBufferGeometry(33, 12, 24),
    [
      new THREE.MeshLambertMaterial({ map: carFrontTexture }),
      new THREE.MeshLambertMaterial({ map: carBackTexture }),
      new THREE.MeshLambertMaterial({ color: 0xffffff }),
      new THREE.MeshLambertMaterial({ color: 0xffffff }),
      new THREE.MeshLambertMaterial({ map: carRightSideTexture }),
      new THREE.MeshLambertMaterial({ map: carLeftSideTexture }),
    ]
  );

  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);

  return car;
}

الأسطح العلوية والسفلية بقيت بلون موحد، بينما حصلت الجوانب والأمام والخلف على خامات مرسومة. هذه التقنية تمنح المجسم البسيط مظهراً أكثر احترافية دون زيادة كبيرة في التعقيد.

حل مشكلة انعكاس خامة الجانب الأيسر

عند إعادة استخدام خامة الجانب الأيمن على الجانب الأيسر، ستظهر النوافذ بترتيب غير صحيح. لحل هذه المشكلة نحتاج إلى ثلاث خطوات:

  1. تحديد مركز الدوران في منتصف الخامة عبر center = new THREE.Vector2(0.5, 0.5).
  2. تدوير الخامة بمقدار Math.PI أي 180 درجة.
  3. تعطيل قلبها العمودي الافتراضي عبر flipY = false.

تصحيح اتجاه خامة الجانب الأيسر في سيارة Three.js قبل وبعد المعالجة

بهذا التصحيح ستظهر النوافذ على الجانبين بشكل متناسق، وهو تفصيل صغير لكنه يصنع فرقاً بصرياً مهماً في النتيجة النهائية.

لماذا يُعد هذا الأسلوب مناسباً للمشاريع الخفيفة؟

يعتمد هذا النموذج على مجسمات بسيطة وخامات مرسومة برمجياً، وهو أسلوب مفيد في عدة حالات:

  • تسريع التطوير الأولي Prototype.
  • تقليل حجم الموارد مقارنة باستخدام صور كثيرة.
  • الحفاظ على أداء جيد في المتصفح.
  • إنتاج أسلوب بصري نظيف ومناسب للألعاب البسيطة.

كما أن استخدام الأشكال الصندوقية لا يعني التضحية بالجودة دائماً؛ فكثير من الألعاب الناجحة اعتمدت أساليب بصرية هندسية وأثبتت أن البساطة قد تكون خياراً فنياً ناجحاً.

النتيجة النهائية لسيارة ثلاثية الأبعاد بسيطة بخامات مخصصة في Three.js

خطوات تطوير المشروع لاحقاً

بعد الانتهاء من هذا النموذج، يمكنك توسيعه بعدة اتجاهات مفيدة:

  • إضافة طريق أو حلبة سباق.
  • تحريك السيارة عبر لوحة المفاتيح.
  • إدراج منطق تصادم وعناصر لعب.
  • إضافة نظام تحريك Animation Loop.
  • تحسين الخامات لتشمل المصابيح والأبواب والتفاصيل الدقيقة.

هذا النوع من المشاريع مناسب جداً لفهم أساسيات الرسوميات ثلاثية الأبعاد في الويب، كما يفتح الباب لتطوير ألعاب أو عروض تفاعلية تعليمية وخفيفة.

الخلاصة التقنية

يوضح هذا المثال أن بناء مشهد ثلاثي الأبعاد مقنع لا يتطلب دائماً نماذج معقدة أو خامات ثقيلة. عبر Three.js يمكن إنشاء سيارة بسيطة من صناديق فقط، ثم تحسينها بصرياً باستخدام خامات مولدة من HTML Canvas. من الناحية التقنية، هذا النهج ممتاز للتعلّم، سريع في التنفيذ، وفعّال في الأداء، كما أنه يرسّخ فهماً عملياً للعلاقة بين الإضاءة والكاميرا والمواد والخامات داخل بيئة الويب ثلاثية الأبعاد.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *