نظام الإحداثيات : دورة OpenGL لغة c++ الدرس الحادي عشر


نظام الإحداثيات هو أهم جزء من أجزاء الرسم المتعلقة بــ 3D حيث سنصل في هذا الدرس إلى بعض مناطق البرمجة الإحترافية قبل الإنطلاق إلى عالم الكاميرات.[1] تحدثنا في الدرس السابق عن إضافة المصفوفات والتعديل عليها عن طريق الإزاحة والتحجيم والتدوير. لكن نظام الإحداثيات كان مبهم وغير واضح في إدارة المشهد.[1]يعبر OpenGL عن جميع النقاط التي نريد رؤيتها وفقًا إلى نظام الإحداثيات. بالتالي فإن جميع إحداثيات XYZ هي بالأساس واقعة بين النقطتين -1 و 1 من المجال (العمق Z).[1] على سبيل المثال , فإن أي من النقاط التي خارج هذا المدى سوف لن يتم رؤيتها في المشهد , ما يعني أن نظام الإحداثيات مرتبط في موقع العرض من الفضاء فقط مع الإحتفاظ بكافة القيم في الذاكرة.[1]

إن ما يميز تحديث المواقع  هو نظام NDC الذي يتم إدراجه في ملف Vertex Shader. حيث يقوم بالإشارة إلى النماذج ليتم تحويلها من مشاهد 3D إلى شاشة العرض 2D .[1]

يشير المصطلح NDC إلى Normalized device coordinate حيث أنه بذلك مجموعة من الإجراءات التي تعمل على تحويل المصفوفات من المشهد الثلاثي إلى شاشة 2D.[2] يتم تنفيذ خاصية التحويل NDC في شاشة الحاسوب عن طريق خطوات محددة ويتم تطبيقها على الكائنات في عالم ثلاثي الأبعاد قبل تحويلها إلى إحداثيات شاشة العرض.[1]



أنواع نظام الإحداثيات[1]

  1. الفضاء المحلي(Local space).
  2. العالمي(World space).
  3. فضاء العرض(View space).
  4. المقتطف(Clip space).
  5. فضاء الشاشة(Screen space).

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



الصورة العامة



يتبنى نظام الإحداثيات في أنظمة الرسم مجموعة من الإختلافات لكل مرحلة. على سبيل المثال لإجراء التحويل من فضاء محدد إلى فضاء آخر يتم استخدام العديد من المصفوفات والتي تشكل أهمية كبيرة بالنسبة لوسائل العرض.[1]

يشار إلى نظام الإحداثيات بالنموذج Model و view و projection. لك أن تتخيل بأن نظام الإحداثيات الخاص بنا يبدأ بالمرحلة الأولى local space على أنها إحداثيات محلية فهي تشكل مكونات النموذج ذاته في فضاء الرسم(مربع أو مثلثات أو مضلعات).

ثم نتخطى تلك المرحلة إلى ما يسمى world coordinates و view coordinate. ثم أخيرًا screen coordinate أو clip coordinate وصولاً إلى شاشة العرض في حاسوبك.[1]

 

نظام الإحداثيات
صورة من كتاب learn OpenGL تظهر مراحل الرسم في نظام الإحداثيات.

 


المحلي(Local space)



يشار إليها أيضًا بإحداثيات النماذج وهي باختصار مكونات الكائن ذاته وأماكن إحداثياته بالنسبة لنقطة الأصل الخاصة به.[1] على سبيل المثال يمكن اعتبار إحداثيات المكعب نموذجا لها.لو أنك قمت بإنشاء كائن عبر برنامج Blender فإن مكونات الكائن تقع تحت منطقة local space. ولك أن تتخيل كم من النماذج بمكوناتها الخاصة التي يمكن جلبها لمنطقة العرض.[1]

الإحداثيات المتعلقة في النموذج تتراوح ما بين 0.5 و -0.5 وتعتبر الإحداثية 0.0 مركزا لعملية الرسم على مستوى Local space فقط.[1]

 


الفضاء العالمي(Local space)



في حال أردنا استدعاء جميع كائنات الرسم إلى مشروعنا فإن وجود إحداثياتها سوف يتركز في نقطة واحدة من منطقة الأصل Origin في العرض , وهذا ما لا نريده على الإطلاق.[1] نحن بالطبع نحتاج أن نحدد أماكنها في مواقع مختلفة من العالم الواسع وبالتالي فإن أماكنها في الفضاء الواسع هي بالطبع ما يطلق عليه World space. [1]

على سبيل المثال , يشكل كل نموذج موقعه بالنسبة لنقطة الأصل (0,0,0). جميع محركات الرسم والتصميم تحتوي على نقطة الأصل التي يبدأ من خلالها البرنامج في العمل.[1] تقوم مصفوفة model بعمليات التحويل التي تتضمن تحجيم وتدوير الكائنات إلى نظام الإحداثيات الخاص في العالم وفقاً للاتجاهات والمواقع التي تنتمي إليها.[1]

 

على سبيل المثال , يمكن تشبيه الأمر بمجموعة من البيوت التي يتم عرضها في الألعاب فهي بالأصل تقع على محاور XYZ مختلفة ومتفاوتة بالفعل!.

 


فضاء العرض(View space)



يشير فضاء العرض(View space) إلى ما يريد بالفعل البشر رؤيته في نظام الإحداثيات. وهو يتمثل بكاميرا من OpenGL أو يطلق عليها أيضا eye space.[1] يمكن اعتبار فضاء العرض بأنه نتيجة تحويل عالم الفضاء ثلاثي البعد إلى مشهد يستطيع المستخدم رؤيته والتفاعل معه. [1]

على سبيل المثال يشار إليه أيضًا بنقطة العرض Point of view ويتم تنفيذه عن طريق دمج من خصائص التحويل مثل التدوير والإزاحة والتحجيم للمشهد.[1] يتم تخزين جميع الخصائص السابقة في مصفوفة عرض تعمل على تحويل إحداثيات العالم إلى منطقة العرض. سنقوم بمناقشة هذه المسألة بالتفصيل لاحقًا.[1]

 


المقتطف(Clip space)



عندما تعمل ملفات GLSL تعبر مكتبة OpenGL عن نظام الإحداثيات بمدى يشمل كافة النقاط الواقعة من منطقة العرض.[1] بالتالي قد يتغاضى عن المشاهد المرسومة خارج جهة العرض مع الحفاظ على وجودها في البرنامج. يمكن إهمال كافة جوانب العرض الواقعة خارج المشهد مكتفيًا بذلك بما تظهره الشاشة للمستخدم.[1]

لكي يتم تحويل نظام الإحداثيات من منطقة العرض إلى مقتطف الصورة يتم استخدام تقنية تسمى بــ Projection matrix أو نظرية الإسقاط.[1] فهي تشير إلى مدى عميق جدًا يتراوح ما بين إحداثيات -1000 و 1000 درجة للأبعاد.[1]

بعد ذلك يتم إجراء نظام التحويل عبر NDC (-1 , 1). بالتالي سوف يتم تجاهل جميع الإحداثيات الواقعة خارج المدى.[1] لن يتم مشاهدة إحداثيات مصفوفة النقاط (1250 , 500 , 750) لطالما قيمة x تقع خارج المدى.[1]

وبمجرد تحويل جميع الإحداثيات لمرحلة clip space سوف يطلق على تلك العملية perspective division.[1] بينما يتم تقسيم جميع إحداثيات المصفوفة XYZ على متغير متجانس يسمى w. بالتالي ينتج لدينا متغير جديد سوف يؤدي إلى تحويل المنظور إلى من مصفوفة 4D إلى 3D عن طريق NDC التي تتولى عملية الظهور على شاشات العرض.[1]


صندوق frustum



لا يمكن لنا اقتطاع المشهد دون تعريف صندوق أو حافظة تتولى مهمة تضمين النماذج الظاهرة في المقتطف وتجاهل تلك الواقعة في الخارج.[1] يتم تسميتها بــ frustum في نظام الإحداثيات حيث أنها مكعب يضم منطقة العرض يعمل بالإسقاط Orthographic.[1]

يحتوي هذا المكعب على العرض والارتفاع الموازي لصندوق العرض في الشاشة , ليشمل ظهور المقتطف.[1] بالتالي لا يتم إجراء القص على جميع النماذج الواقعة داخله بينما يتم استثناء المناطق الواقعة خارج حدود المكعب.[1]

 

يتم تطبيق العمق z باستخدام صندوق frustum ويشار إليه بالسطح القريب والسطح البعيد.

 

يمكنك استخدام صندوق frustum عن طريق مكتبة glm التي قمنا بــ تضمينها في الدرس السابق.

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

تشير القيمتين الأولى والثانية إلى اليمين واليسار من مكعب العرض بينما تشير كل من القيمة الثالثة والرابعة إلى الأسفل والأعلى من منطقة العرض.[1] ما يعني أننا أمام صندوق من فضاء 3D تم تحديد العرض والارتفاع الخاص به.[1]

أول أربع قيم من الدالة السابقة تشير إلى أبعاد الصندوق القريب Near plane.[1] لكن نضع بالحسبان بأن القيمة الخامسة والسادسة تحدد المسافة بين الصندوق القريب والبعيد.[1] يتولى صندوق frustum مهام العرض في نظرية الإسقاط حيث يعمل على تحويل كافة نظام الإحداثيات إلى NDC. وذلك يعني تحويل العرض إلى شاشة حاسوبك المسطحة.[1]

 


نظام الإحداثيات والإسقاط



عندما تشاهد العالم الحقيقي من حولك ستلاحظ أنه كلما ابتعدت المسافة صغر حجم الأشياء إلى أن تصل إلى مدى لا تستطيع رؤية الأشياء بشكل واضح.[1] ينطبق ذلك أيضًا على نظام الإحداثيات المتعلق بالإسقاط أو يمكن تسميته perspective وهو غالبًا ما يحدث بسبب أنك تشاهد العالم من جزئين مختلفين.[1]

يتم استخدام الإسقاط عن طريق مصفوفة تقوم باحتساب مدى المقتطف لكي تعالج قيمة w لكل إحداثية ثم تعمل على تقديمها للمشاهد.[1] بمجرد تحويل الإحداثيات إلى المقتطف يتم وضعها بين قيمتي w و -w بينما تخرج أي إحداثية خارج العرض من المقتطف.[1]

بالتالي توفر لك مكتبة OpenGL مشاهدة الإحداثيات الواقعة بين -1 و 1.[1] يمكن إستخدام الإسقاط عن طريق دالة حسابية من مكتبة GLM على النحو التالي:

glm::mat4 proj = glm::perspective(45.0f, (float)width/(float)height, 0.1f,
100.0f);

في الدالة السابقة ستلاحظ أن المتغير الأول يدل على قيمة FOV اختصارا لــ Field of view وهو مقدار زاوية العرض بالنسبة لمقتطف frustum , وغالبًا ما يتم تحديدها على أنها زاوية حادة 45 درجة. بينما يدل المتغير الثاني على aspect ratio ويتم احتسابه عن طريق قسمة العرض على الإرتفاع.

يدل المتغير الأخير وقبل الأخير على تحديد المسافة القريبة والمسافة البعيدة لصندوق العرض frustum.[1] حيث أن كل ما بين القيمتين سيتم عرضه في المقتطف.[1]

 


تطبيق نظام الإحداثيات على المشروع



يبقى لدينا الآن تطبيق نظام الإحداثيات على المشروع الخاص بنا , وهي مسألة لن تستدعي الكثير من الوقت لأننا سنعتمد على جميع المكتبات في إعداد المشهد. لكي تتمكن من الرسم على طريقة 3D يجب إضافة مصفوفة Model وهي التي تتكون من التحويل والتحجيم والتدوير.

بينما تطبيقها يتطلب منا تحويل كافة إحداثيات العناصر إلى global world , وعلى فرض أن لدينا مصفوفة لتدوير العنصر أولا فإننا نقوم بتنفيذ الدالة التالية:

(1.0f)glm::mat4 model;
model = glm::rotate(model, -55.0f, glm::vec3(1.0f, 0.0f, 0.0f));

تشير الدالة السابقة إلى معالجة نظام الإحداثيات عن طريق دالة التدوير التي تم الإتصال بها , بالتالي فإن أي عملية تدوير مصفوفة النموذج سوف تؤثر بإحداثيات العرض في global world.

يعمل نظام OpenGL بالطريقة التالية , القيم الموجبة من x تقع على الجانب الأيمن من منطقة العرض بينما قيم z الموجبة تشير إلى المنطقة الأقرب إليك في الشاشة أو يمكن القول بالقرب من near plane في صندوق frustum.[1]

بعد تطبيق الوظيفة السابقة في كود البرنامج يجب أن ننشئ مصفوفة تحويل لمنطقة العرض. بالتالي فهي تقوم بعرض نقطة الأصل للمتغيرين XY باستثناء z التي سيتم تحديدها بقيمة سالبة.[1]

(1.0f)glm::mat4 view;
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

إن آخر ما يمكن استخدامه في نظام الإحداثيات هو مصفوفة الإسقاط وهو ما تشير إليه الوظيفة التالية:

glm::mat4 projection(1.0f);
        projection = glm::perspective(45.0f, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);

 


تعديلات ملف vertex shader



يجب أن ندرك أنه في حال قمت بتشغيل الشيفرة السابقة لن تعمل لديك على النحو المطلوب بسبب عدم تعديل نظام الإحداثيات في ملف vertex shader.[1] يتطلب منا الأمر تعريف متغيرات في ملف vertex shader وهما model و view و projection.[1]

 

يتم تعريف جميع مصفوفات الإسقاط بصيغة uniform , حيث يتم التعرف عليها على أنها صفات موحدة.

 

 

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

بينما يتم إجراء ضرب المصفوفات في منطقة gl_position لتصبح على النحو التالي:

gl_Position = projection * view * model * vec4(position, 1.0f);

 


الإتصال في الملف من الخارج



يجب إرسال المصفوفات إلى ملفات GLSL لكي تتولى مهام العرض على النحو المطلوب , وبالتالي يجب تعريف المصفوفات أولا:

glm::mat4 model(1.0f);
glm::mat4 view(1.0f);
glm::mat4 projection(1.0f);

بعد أن قمنا بتعريف المصفوفات سوف نقوم بتعبئة المصفوفات في البيانات المتعلقة بمنطقة نظام الإحداثيات:

model = glm::rotate(model, -45.0f, glm::vec3(1.0f, 0.0f, 0.0f));
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
        projection = glm::perspective(45.0f, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);

يتم في المرحلة القادمة إرسال المصفوفات إلى ملف Vertex shader.

GLint modelLoc = glGetUniformLocation(ourShader.Program, "model");
GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");
GLint projLoc = glGetUniformLocation(ourShader.Program, "projection");

ثم بعد ذلك تستلم مكتبة OpenGL القيم التي تم تحضيرها في ملفات GLSL.

glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

 

نظام الإحداثيات
صورة يظهر فيها تطبيق منظور الإسقاط على مشروع OpenGL.

 


تحويل النموذج إلى 3D



بالرغم من تفعيل نظام الإحداثيات ومنظور الإسقاط إلا أننا لا زلنا نستدعي نموذج ثنائي الأبعاد , ولربما من الأفضل جعل السطح المعروض على شكل مكعب مع إمكانية مضاعفة أعدادها في فضاء الرسم. لكن يتطلب ذلك إضافة المثير من إحداثيات الرسم التي يصعب تعبئتها بطريقة يدوية. [1]

بالتالي نعمل على تعديل إحداثية الرسم الواقعة على النموذج ويمكنك الحصول عليها من هنا.[1]

يمكنك أيضًا استبدال دالة glDrawElement بدالة رسم المصفوفة:

glDrawArrays(GL_TRIANGLES, 0, 36);

نقوم بإلغاء indices و EBO والعمل فقط بواسطة VBO بالتالي يتم التعديل في القيم لتبدو على النحو التالي:

// Position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // TexCoord attribute
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

عند تشغيل البرنامج ستشاهد مكعب واحد مكون من أسطح متعددة مع ظهور العمق مثل الصورة التالية:

نظام الإحداثيات
صورة يظهر فيها رسم مكعب عن طريق مكتبة OpenGL.

 

ربما نستطيع إضافة عمليات التدوير التلقائي على مصفوفة model المتعلقة بإحداثيات النماذج عن طريق الكود التالي:

model = glm::rotate(model, (GLfloat)glfwGetTime() * 1.0f, glm::vec3(0.5f, 1.0f, 0.0f));

عند تشغيل المشروع ستلاحظ بأن المكعب سيظهر مفتوح من جميع الجوانب عند دورانه , ولتخطي تلك المسألة يتطلب منا أمرين الأمر الأول هو تفعيل العمق عن طريق الوظيفة التالية:

glEnable(GL_DEPTH_TEST);

يفضل وضع الوظيفة السابقة خارج حلقة loop الخاصة بمنطقة الرسم , بينما الوظيفة التالية ستعمل في داخل الحلقة:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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

تفعيل العمق
صورة يظهر فيها تفعيل العمق Z في مكتبة OpenGL.

 


مضاعفة أعداد المكعبات في الفضاء



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

سوف نقوم بإنشاء مصفوفة من متجهات ثلاثية الأبعاد وسوف يتضمن كل متجه نظام الإحداثيات الخاص بكل مكعب , بالتالي ربما تتجاوز أعداد المكعبات 10 كما في الكود التالي:

glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};

الآن نقوم بإنشاء حلقة تكرارية تعمل على جلب البيانات من مصفوفة cube Positions , بالتالي لا ننسى إضافتها في منطقة الإستدعاء الخاصة بمكتبة OpenGL.[1]

    for (GLuint i = 0; i < 10; i++)
    {
        glm::mat4 model(1.0);
        model = glm::translate(model, cubePositions[i]);
        GLfloat angle = 20.0f * i;
        model = glm::rotate(model, angle, glm::vec3(1.0f, 0.0f, 0.0f));


        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

 

مضاعفة أعداد المكعبات
صورة تظهر أعداد من مكعبات العرض 3D في مكتبة OpenGL.

 

 

 

المراجع
  1. [1]^ كتاب ـــــــ offline learn OpenGL created by Joey de Vries.
  2. [2]^Device Coordinate.
  3. [3]^ مصادر المشروع من المراجع.

اترك تعليقاً

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