تفعيل texture يساعد في تعبئة النماذج الخاصة بالرسم 2D و 3D وبما أننا تحدثنا في دروس سابقة عن الألوان وكيفية استخدامها.
سنركز في هذا المقال على تفعيل texture ليعمل على جميع تقنيات الرسم. إن أفضل مثال على استدعاء Texture هو نموذج منزل 3D.
بالتالي سيظهر أمام الكاميرا على أنه بالفعل منزل حقيقي. والسبب في ذلك ارفاق نسيج المكونات الخاصة به متبوعة ببعض التفاصيل الدقيقة.[1]
بينما تفعيل Texture ما هو سوى تكرار لحجارة ذلك المنزل أو نوافذه أو حتى مكوناته الأساسية من الأمام والخلف وكافة نواحي العرض.
سنعمل على تفعيل Texture على إحداثيات تم رسمها من الدروس السابقة. لكن لا بد لنا من شرح بعض المفاهيم عن آلية عمل هذه الأنسجة وكيفية استدعاؤها من بطاقات العرض.
ولكي نتمكن من تكوين نسيج على أسطح مثلثة أو مربعة يجب علينا أن نخبر كل إحداثية من النموذج بماهية الصورة التي تتطابق معه.[1]
على سبيل المثال , يمتلك كل texture إحداثيات مرتبطة ببعضها البعض وهي القادرة على تحديد أجزاء سيتم ملؤها بهذا النسيج.[1]
وبالتالي يتراوح مدى عرض النسيج بقيمة من 0 إلى 1 يتم تطبيقها على إحداثيات x و y من نواحي الرسم.[1]
نتذكر دائما بأننا نقوم بعملية تفعيل texture ثنائي الأبعاد وتسمى عملية التلوين بــ sampling مع نقطة بداية لكل من x و y بإحداثيات 0.0.[1] وتمثل إحداثيات البداية 0.0 اسفل يسار النسيج.
بينما تنتهي قمته بالقيمة 1.1 وهي المتمثلة بإحداثيات النهاية ما يعني الوصول صعودا إلى أعلى يمين النسيج.[1]
يتم تحديد ثلاث إحداثيات تتطابق مع تفعيل 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 };
يشكل الاقتطاع من عينة النسيج أشكال مختلفة من عملية تمثيل الرسم. وسيتم توضيح كل منها على عجالة.
تأخذ العينات مدى من 0,0 و 1,1 بينما قد يحدث وأن تقع بعض الأنسجة خارج المدى.[1] وبالتالي قد تأخذ مكتبة OpenGL إجراء في ذلك الأمر مثل تكرار صور النسيج بأشكال محددة من البيكسل.
والآن إليك بعض الإجراءات التي يتم تطبيقها في الشيفرة مثل:
لا بد من الإشارة بأن استخدام كل خاصية سيؤدي إلى مخرجات مختلفة السياق. بالتالي نبدأ على بركة الله في تفعيل 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 أهمية كبيرة أثناء تفعيل texture . لنتخيل أن لدينا غرفة كبيرة مع آلاف الكائنات وكل كائن يمتلك نسيج خاص به.[1]
بالتالي سيبقى المشهد محافظًا على أداء التباين الخاص كلما ابتعدت أو اقتربت منه. وقد يجد OpenGL صعوبة في إرجاع القيم الصحيحة للألوان في التباين المرتفع.
بالإضافة إلى هدر كبير من موارد الذاكرة بسبب احتفاظ كل نموذج بخصائصه الكاملة.[1] ظهرت تقنية mipmaps لتحل بذلك دور في تنظيم الذاكرة مع ترتيب أولوية العرض وفقا للعناصر القريبة للمشهد.[1]
تتخذ mipmap فكرة بسيطة للغاية فعند الإبتعاد عن النسيج تقوم مكتبة OpenGL باستخدام mipmap مختلفة تتناسب مع المسافة في المشهد.[1]
مع عدم ملاحظة اختفاء التفاصيل الدقيقة من النسيج. على سبيل المثال , سنحصل على صورة مصغرة عن الــ texture الفعلي. وذلك أن هنالك مجموعة متعددة من النسيج يتم احتسابها من المكتبة وتجهيزها لشتى مقاييس العرض.[1]
عند التمرير بين أنواع ومستويات mipmap المختلفة ربما تجد OpenGL بعض الآثار المترتبة عند الإستدعاء ومنها الحواف الحادة والبارزة.[1]
ستعمل أيضًا خصائص الفلترة وإجراءات عرض العينة من النسيج وسيتم التبديل فيما بينها بناء على ذلك. لا ننسى أنه عند عمليات التبديل ستوفر المكتبة للمبرمجين مجموعة من المزايا ومنها:
عند استخدام عينة mipmap يتطلب منا إجراء تعديل على الكود السابق التي قمنا بنسخه. سيشمل التعديل آخر قيمة من الدالة الأولى, وبالتالي تصبح على النحو التالي:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
المهمة الأولى التي نحتاجها عند تفعيل texture هي جلبه إلى المشروع الخاص بنا.[1] وضع باعتبارك أن نسيج الصور يحتوي على صيغ متعددة قد تصل إلى عشرات الإمتداد.
وبالتالي يجب أن نعتمد صيغة موحدة عند استدعاء النسيج , على الأقل في هذه الدورة وذلك لكي يسهل علينا التعامل مع متغيرات الكود.[1] على سبيل المثال , يمكن اعتبار الصيغة PNG نموذج جيد في استدعاء الصور.[1]
ولذلك سوف نستخدمها كثيرا. في حال لم يكن لديك ملفات بهذه الصيغة يمكنك الحصول عليها من شبكة الانترنت.[1] يعد تفعيل texture بصيغ متعددة مشكلة كبيرة للمبرمجين.
ويعود ذلك إلى وجود عناصر مصفوفات مملة للغاية وتحتاج إلى الجهد والوقت خلال التحويل.[1] لكن مع توفر العديد من الوسائل سيتمكن كل منهم من استخدام أدوات الإنترنت أو اللجوء إلى مكتبة SOIL والتي تدعم صيغ متعددة.[1]
تمتثل مكتبة 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);
سنقوم باختيار إحدى الإحداثيات التي تم استخدامها من دروس سابقة.[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 في رسم العناصر وفقًا لمقاييس محددة يتم استغلالها من قبل مكتبة 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);
تدل الصفات على العناصر التي تم تحديدها في ملف 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); }
سوف يجري في هذا الجزء إعداد دالة الرسم عند تفعيل texture.[1] بالتالي ستجد أن عملية الرسم لن تتغير على الإطلاق بالنسبة للدروس السابقة.
glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); gl Bind Vertex Array(0);