تفعيل texture : دورة OpenGL لغة c++ الدرس التاسع

تفعيل texture يساعد في تعبئة النماذج الخاصة بالرسم 2D و 3D

يمكن تفعيل texture ليعمل على جميع تقنيات الرسم ، حيث أن أفضل مثال على استدعائه هو نموذج منزل 3D.

لا شك بأنه يظهر أمام الكاميرا على أنه منزل حقيقي مليئ بقطع الرسم التي يتم تركيبها بواسطة الأنسجة Textures.

يقوم النسيج على تكرار حجارة ذلك المنزل أو نوافذه أو حتى مكوناته الأساسية من الأمام والخلف وكافة نواحي العرض.

سنعمل على تفعيل Texture على إحداثيات تم رسمها من الدروس السابقة.

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

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

 


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

 

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

حيث يتراوح مدى عرض النسيج بقيمة من 0 إلى 1 يتم تطبيقها على إحداثيات x و y من نواحي الرسم.[1]

نتذكر دائما بأننا نقوم بعملية تفعيل texture ثنائي الأبعاد ، وتسمى عملية التلوين بــ sampling مع نقطة بداية لكل من x و y بإحداثيات 0.0.[1] وتمثل إحداثيات البداية 0.0 اسفل يسار النسيج.

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

 

 

 

تفعيل Texture

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

وكذلك الأمر بالنسبة لباقي الإحداثيات حيث تشكل النقطة 1.0 من أسفل يمين المثلث.

[1] ولأننا نريد أن ننتهي من إحداثيات النسيج بأعلى قمة المثلث ، فإن ذلك يعني مركز الوسط من الإحداثيات والتي تشكل 0.5 للمتغير x و 1.0 للمتغير y.[1]

بالتالي يتم تمرير ثلاث إحداثيات عن طريق الظلال vertex shader.

ثم بعد ذلك تمريرها إلى fragment shader الذي سيعمل على إظهار النسيج في كافة الأجزاء من الرسم.[1]

تظهر إحداثيات تفعيل texture على هذا النحو من الكود :

GLfloat texCoords[] = {
        0.0f, 0.0f, // Low left corner
        1.0f, 0.0f, // Low right corner
        0.5f, 1.0f // Top center corner
    };

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

 

أنواع عينة Texture[1]

تأخذ العينات مدى من 0,0 و 1,1 بينما قد يحدث وأن تقع بعض الأنسجة خارج المدى.[1]

وبالتالي قد تأخذ مكتبة OpenGL إجراء في ذلك الأمر مثل تكرار صور النسيج بأشكال محددة من البيكسل.

والآن إليك بعض الإجراءات التي يتم تطبيقها في الشيفرة مثل:

  • GL REPEAT : يتم تكرار قطعة النسيج عند تفعيل Texture دون التدخل في هيكل الصورة المعروضة.
  • GL_MIRRORED_REPEAT : تأخذ سلوك GL_REPEAT ذاته لكنها تؤدي إلى العديد من الانعكاسات والانقلابات.
  • GL_CLAMP_TO_EDGE : تؤدي إلى مد الحواف من جميع أطراف النسيج مع الحفاظ على مركز الوسط.
  • GL_CLAMP_TO_BORDER : يؤدي هذا النمط إلى إظهار الصورة بحجمها الحقيقي في مركز المنتصف مع إحداثيات شبه فارغة.

لا بد من الإشارة بأن استخدام كل خاصية سيؤدي إلى مخرجات مختلفة السياق.

 

بناء شيفرة Texture

لكي نبدأ في تفعيل Texture يجب أن نحدد معمارية النسيج والتي تعمل بإحداثيات 2D.[1]

لكن نضع بالحسبان أن عرض الأنسجة 2D لا علاقة له باستخدام الفضاء 3D.

على سبيل المثال ، تختلف طبيعة المشهد وفقًا لاستخدام الكاميرا وليس لطبيعة الأنسجة.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

يدل العنصر الأول في الكود السابق على تفعيل Texture بتقنية 2D ، بينما القيمة الثانية تدل على مكان وضع النسيج في الإحداثيات.[2]

على سبيل المثال فإن GL_TEXTURE_WRAP_S هي بالواقع قيمة x التي تتراوح بين 0,1 ، وكذلك الأمر بالنسبة للقيمة GL_TEXTURE_WRAP_T فهي تدل على قيمة Y أو مكان انتهاء حافة النسيج 1,1.[2].

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

 

إضافة الحواف

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

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

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

حيث أنه يستلم الألوان المعدة مسبقًا من متغير طبقي بعنوان borderColor.[1]

 

تصفية ملامح النسيج

تظهر جوانب التصفية والتنعيم اعتماد كبير إلى جانب الحاجة إليها عند التعامل مع كائنات كبيرة من المشروع.

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

يمكن أن تقدم لك OpenGL خيارات جيدة عند تفعيل Texture قياسي دون هدر موارد العتاد.

ويتم ذلك من خلال GL LINEAR و GL NEAREST.[1]

أشهر عمليات التصفية هي GL NEAREST فهي قيمة افتراضية يتم إرفاقها خلال عملية التكوين.

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

وعند النظر إلى 4 بيكسل من النسيج فإن GL NEAREST تأخذ عينة من منطقة الوسط ، ليتم تطبيقها في عوامل التصفية.

الطريقة الثانية تسمى GL LINEAR ، حيث تأخذ نفس مكان الإحداثيات في الوسط ولكن مع عينة مزيج من الألوان الأربعة.[1]

 

 

\"تفعيل

 

تدل GL NEAREST على نمط مغلق يمكننا من رؤية جزيئات بيكسل بكل وضوح ، لكن ينصح باستخدام GL LINEAR في بطاقات العرض المحدودة.

وذلك لامتلاكها خصائص التنعيم في مزج الألوان مع إمكانية أقل في ملاحظة بروز البيكسل.

لكن يمكننا التغاضي عن موارد العتاد بإبراز وظيفتي التكبير والتصغير عن طريق الكود التالي:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 

وظائف mipmap

تشكل خاصية mipmap أهمية كبيرة أثناء تفعيل texture . لنتخيل أن لدينا غرفة كبيرة مع آلاف الكائنات وكل كائن يمتلك نسيج خاص به.[1]

بالتالي سيبقى المشهد محافظًا على أداء التباين الخاص كلما ابتعدت أو اقتربت منه.

وقد يجد OpenGL صعوبة في إرجاع القيم الصحيحة للألوان في التباين المرتفع.

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

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

تتخذ mipmap فكرة بسيطة للغاية ، فعند الإبتعاد عن النسيج تقوم مكتبة OpenGL باستخدام mipmap مختلفة تتناسب مع المسافة في المشهد.[1]

مع عدم ملاحظة اختفاء التفاصيل الدقيقة من النسيج. على سبيل المثال ، نحصل على صورة مصغرة عن الــ texture الفعلي.

وذلك أن هنالك مجموعة متعددة من النسيج يتم احتسابها من المكتبة وتجهيزها لشتى مقاييس العرض.[1]

 

أنواع mipmap[1]

عند التمرير بين أنواع ومستويات mipmap المختلفة ربما يجد OpenGL بعض الآثار المترتبة عليه عند الإستدعاء ومنها الحواف الحادة والبارزة.[1]

تعمل أيضًا خصائص الفلترة وإجراءات عرض العينة من النسيج وسيتم التبديل فيما بينها بناء على ذلك.

لا ننسى أنه عند عمليات التبديل ستوفر المكتبة للمبرمجين مجموعة من المزايا ومنها:

  • GL NEAREST MIPMAP NEAREST : تأخذ أقرب عينة mipmap متناسقة مع حجم البيكسل لأقرب تفعيل Texture.[1]
  • GL NEAREST MIPMAP LINEAR : تأخذ أقرب عملية متاحة من عينات MIPMAP مع أقرب لون من الألوان المتاحة.[1]
  • GL NEAREST MIPMAP LINEAR : اقحام بين قيمتين من عينات MIPMAP مع أقرب نقاط خطية للنسيج.[1]
  • خطية مقتحمة بين أقرب عينة MIPMAP مع عينات خطية أيضا من النسيج.

عند استخدام عينة mipmap يتطلب منا إجراء تعديل على الكود السابق التي قمنا بنسخه.

سيشمل التعديل آخر قيمة من الدالة الأولى, وبالتالي تصبح على النحو التالي:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 

إجراء تفعيل Texture

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

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

في حال لم تتوفر ملفات تكوين هذه الصيغة يمكنك الحصول عليها من شبكة الانترنت.[1]

يعد تفعيل texture بصيغ متعددة مشكلة كبيرة للمبرمجين ، ويعود ذلك إلى وجود عناصر مصفوفات مملة للغاية وتحتاج إلى الجهد والوقت خلال التحويل.[1]

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


مكتبة SOIL


تمتثل مكتبة SOIL إلى وظائف لغة OpenGL بشكل رائع ، فهي تدعم غالبية الصور التي يتم الإتصال بها عبر اللغة.[1]

بالإضافة إلى سهولة استخدامها وتطبيقها في المشروع.

بالتالي نعمل على تضمينها في المشروع الخاص بنا عبر ملفات Include و SOIL.lib.[1]

نقوم بعملية الربط بنفس الخطوات التي قمنا بها في بداية الدورة.

#include\"SOIL.h\"

يتطلب الربط إرفاق صورة ليتم عرضها ، ولذلك نقوم بإرفاق الصورة الخاصة لدى المراجع الخاصة بنا.

تستطيع تحميلها ووضعها في حاسوبك.

https://learnopengl.com/img/textures/container.jpg

يتم الاتصال بمكتبة SOIL عن طريق متغير من نوع char يستقبل القيم من دالة SOIL load image.

ولا بد من تهيئة أبعاد الصورة التي نريد استخدامها ليصبح الكود على النحو التالي:

int width, height;
unsigned char* image =
 SOIL_load_image(\"cropped-HiperAktif.png\", &width, &height, 0,	SOIL_LOAD_RGB);

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

والمتغير الذي يحمل القيمة 0 هو مسؤول عن عدد القنوات التي تمتلكها الصورة , وسوف نبقيه على ما هو عليه.

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

وبذلك يتم تخزين النتيجة في مصفوفة كبيرة جدا من الأحرف.[1]


توليد النسيج


يعرف عن OpenGL أنها تستخدم نفس الأسلوب في كل عملية استدعاء ، وينطبق ذلك على تفعيل texture ما.[1]

وبالتالي سنقوم الآن بتفعيل معرف ID للنسيج ليكون على النحو التالي:

GLuint texture;
glGenTextures(1, & texture);

الدالة glGenTextures تأخذ المدخل القادم من النسيج متمثلة بعدد الأنسجة المرفقة ، ويعبر الرقم 1 عن أن لدينا فقط نسيج واحد.[1]

بينما القيمة الثانية فإنها تدل على المتغير الذي قمنا بتعريفه.

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

glBindTexture(GL_TEXTURE_2D, texture);

الآن تم ارفاق النسيج ، وكل ما لدينا هو تفعيل texture عن طريق الصورة التي تم إسنادها مسبقًا.[1]

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

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
        GL_UNSIGNED_BYTE, image);

تدل القيمة الثانية على مستوى MIPMAP الذي تريد تفعيل texture بناء عليه.[1]

ويمكن تحديد المستوى يدويا لكننا نكتفي بالقيمة 0.

والقيمة الثالثة تقوم بإخبار OpenGL بنوع الصيغة التي سيتم توليد النسيج من خلالها.

بالتالي نقوم بترك الخيار RGB كما هو.[1]

قيم الأبعاد width و height تعطي حجم البيكسل المحدد مسبقًا ، ولقد قمنا بتخزين الأبعاد في الكود السابق.[1]

بينما آخر قيمتين على نوع وقيم البيانات التي سيتم ارفاقها.

وهي قيمة من نوع unsigned.[1] الآن حان الوقت لتوليد MIPMAP الخاصة بالنسيج وسيتم ذلك عن طريق الدالة التالية:

glGenerateMipmap(GL_TEXTURE_2D);

الخطوة الأخيرة من تفعيل texture تتطلب إلغاء الربط وتحرير مكتبة SOIL من الصورة التي تم تحميلها.[1]

ونقوم بتطبيق ذلك من خلال كود بسيط مكون من وظيفتين.

SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);

 


تطبيق Texture على الإحداثيات


سنقوم باختيار إحدى الإحداثيات التي تم استخدامها من دروس سابقة.[1]

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

بالتالي يحتوي الكود التالي على إحداثيات الرسم والألوان , بالإضافة إلى إحداثيات النسيج.

GLfloat vertices[] = {
// Positions // Colors // Texture Coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // Top Right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // Bottom Left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top Left
};

يتكون كل سطر من الكود السابق على كل من إحداثيات XYZ والمتمثلة بمواقع الرسم.[1]

بالإضافة إلى الألوان RGB خلال رسم الإحداثيات.[1] تدل إحداثيات ST على النسيج 2D الذي يعطي خانتي الرسم على وجه التحديد.[1]

أيضا لدى الإحداثيات السابقة مؤشرات تساعد المكتبة على عملية الرسم من خلال الكود التالي:

GLuint indices[] = {  // Note that we start from 0!
      0, 1, 3, // First Triangle
      1, 2, 3  // Second Triangle
    };

 


الإشارة إلى VBO و EBO


إن إعداد الإحداثيات عن طريق VBO هو أمر ملازم لنا في هذه الدورة ، بينما يساعد EBO في رسم العناصر وفقًا لمقاييس محددة يتم استغلالها من قبل مكتبة OpenGL.

بالتالي لا بد لنا من الإشارة إلى ضرورة إضافة الكود التالي في مشروعك وليكن في دالة الإعداد والتحضير:

glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

 

تلعب دالة VBO دور كبير في تجهيز إحداثيات الرسم , بالتالي لن نغفل عن استدعائها حتى لا تظهر الأخطاء.

تفعيل الصفات


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

لذلك لا بد لنا من الإشارة لجميع تلك العناصر في كود c++.

بالتالي نعمل على تفعيل مكان العرض عن طريق الصفات التالية:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

للوصول إلى إحداثيات الألوان يتعين علينا إضافة الكود التالي:

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

بينما يتم الوصول إلى إحداثيات الــ Texture عن طريق الكود التالي:

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

نلاحظ في الوظيفة السابقة بأننا قمنا بتعيين خريطة الرسم عن طريق 8 مواقع ستتولى لغة GLSL مهام التعامل معها.

لذا يتعين علينا الإستعانة بملفات GLSL لكي يتم استخراج قيم الإحداثيات من بطاقة العرض.

على سبيل المثال ، ربما يتم التعديل على ملف Vertex Shader بإضافة Layout مسؤول عن الألوان وإحداثيات تفعيل texture.

بينما يتم تخريج ملفين إلى المراحل التالية.

#version 330 core


layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;


out vec3 our Color;
out vec2 TexCoord;

void main()
{
     gl_Position = vec4(position, 1.0f);
     ourColor = color;
     TexCoord = texCoord;
}

بعد أن قمنا بتعيين القيم السابقة سيتم استقبال التعديلات التي طرأت على vertex shader في ملف fragment shader.[1]

بالتالي يتعين على المرحلة التالية من GLSL استقبال كائن texture.

يعود الفضل في ذلك بأن لغة GLSL هي بالأصل داعمة إلى لجميع أنواع بيانات النسيج.

على سبيل المثال , يتم استقبال عينة النسيج من خلال يونيفورم sampler 2D.[1]

#version 330 core

in vec3 our Color;
in vec2 TexCoord;

out vec4 color;
uniform sampler2D ourTexture;

void main()
{
    color = texture(ourTexture, TexCoord);
}
من فضلك ، لا تنسى ترجمة كل من الملفات vertex و frag في منطقة الإستدعاء ، وذلك لكي يعمل البرنامج عند تفعيل texture الخاص بهذا الدرس.

وظائف الرسم


سوف يجري في هذا الجزء إعداد دالة الرسم عند تفعيل texture.[1]

بالتالي ستجد أن عملية الرسم لن تتغير على الإطلاق بالنسبة للدروس السابقة.

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
gl Bind Vertex Array(0);

\"تفعيل

 

المراجع

  1. [1]^ كتاب ـــــــ offline learn OpenGL created by Joey de Vries.
  2. [2]^What does changing GL_TEXTURE_WRAP) do?.

مصادر تم الرجوع إليها خلال المعاينة:

  • ^ig7icd32.pdb not loaded.

اترك تعليقاً

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