كاميرا اوبن جل : دورة OpenGL لغة c++ الدرس الثاني عشر


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

عندما نتحدث عن الكاميرات نحن نتحدث عن نظام الإحداثيات المتعلق من منظور الكاميرا الواحدة فقط.[1] حيث أن مصفوفات العرض تعمل على تحويل إحداثيات العالم إلى الكاميرا على أنها نقطة الأصل. بالتالي ولكي نتمكن من تحديد كاميرا اوبن جل فإننا نحتاج إلى الموقع في world space. [1]


 


مكونات كاميرا اوبن جل[1]

  1. الموقع (Camera position).
  2. اتجاه الكاميرا (Direction).

 


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

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

نضع بالاعتبار بأن القيمة الثالثة من الإحداثيات تدل على محور Z في مرحلة world space.[1] ما يعني أن الكاميرا تحتل الإحداثية 3 في العالم.[1]

 

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

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

 

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

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

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

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

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

يتم الوصول إلى كل من قيمة X و Z من المتجهات السابقة ويمكن استخدامها في استخراج متجه جديد يشير إلى النظر للأعلى.[1]

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

 


خاصية Look At



من إحدى مزايا كاميرا اوبن جل هي خاصية Look At والتي تمكنك من إضافة مصفوفة من ثلاثة محاور مع إجراء تحويل على أي متجه منها من خلال ضرب المصفوفة.[1] بالتالي تتكون مصفوفة Look At من ثلاثة متجهات حيث أن R هو المتجه الأيمن بينما يشير المتجه U إلى الأعلى و D هو الإتجاه.[1]

توفر لنا مكتبة GLM جميع مكونات الكاميرات على أنها مصفوفات مجهزة من قبل.[1] على سبيل المثال مصفوفة الموقع وهدف التصوير بالإضافة إلى متجه النظر للأعلى في فضاء الرسم.

يمكنك إضافة مصفوفة Look At عن طريق الكود التالي:

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

 


تحريك الكاميرا



قبل الخوض في إدخالات المستخدم لنبدأ بعمل تدوير للكاميرا عند طريق بعض معادلات علم المثلثات.[1] حيث نقوم بتعيين إحداثيات X و Z لكل إطار يمثل نقطة في الدائرة. على سبيل المثال , نستطيع إعادة احتساب كل من قيم x و y فإننا بذلك نتخطى جميع النقاط في دائرة لينتج عنها التدوير حول المشهد.[1]

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

GLfloat radius = 10.0f;
GLfloat camX = sin(glfw GetTime()()) * radius;
GLfloat camZ = cos(glfw GetTime()()) * radius;

نقوم الآن بإجراء تعديل على مصفوفة العرض View التي تم بنائها سابقًا لتصبح على النحو التالي:

glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0),
glm::vec3(0.0, 1.0, 0.0));
كاميرا اوبن جل
صورة يظهر فيها تدوير كاميرا اوبن جل في فضاء العرض 3D.

 


التنقل بين النماذج



يعد التأرجح بين النماذج أمر ممتع حقا ولكن يتطلب إعداد نظام الحركة ونظام التسارع.[1] لكي نبدأ في هذا الإجراء يتعين علينا تعريف نظام كاميرا اوبن جل أعلى البرنامج على النحو التالي:

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 camera Front = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

يلي الكود السابق دالة Look At والتي يمكن ارفاقها في الكود لتصبح على النحو التالي:

view = glm::lookAt(cameraPos, cameraPos + camera Front, cameraUp);

لقد قمنا بتعيين موقع الكاميرا في المتجه السابق camera pos بينما يشكل الاتجاه الموقع الحالي + المتجه الذي قمنا بتعيينه.


تفعيل وحدة إدخال الحركة



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

GLfloat cameraSpeed = 0.05f;
if(key == GLFW_KEY_W)
cameraPos += cameraSpeed * camera Front;
if(key == GLFW_KEY_S)
cameraPos -= cameraSpeed * camera Front;
if(key == GLFW_KEY_A)
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) *
cameraSpeed;
if(key == GLFW_KEY_D)
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) *
cameraSpeed;

تمثل الشروط السابقة الأحداث التي تتولى القيام بها وحدات الإدخال لحظة الضغط على WASD من أزرار الكيبورد.[1]

 

يشير المصطلح WASD إلى أزرار لوحة المفاتيح W و S و A و D , وهي أحداث تتولى كاميرا اوبن جل التعامل في معادلات الحركة.

 

على سبيل المثال , عند الضغط السير للأمام أو الخلف فإن هناك إجراء يتولى القيام بطرح أو جمع المتجه بالنسبة لنقطة الأصل Origin.[1] بينما عند الاتجاه والحركة يمينا ويسارا سيتم تطبيق Cross product لإضافة المتجه المناسب.[1]

 


انسيابية الحركة



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

bool keys[1024];

وفي دالة key callback نقوم بإضافة السطور التالية:

if(action == GLFW_PRESS)
keys[key] = true;
else if(action == GLFW_RELEASE)
keys[key] = false;

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

void movement()
{
    // Camera controls
    GLfloat cameraSpeed = 0.01f;
    if (keys[GLFW_KEY_W])
        cameraPos += cameraSpeed * camera Front;
    if (keys[GLFW_KEY_S])
        cameraPos -= cameraSpeed * camera Front;
    if (keys[GLFW_KEY_A])
        cameraPos -= glm::normalize(glm::cross(camera Front, cameraUp)) *
        cameraSpeed;
    if (keys[GLFW_KEY_D])
        cameraPos += glm::normalize(glm::cross(camera Front, cameraUp)) *
        cameraSpeed;
}

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

 


تفعيل التسارع



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

يشار إلى delta time على أنها مخزن حقيقي لسرعة اللعبة ومشاهد العرض. على سبيل المثال فإن احتساب آخر إطار من العرض يستغرق وقت أكثر من المعدل.[1] يمكننا احتساب وقت دلتا عن طريق إضافة متغيرين في الأعلى من المشروع.[1]

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

في حلقة while الخاصة بمكتبة OpenGL نقوم بإضافة معادلة فرق الوقت التالية:

GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

بالتالي نذهب الآن إلى دالة do movement التي قمنا بإنشائها مؤخرًا ثم نقوم بإضافة المتغير التالي:

GLfloat cameraSpeed = 5.0f * deltaTime;

بعد ذلك نقوم بضرب المتغير بكل من قيم الإتجاه للأمام W والخلف S تمامًا مثل الشكل التالي:

if (keys[GLFW_KEY_W])
        cameraPos += cameraSpeed * camera Front;
    if (keys[GLFW_KEY_S])
        cameraPos -= cameraSpeed * camera Front;

 


النظر بزوايا متعددة



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

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

 


زوايا أويلر



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

سوف نستغني عن استخدام roll ونعمل على تطبيق كل من pitch و yaw في هذه الدورة. بالتالي نستطيع التحويل إلى 3D بطريقة أقل صعوبة.[1]

تمثل كل من القيمتين اتجاه جديد بالنسبة لـ كاميرا اوبن جل ويتطلب تحويل القيم إلى إدراك البعض من علم المثلثات.[1] على سبيل المثال , في حال قمنا بمعرفة مكان الوتر سوف يكون من السهل القيام باحتساب المثلثات.[1]

نقوم الآن بتعريف متغيرين في أعلى المشروع وليكن كما في الكود التالي:

GLfloat yaw = -90.0f;	
GLfloat pitch = 0.0f;

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

 


تفعيل مدخلات الفأرة



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

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

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

عند تفعيل المشروع الآن ستلاحظ بالفعل مدى اختفاء مؤشر الفأرة في صندوق العرض.[1] بل حتى أنها لن تخرج إحداثيات الفأرة من الصندوق في فترة تشغيل البرنامج.[1] لكي يتم احتساب كل من قيمة pitch و yaw لا بد من إخبار مكتبة GLFW الاستماع إلى حركة الفأرة وذلك من خلال الإعلان عن دالة قبل main:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

يمكنك الاتصال بها عن طريق الوظيفة التالية:

glfwSetCursorPosCallback(window, mouse_callback);

 


متطلبات تفعيل أويلر للفأرة


  • احتساب قيمة الإزاحة من آخر إطار.
  • إضافة نسبة الإزاحة بالنسبة إلى كاميرا اوبن جل.
  • إضافة القيم الثابتة عن أقصى وأقل قيمة لكل من Yaw و Pitch.
  • احتساب إتجاه المتجهات.

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

GLfloat lastX = 400, lastY = 300;

بعد تعيين مركز الفأرة على أنها قيمة افتراضية سوف يتسنى لنا القيام بنسخ الكود التالي داخل دالة mouse callback: 

GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos; 

    last X = xpos;
    last Y = ypos;

    GLfloat sensitivity = 0.05f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

 

لاحظ أننا قمنا بإضافة sensitivity وهي المسؤولة عن حركة المشهد بانسيابية ونعومة. لكن ضع في الإعتبار في حال أقدمت على مسح هذا المتغير سيتم تحريك المشهد بطريقة مزعجة.[1] الآن نقوم بالتأثير على كل من قيمة yaw و pitch عن طريق إضافة قيم الإزاحة الجديدة.

yaw += xoffset;
pitch += yoffset;

الآن سوف نقوم بتحديد بعض الثوابت التي تعيق المستخدم من تحريك كاميرا اوبن جل بشكل مزعج. [1]

if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0)
pitch = -89.0;

لاحظ في الكود السابق بأننا لم نقوم بتحديد حركة yaw لذلك نترك الخيار للمستخدم في الحركة.[1] الخطوة الأخيرة من تفعيل مدخلات الفأرة هي احتساب المتجه الفعلي لكل من قيمتي pitch و yaw كما في الكود التالي:

glm::vec3 front;
    front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
    front.y = sin(glm::radians(pitch));
    front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
    cameraFront = glm::normalize(front);

 

 

 

 

 

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

 

اترك تعليقاً

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