وسام البهنسي

مدونة مبرمج معماري

أنت والمصفوفات! (الجزء الأول)

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

 

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

 

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

 

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

 

لماذا المصفوفات؟

 

تستطيع المصفوفة الواحدة إنجاز كافة العمليات الرياضية من إزاحة وتحجيم ودوران بأي ترتيب وبأي عدد. مثلاً، إزاحة فتدوير فتكبير فإزاحة فإسقاط. نعم. فباستخدام مصفوفة مكونة من 16 عنصراً (4×4) تستطيع تحجيم وتدوير وتحريك المجسمات بل وإسقاطها على الشاشة أو تحويلها إلى ظلال مسطحة وعمليات كثيرة أخرى…

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

 

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

struct vector3 { float x,y,z; }; // بنية تمثل رأس مثلث في الفراغ
 
// قائمة ثابتة برؤوس المثلث
vector3 triangle_vertices[] =
{
   { 0.5f, 1.0f, 0.0f },   // الرأس العلوي
   { 0.0f, 0.0f, 0.0f },   // الرأس الأيسر
   { 1.0f, 0.0f, 0.0f },   // الرأس الأيمن
};
 
float fScaleAmount = 0.0001f;  // مقدار التضخيم كل لقطة
float fMoveAmount = 0.0001f;  // مقدار الإزاحة كل لقطة
 
float fScaleAmountNow = 1.0f + fScaleAmount * fTimeElapsedInSeconds;  // مقدار التضخيم في هذه اللحظة
float fMoveAmountNow = fMoveAmount * fTimeElapsedInSeconds;  // مقدار الإزاحة في هذه اللحظة
 
vector3 new_vertices[3]; // قائمة الرؤوس بعد تطبيق كافة التحويلات
 
// تطبيق التضخيم
for (int i=0;i<3;i++)
{
   new_vertices[i].x = triangle_vertices[i].x * fScaleAmountNow;
   new_vertices[i].y = triangle_vertices[i].y * fScaleAmountNow;
   new_vertices[i].z = triangle_vertices[i].z * fScaleAmountNow;
}
 
// الإزاحة على المحور السيني
new_vertices[0].x += fMoveAmountNow;
new_vertices[1].x += fMoveAmountNow;
new_vertices[2].x += fMoveAmountNow;
 
// الرسم باستخدام دايركت ثري دي
pD3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,1,new_vertices,sizeof(vector3));
 
// الرسم باستخدام أوبن جي إل
glVertexPointer(3, GL_FLOAT,0,new_vertices);
glDrawArrays(GL_TRIANGLES,0,3);
(ملاحظة: إن لم تستوعب أسطر الرسم في دايركت ثري دي أو أوبن جي إل فلا تحزن، فهي ليست محط اهتمامنا الآن. يكفي أن تعرف أنها تستقبل مجموعة إحداثيات لرؤوس مثلثات وترسمها على الشاشة)
 
اضطررنا إلى عمل نسخة جانبية من الإحداثيات الأصلية، وتعديل هذه النسخة ورسمها، وذلك كي نستطيع تطبيق العملية الرياضية المطلوبة بشكل صحيح. الكود السابق يتعامل مع مثلث واحد فقط، فما بالك بمجسم من ثلاثين ألف مثلث يمثل شخصية تتحرك بكل الاتجاهات وتقوم بحركات معقدة؟ في كل لقطة تعدل إحداثيات رؤوس ثلاثين ألف مثلث. تخيل!
 
في الحقيقة هذا ما كان يفعله مبرمجو الرسوميات منذ عشر سنوات، ولعلك تلاحظ أن المجسمات في تلك الأيام كانت في غاية البساطة. اليوم لدينا معالج رسوميات مختص (GPU) قادر على أداء هذا العمل دون إنهاك برنامجك بهذه التفاصيل.
 
والآن كود الرسم الكامل مع استخدام المصفوفات ومعالج الرسوميات لإنجاز التحويل:
struct vector3 { float x,y,z; }; // بنية تمثل رأس مثلث في الفراغ
 
// قائمة ثابتة برؤوس المثلث
vector3 triangle_vertices[] =
{
   { 0.5f, 1.0f, 0.0f },   // الرأس العلوي
   { 0.0f, 0.0f, 0.0f },   // الرأس الأيسر
   { 1.0f, 0.0f, 0.0f },   // الرأس الأيمن
};
 
float fScaleAmount = 0.0001f;  // مقدار التضخيم كل لقطة
float fMoveAmount = 0.0001f;  // مقدار الإزاحة كل لقطة
 
float fScaleAmountNow = 1.0f + fScaleAmount * fTimeElapsedInSeconds;  // مقدار التضخيم في هذه اللحظة
float fMoveAmountNow = fMoveAmount * fTimeElapsedInSeconds;  // مقدار الإزاحة في هذه اللحظة
 
// في دايركت ثري دي
D3DXMATRIX matScale,matMove;
D3DXMatrixTranslation(&matMove, fMoveAmountNow, 0.0f, 0.0f); // مصفوفة الإزاحة
D3DXMatrixScale(&matScale, fScaleAmountNow, fScaleAmountNow, fScaleAmountNow); // مصفوفة التضخيم
 
D3DXMATRIX matXForm = matScale * matMove; // المصفوفة الشاملة للتضخيم فالإزاحة
 
pD3DDevice->SetTransform(D3DTS_WORLD,&matXForm); // ضبط دايركت ثري دي ليستخدم المسفوفة الجديدة
pD3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST,1,triangle_vertices,sizeof(float)*3); // الرسم
 
 
// في أوبن جي إل
glLoadIdentity(); // ضبط مصفوفة الوحدة
glScalef(fScaleAmountNow,fScaleAmountNow,fScaleAmountNow); // إضافة تحجيم على المصفوفة
glTranslatef(fMoveAmountNow,0,0); // إضافة إزاحة إلى المصفوفة
 
glVertexPointer(3, GL_FLOAT,0,triangle_vertices);
glDrawArrays(GL_TRIANGLES,0,3); // الرسم
 
إذن رأينا كيف نستطيع تعميم الحسابات وتوفير الجهد وتبسيط المعالجة وتوحيدها باستخدام المصفوفات. فالآن كل ما عليك فعله لتغيير مواقع الرؤوس هو تحويل إحداثيات كل منها بالمصفوفة المناسبة وصلى الله وبارك.
 
يختص معالج الرسوميات بتنفيذ عملية التحويل بسرعة كبيرة على رؤوس المجسمات، لذلك كما ترى في الكود أعلاه، لم نقم بذلك بأنفسنا، وإنما تركنا هذه المهمة المملة لمعالج الرسوميات…

 

في الجزء الثاني من هذه التدوينة إن شاء الله سأشرح لك ممّ تتألف المصفوفة وما هي عملية التحويل وكيف يتأتى لمصفوفة واحدة أن تعبر عن كافة العمليات التي ذكرناها في الأعلى.

 

إلى اللقاء…

التعليقات (3) -

  • مؤيد

    28/07/2009 08:32:23 م | الرد

    شكراً وسام، استمتعت جداً واستفدت وأنا أقرأ هذه التدوينة، بانتظار الجزء القادم.

  • ياسر

    05/08/2010 05:56:05 ص | الرد

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

  • الج��ائر

    04/09/2010 04:57:46 م | الرد

    السلام عليكم ورحمة الله تعالى وبركاته
    جعل الله كل حرف في ميزان حسناتك أستاذنا وسام
    شرح رائع حقا ومعلومات قيمة استفدنا منها , مع بعض الإبهام في عدة دوال
    هللك أن تشرح لنا وظيفة هاته الدوال

    fTimeElapsedInSeconds / D3DXMATRIX / glLoadIdentity

أضف تعليقاً

Loading