اذهب إلى المحتوى

غطينا في مقالنا السابق بعض أساسيات الرسوم ثنائية البعد ضمن العنصر <canvas>، لكنك لن تلمس عمليًا فعالية هذا العنصر ما لم ترى قدرته على تحريك الرسوم. إذ يقدم هذا العنصر إمكانية إنشاء صور ورسومات باستخدام سكربتات مخصصة، لكن إن لم يكن هدفك تحريك أي شيء، عليك استخدام صور ثابتة لتوفر على نفسك عناء العمل.

إنشاء الحلقات في Canvas

لن يكون صعبًا التعامل مع الحلقات في <canvas>، وما عليك سوى استخدام تعليمات هذا العنصر (التوابع والخاصيات) داخل حلقة for أو غيرها من الحلقات كغيرها من شيفرات جافا سكريبت. لهذا سنعطي مثالًا تطبيقيًا عن الموضوع:

  1. أنشئ نسخة جديدة عن القالب الرسومي الذي أنشأناه في المقال السابق وافتحه ضمن محرر الشيفرة الذي تستخدمه.
  2. أضف السطر التالي إلى أسفل ملف جافا سكريبت، ويتضمن هذا السطر تابعًا جديدًا هو ()translate الذي يحرّك نقطة المبدأ في لوحة الرسم:
ctx.translate(width / 2, height / 2);

يسبب ذلك تحريك نقطة المبدأ (0,0) إلى مركز اللوحة بدلًا من كونها في الزاوية العليا اليسارية. ولهذا اﻷمر فائدته في الكثير من الحالات كما في مثالنا، إذ نريد أن يكون التصميم منسوبًا إلى مركز اللوحة.

أضف اﻵن الشيفرة التالية:

function degToRad(degrees) {
  return (degrees * Math.PI) / 180;
}

function rand(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

let length = 250;
let moveOffset = 20;

for (let i = 0; i < length; i++) {}

سننجز هنا نفس الدالة ()degToRad التي رأيناها في مثال المثلث في مقالنا السابق، ونستخدم الدالة التي تعيد رقمًا عشوائيًا بين حدين علوي وسفلي معينين. إضافة إلى ذلك ننشئ المتغيرين length و moveOffset (سنرحهما لاحقًا)، كما نستخدم حلقة for فارغة.

ما سنفعله هنا هو رسم شيء ما ضمن اللوحة لكن ضمن الحلقة for ثم نكرر ما نفعله عدة مرات. أضف اﻵن الشيفرة التالية داخل الحلقة for:

ctx.fillStyle = `rgb(${255 - length} 0 ${255 - length} / 90%)`;
ctx.beginPath();
ctx.moveTo(moveOffset, moveOffset);
ctx.lineTo(moveOffset + length, moveOffset);
const triHeight = (length / 2) * Math.tan(degToRad(60));
ctx.lineTo(moveOffset + length / 2, moveOffset + triHeight);
ctx.lineTo(moveOffset, moveOffset);
ctx.fill();

length--;
moveOffset += 0.7;
ctx.rotate(degToRad(5));

في كل تكرار:

  • نضبط fillStyle ليكون ظلًا بنفسجيًا شفافًا قليلًا ويتغير كل مرة وفقًا لقيمة المتغير length. وكما سترى سيقل الطول في كل تكرار وبالتالي سيكون أثر ذلك على اللون الذي يصبح أكثر لمعانًا مع كل مثلث يُرسم على التتابع.
  • نبدأ مسار الرسم.
  • ننقل قلم الرسم إلى اﻹحداثي (moveOffset, moveOffset) ويُعرّف هذا المتغير المسافة التي يجب أن نحركها في كل مرة نرسم فيها مثلًا جديدًا.
  • نرسم خطًا إلى اﻹحداثي (moveOffset+length, moveOffset) وهذا الخط طوله قيمة المتغير length ويوازي المحور x.
  • نحسب ارتفاع المثلث كما فعلنا في المقال السابق.
  • نرسم خطًا نحو رأس المثلث المتجه نحو الأسفل ومن ثم خطا إلى نقطة بداية المثلث.
  • نستدعى التابع ()fill لملء المثلث.
  • نحدّث قيمة المتغيرات التي تصف سلسلة المثلثات التي نرسمها كي نتمكن من رسم المثلث التالي. نخفض قيمة المتغير length بمقدار 1 وبالتالي سيصغر المثلث كل مرة. كما نزيد قيمة moveOffset بمقدار صغير كي يكون كل مثلث أبعد قليلًا عن سابقه. ونستخدم التابع ()rotate الذي يسمح لنا تدوير اللوحة بأكملها، حيث ندورها بمقدار خمس درجات قبل أن نرسم المثلث التالي.

هذا كل ما في اﻷمر، وستبدو نتيجة مثالنا كالتالي:

نشجعك اﻵن على إجراء تغييرات في هذا المثال وتجرّب ما تعلمته. إذ يمكنك مثلًا:

  • أن ترسم مربعًا أو قوسًا بدلًا من المثلث.
  • أن تغير قيمة المتغير length أو moveOffset.
  • تغيير اﻷرقام العشوائية التي نولدها باستخدام الدالة ()rand التي وضعناها في الشيفرة السابقة ولم نستخدمها.

ملاحظة: بإمكانك الاطلاع على الشيفرة كاملة لهذا المثال على جت-هب.

الرسوم المتحركة

ما فعلناه في مثالنا السابق أمر جميل، لكن ما تحتاجه حقيقة حلقة ثابتة تستمر وتستمر في أي تطبيق فعلي يعتمد على عنصر <canvs> (مثل اﻷلعاب الإلكترونية، والعرض البصري المباشر). فلو فكرت بلوحة الرسم على أنها فيلم سينمائي، ستعرف أنه عليك تحديث اﻹطارات المعروضة بشكل مستمر وبمعدل 60 إطار في الثانية (القيمة المثالية) كي يبدو المشهد المتحرك ناعمًا ومريحًا للعين البشرية.

تقدم لك جافا سكريبت مجموعة من الدوال التي تسمح لك بتنفيذ دوال أخرى بشكل متكرر عدة مرات في الثانية الواحدة. ونجد أنسب هذه الدوال لمثالنا الدالة ()window.requestAnimationFrame التي تأخذ معاملًا واحدًا وهو اسم الدالة التي نريد استدعاءها. فإن رسمت هذه الدالة تحديثًا جديدًا من سلسلة الرسوم المتحركة التي سنعرضها، عليك حينها استدعاء الدالة ()window.requestAnimationFrame مجددًا قبل نهاية الدالة المنفذة للرسم كي تستمر حلقة الرسم. تنتهي الحلقة عندما تتوقف عن استدعاء ()window.requestAnimationFrame أو عند استدعاء الدالة ()window.caancelAnimationFrame بعد استدعاء ()window.requestAnimationFrame وقبل البدء برسم اﻹطار (الذي سيكون اﻷخير حينها).

ملاحظة:من الممارسات الجيدة استدعاء الدالة ()window.caancelAnimationFrame من شيفرتك الرئيسية عند الانتهاء من الرسم، كي تضمن عدم وجود أية تحديثات أخرى يمكن أن تُعرض على اللوحة.

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

function loop() {
  ctx.fillStyle = "rgb(0 0 0 / 25%)";
  ctx.fillRect(0, 0, width, height);

  for (const ball of balls) {
    ball.draw();
    ball.update();
    ball.collisionDetect();
  }

  requestAnimationFrame(loop);
}

loop();

نشغل الدالة()loop مرة واحدة في آخر الشيفرة لنبدأ دورة الرسوميات برسم اﻹطار المتحرك الأول ومن ثم تتولى الدالة ()loop مسؤولية استدعاء الدالة (loop(requestAnimationframe التي ستحضر وترسم اﻹطار الثاني من الرسوم المتحركة وتكرر اﻷمر حتى النهاية.

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

لهذا يُعد تحسين الرسوم المتحركة اختصاصًا برمجيًا بحد ذاته، ويتطلب استعمال العديد من التقنيات الذكية المتاحة. لكن هذا اﻷمر خارج إطار مقالنا والمثال الذي نعمل عليه. وعمومًا، تتطلب عملية تنفيذ رسوم متحركة ضمن اللوحة الخطوات التالية:

  1. مسح محتوى اللوحة باستخدام ()fillRect أو ()clearRect.
  2. تخزين الحالة عند الضرورة باستخدام ()save، وذلك عندما تحتاج إلى حفظ اﻹعدادات التي حدّثتها في اللوحة قبل الاستمرار، وللأمر فائدته في التطبيقات المتقدمة.
  3. رسم الأشياء التي تريد تحريكها.
  4. استعادة الإعدادات التي خزنتها في الخطوة الثانية باستخدام ()restore.
  5. استدعاء الدالة ()requestAnimationFrame لجدولة رسم اﻹطار التالي من الرسم المتحرك.

ملاحظة: لن نغطي الدالتين ()save و ()restore في مقالنا.

تحريك شخصية بسيطة

سننشئ اﻵن رسمًا متحركًا خاصًا بنا، تمشي الشخصية المقتبسة من أحد ألعاب الحاسوب القديمة خلال عرض الرسم المتحرك عبر الشاشة.

  1. أنشئ نسخة جديدة من القالب الذي نستخدمه في أمثلتنا وافتحه في محرر اﻷلعاب.
  2. حدّث شيفرة HTML حتى تعكس الصورة:
<canvas class="myCanvas">
  <p>A man walking.</p>
</canvas>

أضف اﻷسطر التالية إلى نهاية ملف جافا سكريبت كي تكون نقطة المبدأ منتصف لوحة الرسم.:

ctx.translate(width / 2, height / 2);

ننشئ تاليًا كائن HTMLImgeElement ونضبط قيمة الخاصية src له كي تكون عنوان الصورة التي نريد تحميلها ثم نضيف معالجًا للحدث onload الذي يستدعي الدالة ()draw عند اكتمال تحميل الصورة:

const image = new Image();
image.src = "walk-right.png";
image.onload = draw;

سنضيف اﻵن بعض المتغيرات التي تتعقب موقع الشخصية في اللوحة وعدد الشخصيات التي نرسمها على اللوحة:

let sprite = 0;
let posX = 0;

سنشرح تاليًا صورة الشخصية المأخوذة من التطبيق Walking cycle using CSS animation

01 walk right

تتضمن الصورة ست شخصيات تمثل تسلسل حركة الشخصية. عرض صورة كل شخصية 102 بكسل وارتفاعها 148 بكسل. ولرسم كل شخصية على حدة، علينا استخدام التابع ()drawImage لاقتصاص صورة واحدة للشخصية وعرض هذا الجزء فقط، كما فعلنا مع شعار فايرفوكس في مثال سابق. وينبغي ضرب اﻹحداثي X للشريحة بالعدد 102 ويبقى اﻹحداثي Y مساويًا للصفر، وستبقى أبعاد الشريحة دائمًا 102x148 بكسل.

سنضع اﻵن شيفرة الدالة ()draw في اﻷسفل لكي نزودها بالشيفرة اللازمة:

function draw() {}

أما بقية الشيفرة في هذا القسم فستكون ضمن الدالة ()draw. لهذا أضف اﻷسطر التالية التي تمسح اللوحة وتعدها لرسم كل إطار. وانتبه إلى ضرورة تخصيص الزاوية العليا اليسارية من المربع لتكون (width/2, height/2) لأننا اتخذنا مركز اللوحة نقطة البداية.

ctx.fillRect(-(width / 2), -(height / 2), width, height);

نرسم تاليًا الصورة باستخدام الدالة drawImage التي تقبل تسع معاملات:

ctx.drawImage(image, sprite * 102, 0, 102, 148, 0 + posX, -74, 102, 148);

وكما ترى:

  • خصصنا image لتكون الصورة التي نرسمها.
  • يحدد المعاملان 2 و 3 إحداثيا الزاوية العليا اليسارية من الشريحة التي نريد اقتصاصها من الصورة المصدرية، ويكون X هو قيمة المتغير sprite مضروبًا بالعدد 102 (حيث يمثل المتغير عدد الشخصيات الموجودة في الصورة من 0 إلى 5) بينما تبقى قيمة Y هي 0.
  • يحدد المعاملان 4 و 5 أبعاد الشريحة التي نقتصها وهي 102x148 بكسل.
  • يحدد المعاملان 6 و 7 الزاوية العليا اليسارية من الصندوق الذي نرسم ضمنه الشخصية، وتكون قيمة اﻹحداثي X هي 0 + posX وبالتالي نستطيع تغيير مكان رسم الخصية بتغيير قيمة posX.
  • يحدد المعاملان 8 و 9 أبعاد الصورة على اللوحة، وعلينا هنا المحافظة على اﻷبعاد اﻷصلية لهذا كانت قمة المعاملين 102 و 148 على التتالي:

علينا تعديل قيمة المتغير sprite عند كل رسم

if (posX % 13 === 0) {
  if (sprite === 5) {
    sprite = 0;
  } else {
    sprite++;
  }
}

لاحظ كيف وضعنا الشيفرة السابقة ضمن الكتلة ({}if (posX % 13 === 0 واستخدمنا العامل % (عامل باقي القسمة) للتحقق من إمكانية قابلية قسمة قيمة المتغير posX على 13. فإن كان الوضع كذلك ننتقل إلى الشخصية التالية بزيادة قيمة المتغير sprite بمقدار 1 (ثم نعود إلى 0 عندما تصبح قيمته 5). ويعني ذلك فعليًا أننا نغير الشخصية عند اﻹطار 13 وتقريبًا حوالي 5 إطارات في الثانية (تكرر الدالة ()requestAnimationFrame العملية بمعدل 60 إطار في الثانية إن أمكن). وعندما نعرض أخر شخصية نعود بعدها إلى الشخصية 0 وإلا سنزيد المتغير sprite بمقدار 1.

سنعمل اﻵن على آلية تغيير قيمة posX مع كل إطار، لهذا عليك إضافة الشيفرة التالية تحت الشيفرة السابقة:

if (posX > width / 2) {
  let newStartPos = -(width / 2 + 102);
  posX = Math.ceil(newStartPos);
  console.log(posX);
} else {
  posX += 2;
}

نستخدم عبارة if...else للتحقق من تجاوز قيمة المتغير posX القيمة width/2 والذي يعني خروج الشخصية من يمين لوحة الرسم، وعندها نحسب موقعًا جديدًا للشخصية يضعها على يسار الحافة اليسرى للوحة. بينما إن لم تتجاوز قيمة المتغير posX تلك القيمة نزيد قيمته بمقدار 2. وبالتالي ستتحرك الشخصية إلى اليمين قليلًا في الإطار التالي.

ولا بد أخيرًا من تنفيذ الحركة السابقة باستمرار عن طريق استدعاء ()requestAnimationFrame في نهاية الدالة ()draw:

window.requestAnimationFrame(draw);

ستبدو نتيجة الشيفرة اﻵن كالتالي:

ملاحظة: بإمكانك الاطلاع على الشيفرة كاملة لهذا المثال على جت-هب.

تطبيق رسومي بسيط

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

بإمكانك الاطلاع على شيفرة التطبيق من خلال المستودع المخص له على جت-هب.

لنلق نظرة على بعض اﻷجزاء المهمة:

  • أولًا: نتتبع موقع الفأرة من خلال إحداثيات x و y، كما نترصد حدث نقر الفأرة وذلك من خلال المتغيرات curX و curY و pressed. وعندما تتحرك الفأرة يقع الحدث onmousemove وننفذ معالجه الذي يلتقط الإحداثيات الحالية لموقع الفأرة. كما نستخدم أيضًا معالجي الحدثين onmousedown و onmouseup لتغيير قيمة المتغير pressed إلى true عندما نضغط زر الفأرة وإلى false عندما نحرر الزر.
let curX;
let curY;
let pressed = false;

// حدّث إحداثيات موقع الفأرة
document.addEventListener("mousemove", (e) => {
  curX = e.pageX;
  curY = e.pageY;
});

canvas.addEventListener("mousedown", () => (pressed = true));

canvas.addEventListener("mouseup", () => (pressed = false));

عند النقر على الزر "مسح اللوحة Clean canvas" ننفذ دالة بسيطة تمحي اللوحة بأكملها وتعيدها إلى اللون اﻷسود:

clearBtn.addEventListener("click", () => {
  ctx.fillStyle = "rgb(0 0 0)";
  ctx.fillRect(0, 0, width, height);
});

وحلقة الرسم بسيطة هنا، فعندما تكون قيمة المتغير pressed هي true، نرسم دائرة لها اللون الذي يحدده منتقي اﻷلوان color picker ونصف قطر يحدده عنصر تحديد المجال range input. وعلينا رسم الدائرة فوق النقطة المحددة بمقدار 85 بكسل، ذلك أن القياس مأخوذ بالنسبة إلى أعلى شاشة العرض (أعلى نافذة المتصفح) لكننا نرسم الدائرة بالنسبة ﻷعلى اللوحة التي تبدأ تحت شريط التحكم (الذي يضم منتقي اﻷلوان ومحدد نصف القطر) ذو الارتفاع 85 بكسل. ولو رسمنا الدائرة اعتمادًا على قيمة curY ستبدو الدائرة تحت النقطة المحددة للرسم بحدود 85 بكسل.

function draw() {
  if (pressed) {
    ctx.fillStyle = colorPicker.value;
    ctx.beginPath();
    ctx.arc(
      curX,
      curY - 85,
      sizePicker.value,
      degToRad(0),
      degToRad(360),
      false,
    );
    ctx.fill();
  }

  requestAnimationFrame(draw);
}

draw();

جميع أنواع عنصر الدخل <input> مدعومة جيدًا من قبل المتصفحات، وإن لم يدعمها متصفح سيعرض حقل نصي نمطي بدلًا عنه.

الواجهة WebGL

لنترك اﻵن البيئة الرسومية ثنائية البعد ونلقي نظرة سريعة على لوحات الرسم ثلاثية اﻷبعاد. تُستخدم الواجهة البرمجية WebGL API للعمل مع الرسومات ثلاثية البعد، وهي واجهة منفصلة تمامًا عن واجهة البيئة الرسومية ثنائية البعد مع أن شيفرتهما تُصيّر ضمن العنصر نفسه <canvas>.

بنيت WebGL على أساس OpenGL (مكتبة الرسوميات المفتوحة Open Graphics Library) وتسمح لك بالتواصل مباشرة مع المعالج الرسومي للحاسوب GPU. لهذا فكتابة شيفرة WebGL خام أشبه بكتابة شيفرات لغات منخفضة المستوى مثل ++C مقارنة بشيفرة جافا سكريبت، فهي معقدة لكنها قوية جدًا.

استخدام مكتبة جافا سكريبت خارجبة

يستخدم معظم المطورون مكتبات يقدمها طرف آخر عند العمل مع الرسوميات ثلاثية البعد نظرًا لتعقيد WebGL مثل Three.js أو PlayCanvas أو Babylon.js. تعمل هذه المكتبات عومًا على نحو متشابه، فهي تقدم دوال أولية وأشكال مخصصة وكاميرات لعرض الموقع وطرق لتطبيق اﻹضاءة والظل ولتغطية السطوح بخامات مختلفة وغيرها. فهذه المكتبات تتعامل مباشرة مع WebGL بدلًا منك متيحة لك المجال للعمل وفق سوية برمجية أعلى. ويعني هذا بالطبع تعلم واجهات برمجية أخرى (واجهات يقدمها طرف آخر في حالتنا) لكنها أبسط بكثير من التعامل مع شيفرة WebGL الخام.

إعادة إنشاء مكعب

لنلق نظرة على مثال بسيط يشرح استخدام المكتبة WebGL، وسنختار فيه المكتبة Three.js كونها من أكثر المكتبات استخدامًا. وسنبني في مثالنا مكعب ثلاثي اﻷبعاد يدور حول نفسه.

  1. أنشئ نسخة عن ملف المثال) ضمن مجلد جديد ثم احفظ نسخة من الملف metal003.png في المجلد نفسه. ويمثل الملف اﻷخير الصورة التي نستخدمها لتغطية سطح المكعب لاحقًا.
  2. أنشئ ملفًا جديدًا باسم script.js في نفس المجلد السابق.
  3. نزّل المكتبة Three.min.js وخزنها في نفس المجلد السابق.
  4. لدينا اﻵن الملف three.js الذي يرتبط بصفحتنا، ويمكننا كتابة الشيفرة الذي تستخدمه ضمن الملف script.js.

لنبدأ بإنشاء مشهد جديد عن طريق إضافة الشيفرة التالية:

const scene = new THREE.Scene();

تُنشئ الدالة البانية()scene مشهدًا جديدًا يمثل بيئة عمل ثلاثية اﻷبعاد التي نريد عرضها. ونضيف بعد ذلك كاميرا لرؤية المشهد. ووفق مصطلحات التصميم ثلاثي اﻷبعاد، تمثل الكاميرا موقع المراقب، وﻹنشائها أضف اﻷسطر التالية:

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
camera.position.z = 5;

تأخذ الدالة البانية PrespectiveCamera أربع وسطاء:

  • حقل الرؤية: ويدل على اتساع المساحة أمام الكاميرا التي يجب عرضها على الشاشة مقدرة بالدرجات.
  • نسبة العرض aspect ratio: وهي عادة نسبة اتساع الشاشة مقسومًا على ارتفاعها، واستخدام نسب أخرى ستشوه المشهد.
  • مستوي البعد: وتمثل البعد عن الكاميرا الذي لن تصير بعده اﻷشياء.

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

أما المكون الحيوي الثالث فهو المصيّر renderer، وهو كائن يصير المشهد كما يُرى من الكاميرا. وسننشئ اﻵن مصيّرًا باستخدام الدالة البانية ()WebGLRenderer لكننا لن نستخدمه حاليًا:

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

يُنشئ السطر الأول مصيرًا جديدًا والثاني يضبط الأبعاد التي سيرسم المصير ضمنها ما تعرضه الكاميرا، بينما يربط السطر الثالث العنصر <canvas> الذي يُنشئه المصيّر بجسم مستند HTML وسيُعرض كل ما يرسمه المصيّر في نافذة المتصفح.

وﻹنشاء المكعب الذي نريد رسمه في اللوحة، عليك إضافة اﻷسطر التالية إلى نهاية ملف جافا سكريبت:

let cube;

const loader = new THREE.TextureLoader();

loader.load("metal003.png", (texture) => {
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(2, 2);

  const geometry = new THREE.BoxGeometry(2.4, 2.4, 2.4);
  const material = new THREE.MeshLambertMaterial({ map: texture });
  cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  draw();
});

هناك نقاط عديدة يجدر شرحها في الشيفرة السابقة:

  • ننشئ أولًا المتغير العام cube لكي نصل إلى المكعب في أي مكان من الشيفرة.

  • ننشئ تاليًا كائن TextureLoader جديد ونستدعي التابع ()load العائد له. ويأخذ هذا التابع معاملين في حالتنا (علمًا أنه يأخذ أكثر): الخامة التي نريد أن نحمّلها (صورة PNG) ودالة ننفذها عند اكتمال تحميل الخامة.

  • نستخدم داخل الدالة السابقة خاصيات الكائن لتكرار الصورة التي تغلف جميع أوجه المكعب بمقدار 2x2. ومن ثم ننشئ كائن BoxGeometry وكائن MeshLambertMaterial جديدان ونربطهما معًا ضمن شبكة Mesh ﻹنشاء المكعب. ويحتاج أي كائن نمطيًا إلى بنية هندسية (الشكل الذي سيكون عليه) ومظهر مادي (كيف سيبدو السطح الخارجي).

  • وفي النهاية، نضيف المكعب إلى المشهد ومن ثم نستدعي الدالة ()draw لتبدأ عملية تحريك الرسم.

وقبل أن نعرّف الدالة ()draw، نضيف زوجًا من الأضواء إلى المشهد، ليبدو المشهد أكثر حيوية:

const light = new THREE.AmbientLight("rgb(255 255 255)"); // soft white light
scene.add(light);

const spotLight = new THREE.SpotLight("rgb(255 255 255)");
spotLight.position.set(100, 1000, 1000);
spotLight.castShadow = true;
scene.add(spotLight);

يُعد الكائن AmbientLight نوعًا من اﻷضواء البرمجية التي تضيئ المشهد بأكمله بما يشبه الشمس التي تضيء عليك وأنت في الخارج. بينما يمثل الكائن spotLight شعاع ضوئي وفق اتجاه محدد مثل مشعل أو بقعة ضوء.

لنضف اﻵن الدالة ()draw إلى أسفل شيفرة جافا سكريبت:

function draw() {
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);

  requestAnimationFrame(draw);
}

الشيفرة السابقة واضحة عمومًا، إذ ندوّر المكعّب قليلًا في كل إطار حول محوريه اﻷفقي والشاقولي ونصيّر المشهد كما يُرى من الكاميرا ونستدعي أخيرًا الدالة ()requestAnimationFrame لتحضير رسم اﻹطار التالي:

إليك المشهد بشكله النهائي:

ملاحظة: بإمكانك الاطلاع على الشيفرة كاملة لهذا المثال على جت-هب.

الخلاصة

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

ترجمة -وبتصرف- للقسم الثاني من مقال Drawing graphics

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...