واجهات التصيير الثلاثي الأبعاد و OpenGL

كتبه بركات يوم الخميس, 14 كانون الثاني 2016

التصيير الثلاثي الأبعاد 3D rendering هي عمليات تحويل المجسم الثلاثي الأبعاد إلى صورة أو مجموعة صور كفلم، هناك أنواع منها، منها التصيير الفوري والغير فوري، الفوري غالباً يستخدم في مثلاً الألعاب والغير فوري يستخدم مثلاً عند تحويل المشهد في برامج التصميم والنمذجة مثل Blender و 3D Max إلى صورة أو فلم، هذه البرامج تستخدم أيضاً التصيير الفوري عند مرحلة التصميم والغير فوري عند تصيير الصورة أو الفلم، جودة الغير فوري طبعاً أفضل بكثير، حيث يمكن استخدام تقنيات وحسابات أدق لكنها تستغرق وقت طويل، قد يصل إلى أيام حتى لو كنت تستخدم حواسيب خارقة كما في حالات تصيير الأفلام السينمائية الثلاثية الأبعاد، ولاتناسب بعض التطبيقات مثل الألعاب التي تتطلب إنهاء تصيير الإطار في مثلاً 1/60 ثانية.

هناك العديد من الواجهات البرمجية API المخصصة للتصيير الثلاثي الأبعاد مثل Direct3D، وOpenGL، وMantle والواجهة الجديدة 11[Vulkan]، وغيرها، تستخدم هذه الواجهات في التطبيقات التي تتطلب تصيير فوري real-time rendering مثل برامج التصميم الهندسي والفني والألعاب، حيث توفر واجهة برمجية موحدة للتخاطب -غالباً- مع المعالجات الرسومية GPUs، لكن يمكن أن تجري بعضها تصيير جزئي أو كلي باستخدام البرامج عن طريق المعالج حسب التطبيق implementation، قد تكون هناك معالجات رسومية من مصنعين مختلفين تستخدم معماريات مختلفة، لكن هذه الإختلاف يمكن إخفاءه خلف هذه الواجهات، فبدلاً مثلاً من كتابة برنامج يعمل فقط على معمارية محددة مثل معالجات Nvidia GeForce 9000، فيمكنك باستخدام هذه الواجهات كتابة برامج تعمل على أي معمارية تدعم هذه الواجهة.

أغلب هذه الواجهات البرمجية عبارة عن مواصفات، حيث يطبق مصنعين المعالجات الرسومية هذه الواجهات ويوفرون مشغلات العتاد drivers وبيئة التشغيل runtime الازمة لاستخدامها، المواصفات شيء مثل:

الدالة DrawLine(x0, y0, x1, y1) ترسم خط مستقيم بين النقطتين $(x_0, y_0)$ و $(x_1, y_1)$ بسمك معين.

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

تختلف هذه الواجهات في المرونة ومقدار التحكم التي تسمح لك به على عملية التصيير، مثلاً الإصدارات القديمة من OpenGL لم تكن تسمح بذلك التحكم في عملية التصيير مقارنة بالإصدارات الأحدث، الإصدارات الأحدث من واجهات التصيير عموماً تميل إلى جعل المشغلات وبيئات التشغيل أنحف وتقربك للعتاد، بعض هذه الواجهات مثل Mantle و DirectX 12 تعطيك تحكم شبه كامل على إدارة بيئة التشغيل كي تطوع العتاد بالطريقة التي تناسب تطبيقك ومن ثم -إن أحسنت عملها- يتحسن أداء تطبيقك، لكن هذا التحكم يتطلب فهم عميق لعملية التصيير وفهم للعتاد والواجهة البرمجية.

لننظر مثلاً إلى Direct3D، وهي مكون من عدة مكونات للـDirectX، حيث أن Direct3D هي المكون المسؤول عن التصيير الثلاثي الأبعاد، DirectX عموماً مبنية حول Component Object Model أو اختصراً COM، فكر بها على أنها آلية موحدة مستقلة عن المترجمات ولغات البرمجة لتصدير الفئات في C++ من مكتبات الربط الديناميكي في ويندوز DLL، الجيد في DirectX أنه يوفر لك كل ماتحتاجه من مكونات لبناء التطبيق مثل التصيير الثلاثي الأبعاد، وتصيير النصوص، والصوتيات وغيرها، والميزة الجيدة به أن جودة تعريفات ومشغلات Direct3D غالباً أفضل نظراً لكون الكثير من التطبيقات تستخدمها، ومن ثم يزداد البلاغات عن مشاكل وأخطاءها ومن ثم تتحسن، لكن العيب في DirectX عموماً أنه محصور في أنظمة تشغيل ويندوز NT، يمكن نظرياً نقلها لأنظمة أخرى لو كانت هناك رغبة جادة لكن حتى الآن لم يحصل هذا على حد علمي.

لن نتحدث بالتفصيل عن Mantle، فهي وجدت لحل المشكلة عندما تتسبب بيئة التشغيل في خلق نقطة اختناق لتطبيقك عندما تريد رسم العديد من الأشكال، وهذه المشكلة حلت في DirectX 12 وكذلك باستخدام Vulkan (حيث أن Vulkan بالنسبة لـOpenGL 3.3 مثل DirectX 12 لـDirectX 11) وذلك بجعل جزء كبير من مسؤولية إدارة بيئة التشغيل على عاتق المبرمج كما أشرت ويبني التطبيق بالطريقة التي تناسبه، فلم يعد هناك حالياً داعي لاستخدام Mantle خصوصاً أنها تعمل فقط على كروت الشاشة من AMD، والآن تتوفر بدائل قياسية عنها مثل DirectX 12 و Vulkan كما أشرنا، Vulkan لاتزال جديدة وأتتني رسالة على الإيميل قبل عدة أيام من Nvidia عن صدور مشغلاتها، طبعاً لست بحاجة لتعلم أياً من DirectX 12 أو Vulkan في الوقت الحالي، فلن تحتاجها إلا إذا وصلت لمرحلة تكتب فيها تطبيقات عالية الأداء، في الوقت الحالي DirectX 11 و OpenGL 3.3 أكثر من كافية للبدء، استخدام DirectX 12 أو Vulkan سيجعل تطبيقك معقد دون أي فائدة إذا لم يكن المعالج عائق أمامك.

هناك أيضاً OpenGL، ويدعمها العديد من العتاد وأنظمة التشغيل، OpenGL تهتم فقط بعملية التصيير، مثل Direct3D في DirectX، ولاتحدد أي متطبات لعمل حتى الأشياء الأساسية مثل إنشاء النوافذ، والتعامل مع الإدخال، نظراً لأنها قد تحد من محموليها، إنشاء النوافذ والإدخال والصوتيات يمكن عملها باستخدام مكتبات خارجية مثل GLFW و SDL وغيرها.

تعد OpenGL واحدة من أقدم الواجهات البرمجية التي لازالت حية، ظهرت في أوئل التسعينيات، في الإصدارات القديمة قبل OpenGL 2.0 كانت تعتمد على الدوال للتحكم في التصيير، تسمى fixed-function pipline مثل glBegin و glEnd و glVertex* وغيرها، مثال بسيط لرسم مثلث باستخدام OpenGL 1.1 مع GLFW للنوافذ والإدخال:

#include <GLFW/glfw3.h>

static const int WINDOW_WIDTH = 300;
static const int WINDOW_HEIGHT = 300;

int main(int argc, char **argv)
{
  glfwInit();
  glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

  GLFWwindow *Window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT,
                                        "Old OpenGL Hello World!",
                                        nullptr, nullptr);
  glfwMakeContextCurrent(Window);

  glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

  while (!glfwWindowShouldClose(Window)) {
    glfwPollEvents();

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glBegin(GL_TRIANGLES);
    {
      glColor3f(1.0f, 0.0f, 0.0f);
      glVertex3f(-0.5f, -0.5f, 0.0f);
      glColor3f(0.0f, 1.0f, 0.0f);
      glVertex3f(0.5f, -0.5f, 0.0f);
      glColor3f(0.0f, 0.0f, 1.0f);
      glVertex3f(0.0f, 0.5f, 0.0f);
    }
    glEnd();

    glfwSwapBuffers(Window);
  }

  glfwDestroyWindow(Window);
  glfwTerminate();
  return 0;
}

بعد OpenGL 2.0 أصبحت OpenGL تعتمد على المضللات shaders، وهي برامج صغيرة تعمل على المعالجات الرسومية، أمكن هذا من فتح باب لعمل لطيف واسع من التأثيرات لم يكن عملها بسهولة في الإصدارات الأقدم، بدءً من OpenGL 3.0، أصبح استخدام الطرق القديمة مستنكر deprecated، وإزيلت من OpenGL 3.3 ومافوق، وأصبح الآن يلزمك استخدام المضللات، طبعاً يمكن الوصول للدوال القديمة عن طريق وضع التوافقية، لكن من الأفضل تجنبها واستخدام الإصدارات الجديدة، يمكنك البدء من OpenGL 3.3 حيث أن الإصدارات التي تليها مجرد إضافات عليها قد لاتحتاجها.

الشيئ المهم أن تعرفه أن OpenGL تخفي الكائنات عنك، مثلاً في واجهة برمجية عادية سنجد دوال مثل:

Object o = GetObject();
ObjectSetValue(o, 1);

الأمر مختلف في OpenGL، حيث أن الكائن Object مخفي وتحتاج لتفعيله، المثال السابق ستجد شيء مثل:

Enable(OBJECT);
SetValue(1);

أجده شيء غريب لكن لابد أن تتعامل معه.

إذا أردت البدء مع OpenGL الحديثة، 3.3 فأكثر، ستحتاج إلى مكتبة للنوافذ والإدخال، هناك الكثير من المكتبات، لكني وجدت أن GLFW أبسطها، بعض المكتبات مثل SDL توفر أشياء قد لاتحتاجها حالياً وأنت في مرحلة التعلم مثل خيوط المعالجة threads ووظائف للملفات وغيرها، أيضاً إذا كنت على ويندوز فيجب أن تعلم أن مكتبة OPENGL32.DLL في ويندوز تصدر فقط الدوال القديمة، 1.1 ومايسبقها، ستحتاج لتحميل الدوال الحديثة ديناميكياً باستخدام wglGetProcAddress، هذه المشكلة يمكن تجاوزها باستخدام مكتبة أخرى اسمها GLEW، حيث تحمل الدوال ديناميكياً وتجهزها لك.

ستجد هنا مثال بسيط كتبته لنفس المثال السابق لكن باستخدام OpenGL 3.3، هناك مصادر جديدة جميلة لتعلم OpenGL 3.3 أذكر منها open.gl و learnopengl.com.