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

نظام الإحداثيات هو أحد أجزاء الرسم المتعلقة بــ 3D ، ويعبر 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]

\"الصورة

 


الفضاء المحلي (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]

يتم تخزين جميع الخصائص السابقة في مصفوفة عرض تعمل على تحويل إحداثيات العالم إلى منطقة العرض.


المقتطف(Clip space)


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

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

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

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

فهي تشير إلى مدى عميق جدًا يتراوح ما بين إحداثيات -1000 و 1000 درجة للأبعاد.[1]

ثم بعد ذلك يتم إجراء نظام التحويل عبر NDC (-1 , 1).

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

وبمجرد تحويل جميع الإحداثيات لمرحلة clip space سوف يطلق على تلك العملية perspective division.[1]

بينما يتم تقسيم جميع إحداثيات المصفوفة XYZ على متغير متجانس يسمى w.

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


صندوق frustum


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

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

يحتوي صندوق frustum على العرض والارتفاع الموازي لصندوق العرض في الشاشة ، ليشمل ظهور المقتطف.[1]

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

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

 

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

تشير القيمتين الأولى والثانية إلى اليمين واليسار من مكعب العرض ، بينما تشير كل من القيمة الثالثة والرابعة إلى الأسفل والأعلى من منطقة العرض.[1]

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

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

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


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


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

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

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

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

بالتالي توفر مكتبة OpenGL مشاهدة الإحداثيات الواقعة بين -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));

 

\"الإتصال

 


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


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

لكن يتطلب ذلك إضافة الكثير من إحداثيات الرسم التي يصعب ملؤها بطريقة ما. [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);

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

 

 

\"إضافة

 

 

غالبا ما يمكن إضافة عمليات التدوير التلقائي على مصفوفة 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);

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


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


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

كما يقدم لنا منظور الإسقاط إحداثيات كبيرة جدًا نستطيع من خلالها نشر العديد من النماذج.

وبالتالي يسهل العمل في عمليات الإستدعاء لطالما تجاوزنا المراحل السابقة من تضبيط بيئة العرض.[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);
    }

 

المراجع

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

اترك تعليقاً

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