عمليات تحويل WebGL

الترجمة الثنائية الأبعاد لـ WebGL

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

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

  // First lets make some variables 
  // to hold the translation of the rectangle
  var translation = [0, 0];
  // then let's make a function to
  // re-draw everything. We can call this
  // function after we update the translation.
  // Draw the scene.
  function drawScene() {
     // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);
    // Setup a rectangle
    setRectangle(gl, translation[0], translation[1], width, height);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

كل شيء على ما يرام حتى الآن. لكن تخيل الآن أننا أردنا أن نفعل الشيء نفسه بشكل أكثر تعقيدًا. لنفترض أننا أردنا رسم الحرف F يتكون من 6 مثلثات مثل هذا.

حرف F

حسنًا، في ما يلي التعليمة البرمجية الحالية، وسيتعين علينا تغيير setRectangle إلى شيء يشبه هذه الطريقة.

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl, x, y) {
  var width = 100;
  var height = 150;
  var thickness = 30;
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          x, y,
          x + thickness, y,
          x, y + height,
          x, y + height,
          x + thickness, y,
          x + thickness, y + height,

          // top rung
          x + thickness, y,
          x + width, y,
          x + thickness, y + thickness,
          x + thickness, y + thickness,
          x + width, y,
          x + width, y + thickness,

          // middle rung
          x + thickness, y + thickness * 2,
          x + width * 2 / 3, y + thickness * 2,
          x + thickness, y + thickness * 3,
          x + thickness, y + thickness * 3,
          x + width * 2 / 3, y + thickness * 2,
          x + width * 2 / 3, y + thickness * 3]),
      gl.STATIC_DRAW);
}

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

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;

void main() {
   // Add in the translation.
   vec2 position = a_position + u_translation;

   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = position / u_resolution;
   ...

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

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          0, 0,
          30, 0,
          0, 150,
          0, 150,
          30, 0,
          30, 150,

          // top rung
          30, 0,
          100, 0,
          30, 30,
          30, 30,
          100, 0,
          100, 30,

          // middle rung
          30, 60,
          67, 60,
          30, 90,
          30, 90,
          67, 60,
          67, 90]),
      gl.STATIC_DRAW);
}

بعد ذلك، علينا فقط تحديث اللغة u_translation قبل عرض الترجمة المطلوبة.

  ...
  var translationLocation = gl.getUniformLocation(
             program, "u_translation");
  ...
  // Set Geometry.
  setGeometry(gl);
  ..
  // Draw scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

يتم استدعاء الإشعار setGeometry مرة واحدة فقط. لم يعُد هذا العنصر داخل drawScene.

الآن، عندما نرسم WebGL، يتم تنفيذ كل شيء عمليًا. كل ما نقوم به هو إعداد ترجمة وطلب رسمها. حتى لو كان الشكل الهندسي لدينا عشرات الآلاف من النقاط، فإن الكود الرئيسي سيظل كما هو.

تدوير WebGL ثنائي الأبعاد

سأعترف مقدمًا أنّه ليس لدي أي فكرة عمّا إذا كانت طريقة شرح هذا سيكون منطقية، ولكن ما الذي قد يحاوله ذلك.

أودّ أولاً أن أعرِّفك على ما يسمّى "دائرة الوحدة". إذا كنت تتذكر درس الرياضيات للمدرسة الثانوية (لا تذهب إلى النوم علي)! فإن الدائرة لها نصف قطر. نصف قطر الدائرة هو المسافة من مركز الدائرة إلى الحافة. دائرة الوحدة هي دائرة يبلغ نصف قطرها 1.0.

إذا تذكرت من أساسيات الرياضيات بالصف الثالث إذا ضربت شيئًا في 1، فسيظل كما هو. إذًا 123 * 1 = 123. بسيطة للغاية، أليس كذلك؟ حسنًا، دائرة الوحدة، الدائرة التي يبلغ نصف قطرها 1.0 هي أيضًا شكل 1. الرقم 1. إذًا، يمكنك ضرب شيء ما في دائرة الوحدة هذه، وهذا يشبه الضرب في 1 باستثناء حدوث السحر وتدوير الأشياء. سنأخذ هذه القيمة X وY من أي نقطة في دائرة الوحدة وسنضرب الشكل الهندسي فيهما من العينة السابقة. إليك آخر التعديلات على أداة التظليل.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;

void main() {
  // Rotate the position
  vec2 rotatedPosition = vec2(
     a_position.x * u_rotation.y + a_position.y * u_rotation.x,
     a_position.y * u_rotation.y - a_position.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

ونحن نحدّث JavaScript حتى نتمكن من تمرير هاتين القيمتين.

  ...
  var rotationLocation = gl.getUniformLocation(program, "u_rotation");
  ...
  var rotation = [0, 1];
  ..
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

ما هي آلية العمل؟ حسنًا، انظر إلى الرياضيات.

rotatedX = a_position.x * u_rotation.y + a_position.y * u_rotation.x;
rotatedY = a_position.y * u_rotation.y - a_position.x * u_rotation.x;

لنبقى لديك مستطيل وتريد تدويره. قبل أن تبدأ في تدويره، يكون أعلى الجانب الأيمن عند 3.0، 9.0. لنختر نقطة على دائرة الوحدة بمقدار 30 درجة في اتجاه عقارب الساعة من الساعة 12 صباحًا.

تدوير 30 درجة

الموضع على الدائرة هو 0.50 و0.87

3.0 * 0.87 + 9.0 * 0.50 = 7.1
9.0 * 0.87 - 3.0 * 0.50 = 6.3

هذا هو بالضبط المكان الذي نحتاج إليه

رسم دوّار

نفس الشيء لـ 60 درجة في اتجاه عقارب الساعة

تدوير 60 درجة

الموضع على الدائرة هو 0.87 و0.50

3.0 * 0.50 + 9.0 * 0.87 = 9.3
9.0 * 0.50 - 3.0 * 0.87 = 1.9

يتضح لك أنه أثناء تدوير هذه النقطة في اتجاه عقارب الساعة إلى اليمين، تزداد قيمة X وتصغر قيمة Y. إذا استمر التجاوز إلى ما بعد 90 درجة، فإن X ستبدأ في الصغر مرة أخرى وستبدأ Y في الزيادة. يعطينا هذا النمط دوران. هناك اسم آخر للنقاط في دائرة الوحدة. يُدعى جيب الزاوية وجيب التمام. إذًا، لننظر إلى جيب الزاوية وجيب التمام على النحو التالي لأي زاوية معينة.

function printSineAndCosineForAnAngle(angleInDegrees) {
  var angleInRadians = angleInDegrees * Math.PI / 180;
  var s = Math.sin(angleInRadians);
  var c = Math.cos(angleInRadians);
  console.log("s = " + s + " c = " + c);
}

في حال نسخ الرمز ولصقه في وحدة تحكُّم JavaScript وكتابة printSineAndCosignForAngle(30)، سيظهر لك الرمز يطبع s = 0.49 c= 0.87 (ملاحظة: لقد اختفت الأرقام.) إذا جمعت كل العناصر معًا، يمكنك تدوير الأشكال الهندسية بأي زاوية تريدها. ما عليك سوى ضبط التدوير على جيب الزاوية وجيب التمام للزاوية التي تريد التدوير إليها.

  ...
  var angleInRadians = angleInDegrees * Math.PI / 180;
  rotation[0] = Math.sin(angleInRadians);
  rotation[1] = Math.cos(angleInRadians);

آمل أن تكون هذه المعلومات مفيدة. بعد ذلك واحدة أبسط. الضبط.

ما المقصود بوحدات راديان؟

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

قد تكون على دراية بأن الرياضيات باستخدام قياسات المتري أسهل من الرياضيات بالقياسات الإمبراطورية. للانتقال من بوصة إلى قدم، نقسم على 12. للانتقال من بوصة إلى ياردة، نقسم على 36. لا أعرف شيئًا عنك لكن لا أستطيع القسمة على 36 في رأسي. باستخدام المقياس، العملية أسهل بكثير. للانتقال من ملليمتر إلى سنتيمتر، نقسم على 10. من الملليمتر إلى الأمتار نقسمه على 1000. يمكنني القسمة على 1000 في رأسي.

تتشابه وحدات راديان مقابل الدرجات. الدرجات تجعل الرياضيات صعبة. تسهل أجهزة راديان العملية الحسابية. هناك 360 درجة في الدائرة ولكن لا يوجد سوى 2باي راديان. لذا فإن الانعطاف الكامل هو 2باي راديان. نصف دوران هو باي راديان. دوران 1/4، أي أن 90 درجة تساوي باي/2 راديان. لذلك، إذا أردت تدوير عنصر بزاوية 90 درجة، ما عليك سوى استخدام Math.PI * 0.5. إذا أردت تدويره بزاوية 45 درجة، استخدم Math.PI * 0.25 وما إلى ذلك.

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

مقياس WebGL ثنائي الأبعاد

التوسعة سهلة مثل الترجمة

ونضرب الموضع في المقياس المطلوب. وفي ما يلي التغييرات من النموذج السابق.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y +
        scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y -
        scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

ونضيف JavaScript اللازم لضبط المقياس عند الرسم.

  ...
  var scaleLocation = gl.getUniformLocation(program, "u_scale");
  ...
  var scale = [1, 1];
  ...
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Set the scale.
    gl.uniform2fv(scaleLocation, scale);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

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

ما السبب في أنّ الحرف F؟

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

اتجاه F

أي شكل يمكنك معرفة اتجاهه سيعمل، لقد استخدمت "F" للتو منذ أن عرفت أولاً الفكرة.

مصفوفات WebGL 2D

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

على سبيل المثال، إليك المقياس 2 و1 ودوران 30% وترجمة 100 و0.

تدوير وترجمة F

وإليكم ترجمة للرقم 100.0، ودوران بنسبة 30% ومقياس 2، 1

التدوير والمقياس F

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

1 2.0 3
4 5 6.0
7 من نظام التشغيل Android 9

لإجراء الحسابات، نضرب الموضع لأسفل في أعمدة المصفوفة ونجمع النتائج. تحتوي مواضعنا على قيمتين فقط، س وص، ولكن لإجراء هذه العملية الحسابية، نحتاج إلى 3 قيم، لذا سنستخدم 1 للقيمة الثالثة. في هذه الحالة ستكون النتيجة على النحو التالي

newX = x * 1.0 + y * 4.0 + 1 * 7.0

newY = x * 2.0 + y * 5.0 + 1 * 8.0

extra = x * 3.0 + y * 6.0 + 1 * 9.0

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

10.00.0
0.010.0
txتاي1

يمكنك الآن الاطّلاع على

newX = x * 1.0 + y * 0.0 + 1 * tx

newY = x * 0.0 + y * 1.0 + 1 * ty

extra = x * 0.0 + y * 0.0 + 1 * 1

إذا كنت تتذكر جبرك، يمكننا حذف أي مكان يضرب فيه الصفر. الضرب في 1 لا يفعل أي شيء بشكل فعال، لذلك لنسهل عليك معرفة ما يحدث

newX = x + tx;
newY = y + ty;

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

s = Math.sin(angleToRotateInRadians);
c = Math.cos(angleToRotateInRadians);

وننشئ مصفوفة مثل هذه

c0.0
sc0.0
0.00.01

عند تطبيق المصفوفة نحصل على

newX = x * c + y * s + 1 * 0

newY = x * -s + y * c + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

قم بحجب كل شيء وضربه في 0s و1s نحصل عليه

newX = x *  c + y * s;
newY = x * -s + y * c;

وهذا ما كان لدينا في نموذج التناوب. وأخيرًا، المقياس. سنسمي عاملَي المقياس sx وsy وننشئ مصفوفة مثل هذه

صغير0.00.0
0.0سي0.0
0.00.01

عند تطبيق المصفوفة نحصل على

newX = x * sx + y * 0 + 1 * 0

newY = x * 0 + y * sy + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

وهو أمر مهم حقًا

newX = x * sx;
newY = y * sy;

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

function makeTranslation(tx, ty) {
  return [
    1, 0, 0,
    0, 1, 0,
    tx, ty, 1
  ];
}

function makeRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);
  return [
    c,-s, 0,
    s, c, 0,
    0, 0, 1
  ];
}

function makeScale(sx, sy) {
  return [
    sx, 0, 0,
    0, sy, 0,
    0, 0, 1
  ];
}

الآن دعونا نغير أداة التظليل. بدا عنصر التظليل القديم هكذا

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y + scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y - scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;
  ...

ستكون أداة التظليل الجديدة أبسط بكثير.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  vec2 position = (u_matrix * vec3(a_position, 1)).xy;
  ...

وإليك كيفية استخدامنا لها

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix =
       makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

مع ذلك، قد تسأل، فماذا في ذلك؟ لا يبدو ذلك مفيدًا جدًا . أما الآن، إذا أردنا تغيير الترتيب، فلا نحتاج إلى كتابة أداة تظليل جديدة. يمكننا فقط تغيير الحسابات.

    ...
    // Multiply the matrices.
    var matrix = matrixMultiply(translationMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, scaleMatrix);
    ...

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

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix = makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Starting Matrix.
    var matrix = makeIdentity();

    for (var i = 0; i < 5; ++i) {
      // Multiply the matrices.
      matrix = matrixMultiply(matrix, scaleMatrix);
      matrix = matrixMultiply(matrix, rotationMatrix);
      matrix = matrixMultiply(matrix, translationMatrix);

      // Set the matrix.
      gl.uniformMatrix3fv(matrixLocation, false, matrix);

      // Draw the geometry.
      gl.drawArrays(gl.TRIANGLES, 0, 18);
    }
  }

ولتنفيذ ذلك، قدّمنا الدالة makeIdentity التي تنشئ مصفوفة هويات. مصفوفة الهوية هي مصفوفة تمثل فعليًا 1.0 بحيث إذا ضربت في الهوية لا يحدث شيء. تمامًا مثل

X * 1 = X

وكذلك

matrixX * identity = matrixX

إليك التعليمة البرمجية لإنشاء مصفوفة هويات.

function makeIdentity() {
  return [
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
  ];
}

مثال آخر: في كل عينة حتى الآن، يتم تدوير الحرف F حول زاويتها اليسرى. وذلك لأن العملية الحسابية التي نستخدمها دائمًا تدور حول الأصل، وأن الزاوية العلوية اليسرى من 'F' هي في الأصل، (0، 0). ولكن الآن، نظرًا لأنه يمكننا إجراء عمليات حسابية باستخدام المصفوفة ويمكننا اختيار الترتيب الذي يتم فيه تطبيق التحويلات، يمكننا نقل الأصل قبل تطبيق بقية التحويلات.

    // make a matrix that will move the origin of the 'F' to
    // its center.
    var moveOriginMatrix = makeTranslation(-50, -75);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(moveOriginMatrix, scaleMatrix);
    matrix = matrixMultiply(matrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

وباستخدام هذا الأسلوب، يمكنك التدوير أو تغيير الحجم من أي نقطة. والآن أنت تعرف كيف يتيح لك Photoshop أو Flash تحريك نقطة التدوير. دعونا ننتقل إلى مزيد من الجنون. إذا عدت إلى المقالة الأولى حول أساسيات WebGL، قد تتذكر أنّ لدينا رمزًا في أداة التظليل لتحويله من وحدات بكسل إلى مساحة تخزين تشبه هذه المساحة.

  ...
  // convert the rectangle from pixels to 0.0 to 1.0
  vec2 zeroToOne = position / u_resolution;

  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // convert from 0->2 to -1->+1 (clipspace)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

إذا نظرت إلى كل خطوة من هذه الخطوات تباعًا، فإنّ الخطوة الأولى، "التحويل من وحدات البكسل إلى 0.0 إلى 1.0"، هي في الحقيقة عملية مقياس. والعملية الثانية هي أيضًا عملية على نطاق واسع. والثاني هو الترجمة، بينما يرمز آخرها إلى ص في 1. يمكننا في الواقع تنفيذ كل ذلك في المصفوفة التي نمررها إلى أداة التظليل. يمكننا إنشاء مصفوفتين على المقياس، أحدهما لقياسه بمقدار 1.0/دقة، والآخر لضبطه بمقدار 2.0، وثالثًا للترجمة بـ -1.0، و-1.0، ورابع لقياس ص في -1 ثم ضربها جميعًا معًا ولكن بدلاً من ذلك، نظرًا لبساطتها، سننشئ دالة تقوم فقط بإنشاء مصفوفة "إسقاط" لدقة معينة مباشرةً.

function make2DProjection(width, height) {
  // Note: This matrix flips the Y axis so that 0 is at the top.
  return [
    2 / width, 0, 0,
    0, -2 / height, 0,
    -1, 1, 1
  ];
}

الآن يمكننا تبسيط أداة التظليل بشكل أكبر. إليك أداة تظليل الرأس الجديدة بالكامل.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>

وفي JavaScript، نحتاج إلى الضرب في مصفوفة الإسقاط

  // Draw the scene.
  function drawScene() {
    ...
    // Compute the matrices
    var projectionMatrix =
       make2DProjection(canvas.width, canvas.height);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);
    matrix = matrixMultiply(matrix, projectionMatrix);
    ...
  }

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

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