رسم مثلث : دورة OpenGL لغة c++ الدرس الرابع

رسم مثلث عبر وظائف مكتبة OpenGL يتطلب الإدراك بأن الشاشة التي نعمل عليها هي ثنائية البعد.[1] وبالرغم من أن وظائف OpenGL هي دائمة العمل بالرسم 3D إلا أن ما يحدث هو خداع بصري عن طريق خصائص التحويل والجبر الخطي في البرمجة.[1]
على سبيل المثال عند رسم مثلث 2D فإن خصائص بعض الوظائف تتطلب التحويل من 3D الى 2D.[1] على عكس مكتبات أخرى مثل DirectX.
إذ تتبنى بعض الإختلافات في تعريف المتغيرات وطرق الإستدعاء. يتم إدارة عمليات التحويل من 2D إلى 3D عن طريق خط أنابيب يتم تقسيمه إلى قسمين.[1]
القسم الأول يعمل على تحويل وقراءة المشهد إلى مخرجات العرض (الشاشة) بينما القسم الثاني يتعلق بإدارة الإحداثيات داخل صندوق العرض مع فتح نطاق العمق للرسم ثلاثي الأبعاد وإجراء حسابات التحويل.[1]
الفرق بين رسومات 2D و 3D
يكمن الإختلاف بين هاتين التقنيتين بفرق بسيط للغاية إن أدركته سيسهل عليك انتاج أعقد برامج الرسم. على سبيل المثال , تتشابه الرسومات ثنائية البعد 2D بإحداثيات الرسم البياني X و Y التي تلقيتها في محاضرات الرياضيات بمدرستك.
وعندما نشير إلى رسم ثنائي البعد فإن أبرز مثال عليه هو ساعة تناظرية تتطلب معادلات نصف قطر الدائرة والزوايا الحادة والمنفرجة وغيرها من قيم الدائرة الواحدية.
هناك أمثلة أخرى على الرسومات 2D مثل واجهة المستخدم لأنظمة ويندوز ولينيكس و ماك وجوجل وحتى قائمة الهواتف وشاشة النماذج الأخرى. أماكن وجود العناصر لا يتطلب الكثير من التعقيدات , وعلى فرض أن أيقونة الحاسوب تقع أعلى شاشة العرض في سطح المكتب. فإن ذلك يدل على أنها تعتمد أقرب مكان من الإحداثيات وهو 64x64 بيكسل.
و بالتالي غالبًا ما تبدأ الإحداثيات (خط الصفر) في رسم الحاسب من أعلى بيكسل موجود على الشاشة أقصى اليسار. ببعض الحالات من الممكن أن تبدأ الإحداثيات من الوسط كما سنشاهد في الأمثلة القادمة.
جميع قيم إحداثيات 2D هي موجبة بالأصل على أجهزة الحاسوب وفي حال كانت سالبة فإن ذلك يعود إلى نوع المكتبة وسلوك المطور في المشروع.
بينما يختلف الأمر في معادلات الرسم 3D بوجود عمق للعناصر, ما يعني أن هناك احداثيات x و y في الواقع ثابتة وإنما الإختلاف الذي طرأ عليها هو إضافة العمق والذي يعبر عنه بالرياضيات بالقيمة z.
تعتمد القيمة Z إحداثيات مجال واحدي يتراوح بين -1 و 1 , ويدل تطبيقها على الحاسوب ببروز بعض الصفات والخصائص المتعلقة بالعناصر. لكن إدخال إحداثيات الرسم إلى الحاسوب أدى إلى انقلاب بعض الحسابات بسبب علوم (المصفوفات) Algebra.
رسم مثلث باستخدام Shaders
هناك مجموعة من العناصر التي تساعد في رسم مثلث سواء أكان ملونا أو عادي وذلك من خلال خطوط الرسم pipelines.[1] فهي أنابيب متصلة ببطاقة العرض تحول قيم بيكسل إلى ألوان حقيقية.[1] وبسبب الطبيعة الموازية تحتوي غالبية بطاقات الرسم على الآلاف من نوى المعالجة الصغيرة والتي تعمل على معالجة البيانات بسرعة كبيرة عن طريق خطوط الأنابيب.[1]
يمكن الإشارة إليها بــ shaders.[1] وبالتالي فإن بعض هذه الظلال قابلة للإدارة من قبل المطورين فهي تسمح للمبرمجين بكتابة وظائف الألوان الخاصة بهم من خلال الأنابيب.[1] تم كتابة Shaders باستخدام OpenGL GLSL. وبالتالي فإن الأمر لم يقتصر على رسم مثلث فحسب بل أن إدارة المشروع بالكامل تقع على عاتقها.[1]
أنواع أنابيب pipeline[1]
- إحداثي (Vertex Shader).
- شكلي (Shape Assembly).
- هندسة (Geometry).
- فسيفسائي (Tessellation).
- نقطي (Rasterization).
- جزئي (Fragment Shader).
- مخلوط (Blending).
توفر لك الأنابيب السابقة مجموعة شاملة جدًا من طرق التلوين تدعمها غالبية بطاقات العرض.[1] ربما سنلقي الضوء أكثر على Vertex Shader و Fragment Shader لأنه يتطابق مع هذا الدرس بشكل كبير.[1]
أنابيب Vertex Shader
يتم الإشارة بــ Vertex shader إلى الظلال الرأس وهي بذلك الجزء الخاص بتعيين الإحداثيات التي سنتطرق إليها في مدى رسومات 3D.[1] على سبيل المثال , تركز هذه الخصائص على تظليل النقاط الرئيسية لكل إحداثية عند رسم مثلث.[1] في حال وجدت صعوبة في الفهم سوف تتبين الصورة جيدًا من خلال الأمثلة.[1]
التظليل الجزئي (Fragment Shader)
تستدعي الحاجة لإستخدام Fragment Shader عندما نود احتساب اللون النهائي لكل بيكسل داخل حدود المثلث.[1] وهو مستخدم عن كثب أيضًا في المراحل المتقدمة من الأضواء وانعكاساتها والتي سيتم شرحها لاحقا في هذه الدورة إن شاء الله.[1]
البدء في رسم مثلث
لكي نتمكن من رسم مثلث وجب علينا تخزين احداثيات Vertex في مصفوفة خاصة من لغة c++ أو بداخل متغير من مكتبة GLFW. بالتالي سوف نتقيد في المصادر التي لدينا عبر إنشاء إحداثيات المثلث كما في الشيفرة التالية:
GLfloat vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f };
لا بد من الإشارة أن إحداثيات الرسم تبدأ من القيمة 0 وأول نقطة تقع على الحدود السالبة من مجال الرسم. وذلك بناء على طريقة vertex التي قمنا باستخدامها. تخيل أن نقطة الصفر في منتصف المثلث ويتم تشكيل الأضلاع بناء عليها.
لا نعطي أهمية للقيمة الثالثة فهي تساوي 0.0f بكافة الأضلاع , فقط نريد الإبقاء عليها كما هي. بالتالي لا ننسى أن مكتبة OpenGL تعمل بنظام 3D لذلك ما نقوم به الآن بنظام الاستدعاء هو تعطيل العمق Z.[1]
إعداد VBO
يتم ترحيل الإحداثيات التي قمنا بحفظها إلى نظام VBO وهو عبارة عن مخزن قادر على حفظ أعداد كبيرة من الإحداثيات. يتميّز المخزن VBO بسرعة عالية وإدارة قوية لعتاد البطاقة فهو يتسع لأعداد كبيرة من المضلعات. يمكن فصل قيم أكثر من مخزن VBO عن طريق عنوان ID خاص يعمل على تجريد إحداثيات الأجزاء عن بعضها البعض.[1] بالتالي نقم بإضافة الشيفرة التالية إلى الكود الخاص بنا:
GLuint VBO; glGenBuffers(1, &VBO);
تمتلك مكتبة OpenGL أكثر من كائن ونوع لتخزين البيانات.[1] وتتطلب منا الإحداثيات مخزن من نوع مصفوفة لذلك سنقوم بإضافة الشيفرة التالية إلى المشروع:
glBindBuffer(GL_ARRAY_BUFFER, VBO);
يتم الإتصال بأي مخزن عن طريق دالة glBufferData حيث أنها تقوم بتحضير البيانات الخاصة أثناء إحداثيات رسم مثلث التي قمنا بتعريفها.[1] المصفوفة دائما تأخذ الصفة GL_ARRAY_BUFFER ليتم الإشارة إلى نوع البيانات مع تحديد خاصية الرسم GL_STATIC_DRAW.[1]
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
يعتمد رسم مثلث مبدئي على خاصية GL_STATIC_DRAW ما يعني أن بيانات المخزن الخاص به ستبقى ثابتة طوال فترة تشغيل البرنامج. تشير أنواع أخرى من الرسم إلى بيانات متغيرة مثل GL_DYNAMIC_DRAW أو GL_STREAM_DRAW وهي حدوث تغيير على مضلعات النموذج في وقت محدد.[1] غالبًا ما يتم دمج هذه الخصائص عند تمكين وتعطيل بعض الأحداث المتغيرة في المشاريع الكبيرة.[1]
ملف أنابيب Vertex Shader
يمكن أن يساعدنا Vertex Shader في إجراء بعض الظلال على إحداثيات المثلث وفقًا للألوان التي نحددها في Fragment Shader. على سبيل المثال سيتم الإشارة إلى احداثيات الظلال الرأسية من خلال ملف يتم اعادة الاتصال به عبر أدوات اللغة.[1] وفي حال أردنا قراءة المصادر من متغير فإن ذلك ممكن وبحسب المصادر المتوفرة لدينا في هذه الدورة.
بالتالي سنعمل على وضع بيانات أنابيب الظل والألوان عبر مصفوفة Character من متغيرات لغة سي أو من مكتبة OpenGL.[1] يتم استخدام Vertex Shader عبر لغة GLSL (OpenGL Shading Language).[1] ومن ثم ترجمته في المشروع الخاص بنا. في هذه الشيفرة أقل أساسيات مطلوبة:
#version 330 core layout (location = 0) in vec3 position; void main() { gl_Position = vec4(position.x, position.y, position.z, 1.0); }
كما تجد فإن رسم مثلث يتطلب 3 إحداثيات للنقاط xyz وقد تجد بأن الشيفرة متشابهة جدا مع لغة c++. كل ظل يبدأ بإعلان إصدار المكتبة في الأعلى , ثم يتم الإعلان عن vector نظرا لأن GLSL تتطلب الوصول إلى القيم من خلال المتجهات.[1]
ثم نقوم بتحديد مدخلات كل قيمة عن طريق متغير باسم position مع إعلان مكان الرسم بقيمة 0.[1] يمكنك تعريف Vertex Shader عن طريق متغير. قم بنسخ الشيفرة التالية فوق دالة main.cpp.
const GLchar* vertexShaderSource = "#version 330 core " "layout (location = 0) in vec3 position; " "void main() " "{ " "gl_Position = vec4(position.x, position.y, position.z, 1.0); " "}";
المتجهات Vectors
يتم الإشارة إلى برمجة الرسومات بالمفاهيم الرياضية التقليدية ومنها المتجهات, حيث أنها تمثل مواقع واتجاهات الفضاء في رياضيات الرسم.[1] في لغة GLSL يتم استلام القيم عبر متجهات من قيم XYZ لتمثل أماكن وإحداثيات الرسم من الفضاء بينما القيمة W تدل على شاشة المخرجاتإسقاط الرسم.[1] سيتم مناقشة المتجهات بعمق في الدرس القادم.
إرفاق مصادر Vertex Shader
لقد قمنا بكتابة vertex shader في متغير خاص ولكننا لم نقم بتجهيز بيئة OpenGL لقراءة المصادر. ولذلك نعرف متغير بعنوان vertex وهو من نوع GLuint تماما مثل الشيفرة التالي ثم نعمل على إضافة الظلال الخاصة به.
GLuint vertex; vertex= glCreateShader(GL_VERTEX_SHADER);
الآن نقوم بإرفاق الملفات الخاصة بــ Vertex Shader عن طريق الكود التالي:
glShaderSource(vertex, 1, &vertexShaderSource, NULL); glCompileShader(vertex);
يتكون الكود السابق من متغير vertex Shader الذي سيحمل القيم القادمة من GLSL. بينما تشير القيمة 1 إلى إعلان البدء بـالظلال الرأسية , المتغير الثالث هو نصوص GLSL التي قمنا بتعريفها واستدعائها. عند رسم مثلث بالطريقة السابقة , من المستحسن فحص عملية الربط الكود Vertex. قد لا تعتبر أساسية ولكن فقط للتحقق من أن الأمور تسير على ما يرام.
GLint success; GLchar infoLog[512]; glGetShaderiv(vertex, GL_COMPILE_STATUS, & success); if(!success) { glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED " << infoLog << std::endl; }
مصادر Fragment Shader
تعتبر الظلال الجزئية هي الظلال الثانية والنهائية التي نريد استخدامها في درس رسم مثلث. فهي تتعلق بإجراء الحسابات على مخرجات البيكسل من ناحية الألوان. على سبيل المثال , فإن رسومات الحاسب تتكون من ثلاثة ألوان رئيسية RGBA وهي اختصار للأحمر والأخضر والأزرق ومزيج ألفا.
يصبح لدينا الترتيب Red , Green , Blue , Alpha(RGBA). ومع دمج وتركيب الألوان سنحصل على أكثر من 16 مليون لون.[1]
بالتالي وعند تحضير شيفرة Fragment Shader فإنها تصبح على النحو التالي:
#version 330 core out vec4 color; void main() { color = vec4(1.0f, 0.5f, 0.2f, 1.0f); }
يتم إخراج الألوان عبر متجه يحتوي على 4 قيم RGBA. تمثل درجة 1.0 قيمة عشرية مبهمة. تتم الموالفة من خلال تركيز وخفت درجات الألوان الأساسية من العرض.[1]
إرفاق مصادر Fragment Shader
يتم تفعيل الظلال الجزئية بطريقة مماثلة لـ Vertex Shader. ومن خلال الشيفرة التالية يتبين لنا عملية تفعيل وارفاق Fragment Shader:
GLuint fragment; fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fragmentShaderSource, NULL); glCompileShader(fragment);
إضافة وربط ظلال البرنامج
يشير متغير Shader Program إلى آخر خطوة من ربط ودمج الظلال التي قمنا بإعدادها.[1] لا بد من التذكير أن عملية الربط لا تنطبق حصريًا على رسم مثلث , بل يمكن أن تتجاوز المضلعات الأكثر تعقيدًا وخاصة في الرسومات 3D.
عند دمج الظلال سويًا فإننا نحصل على مخرجات كل مدخل من مراحل الظل السابق. وينطبق ذلك أيضا على مراحل GLSL المتعددة مثل النقطية والفسيفساء وغيرها.[1]
نقوم الآن بربط الظلال من خلال الكود التالي:
GLuint program; program= glCreateProgram(); glAttachShader(program, vertex); glAttachShader(program, fragment); gl Link Program(program);
سنفعل شرط التحقق فيما لو نجح الربط أم لا , تمامًا مثل ملفات Shaders. بالتالي يصبح كود التحقق على النحو التالي:
//Check status of program linking glGetProgramiv(shaderProgram, GL_LINK_STATUS, & success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); std::cout << "Failed To Link Shader Program!"; }
ثم لا ننسى تفعيل البرنامج مع حذف كائنات الظل بعد عملية التفعيل لأننا لم نعد بحاجة لها. بالتالي تصبح شيفرة التفعيل على النحو التالي:
//GL use program gl UseProgram(program); //Delete buffers glDeleteShader(vertex); glDeleteShader(fragment);
ربط وتفعيل الخصائص والسمات
بعد تعيين جميع خصائص رسم مثلث , يتعين علينا تفعيل السمات (glVertexAttribPointer ). والتي تسمح لنا بتحديد الإدخالات التي نريدها بالفعل وذلك لأننا نقوم بتعيين السمات بشكل يدوي مع تمرير الإحداثيات. يتم الوصول إلى السمات عن طريق glVertexAttribPointer تمامًا مثل الشيفرة التالية:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), ( GLvoid*)0); glEnableVertexAttribArray(0);
بالتالي , تدل القيمة الأولى على البدء من إحداثيات صفر كما أشرنا إليه في Vertex Shader. فهو بذلك يحجز مساحة البدء بالرسم التي سيبدأ بتمرير المتغيرات لها. تدل القيمة الثانية على طبيعة البيانات التي تستقبلها لغة GLSL وهي من نوع vec3. بينما تعبر القيمة الثالثة عن أن البيانات عشرية من نوع Float.
سيتم استقبال بيانات رسم مثلث من خلال مخزن VBO, حيث أنه يتولى مهام إحداثيات الرسم في المكتبة. بالتالي يتم تشغيل المخزن قبل الاتصال بدالة glVertexAttribPointer. الآن تصبح الشيفرة الخاصة بالسمات على النحو التالي بالترتيب:
GLuint VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), ( GLvoid*)0); glEnableVertexAttribArray(0); //GL use program gl Use Program(shaderProgram);
يتطلب رسم مثلث وأي كائن آخر تفعيل كافة الخطوات المتعلقة بالصفات. على سبيل المثال , عند رسم 5 مثلثات يتطلب منا VBO مستقل لكل واحدة منه ستحدد لنا المكان والإحداثيات وغيرها من القيم.
تفعيل Vertex Array Object(VAO)
يطلق عليها VAO وهي تتشابه قليلا مع VBO إلا أنها متطلب لعملية الرسم عبر OpenGL. على سبيل المثال هي لا تتولى مهام تعيين الإحداثيات. ربما تشير إلى أماكن توزيعها مع تمكين وتعطيل الخصائص دون الرجوع إلى المخزنات السابقة.
تحتوي VAO على عناصر أهمها تفعيل الإحداثيات أو إلغاؤها. التعرف على السمات القادمة والارتباط بها. نقوم الآن بتعريف كائن VAO كما في الشيفرة التالية:
GLuint VAO; glGenVertexArrays(1, &VAO);
يتم تمكين VAO قبل الرسم , وبعد الرسم يتم تعطيلها تمامًا مثل الشيفرة التالية:
//Bind the VAO gl BindVertexArray(VAO); //Your Draw Method inside //Unbind the VAO gl BindVertexArray(0);
رسم مثلث (الوظائف النهائية)
توفر لنا دالة glDrawArrays البدء برسم مثلث وفقا لكافة المعطيات التي تم تفعيلها من مكتبة OpenGL. بالتالي لا ننسى أن ربط VAO سيعمل قبل دالة الرسم ومن ثم سيتوقف عند إتمام الوظيفة لتصبح الشيفرة بهذا الشكل:
//Draw Triangle gl Use Program(shaderProgram); gl BindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); gl BindVertexArray(0);
شيفرة رسم مثلث
#include<iostream> #include <glew.h> #include <glfw3.h> #define GLEW_STATIC // Function prototypes void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); // Window dimensions const GLuint width = 800, height = 600; // Shaders const GLchar* vertexShaderSource = "#version 330 core " "layout (location = 0) in vec3 position; " "void main() " "{ " "gl_Position = vec4(position.x, position.y, position.z, 1.0); " "}"; const GLchar* fragmentShaderSource = "#version 330 core " "out vec4 color; " "void main() " "{ " "color = vec4(1.0f, 0.5f, 0.7f, 1.0f); " "} "; void initialize(); GLFWwindow* createWindow(const GLuint width , const GLuint height); void compileShaders(const char* shaderName , const GLchar* sources , GLuint& shader); void checkCompileStatus(GLuint shaderObject, const char* shaderName, bool compile); void cleanMemory(GLuint VBO, GLuint VAO); // The MAIN function, from here we start the application and run the game loop int main() { initialize(); GLFWwindow* window = createWindow(1024, 768); // Set the required callback functions glfwSetKeyCallback(window, key_callback); // Build and compile our shader program // Vertex shader GLuint vertex; compileShaders("vertex" , vertexShaderSource , vertex); // Fragment shader GLuint fragmentShader; compileShaders("fragmentShader", fragmentShaderSource, fragmentShader); // Link shaders GLuint program = glCreateProgram(); glAttachShader(program , vertex); glAttachShader(program , fragment); gl Link Program(program); // Check for linking errors checkCompileStatus(shaderProgram, "Shader Program", false); glDeleteShader(vertex); glDeleteShader(fragment); // Set up vertex data (and buffer(s)) and attribute pointers GLfloat vertices[] = { -0.5f, -0.5f, 0.0f, // Left 0.5f, -0.5f, 0.0f, // Right 0.0f, 0.5f, 0.0f // Top }; GLuint VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s). glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind glBindVertex Array(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs) // Game loop while (!glfwWindowShouldClose(window)) { // Check if any events have been activated (key pressed, mouse moved etc.) and call corresponding response functions glfwPollEvents(); // Render // Clear the colorbuffer glClearColor(0.2f, 0.3f, 0.3f, 0.5f); glClear(GL_COLOR_BUFFER_BIT); // Draw our first triangle gl Use Program(shaderProgram); glBindVertex Array(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertex Array(0); // Swap the screen buffers glfwSwapBuffers(window); } clean Memory(VBO,VAO); return 0; } // Is called whenever a key is pressed/released via GLFW void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); } void initialize() { // Init GLFW glfwInit(); // Set all the required options for GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); } GLFWwindow* createWindow(const GLuint width , const GLuint height) { // Create a GLFWwindow object that we can use for GLFW's functions GLFWwindow* window = glfwCreateWindow(width , height, "Our First Triangle", nullptr, nullptr); glfwMakeContextCurrent(window); // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions glewExperimental = GL_TRUE; // Initialize GLEW to setup the OpenGL Function pointers glewInit(); // Define the viewport dimensions int width, height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); return window; } void compileShaders(const char* shaderName, const GLchar* sources , GLuint& shader) { if (shaderName == "vertexShader") { shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(shader, 1, &vertexShaderSource, NULL); glCompileShader(shader); checkCompileStatus(shader, "vertex shader", true); } else { shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(shader, 1, &fragmentShaderSource, NULL); glCompileShader(shader); checkCompileStatus(shader, "fragment shader", true); } } void checkCompileStatus(GLuint shaderObject, const char* shaderName, bool compile) { // Check for compile time errors GLint success; GLchar infoLog[512]; if (compile) { glGetShaderiv(shaderObject, GL_COMPILE_STATUS, & success); if (!success) { glGetShaderInfoLog(shaderObject, 512, NULL, infoLog); std::cout << "ERROR::SHADER::" << shaderName << "::COMPILATION_FAILED " << infoLog << std::endl; } else { std::cout << shaderName <<" Compiled Successfully "; } } else { glGetShaderiv(shaderObject, GL_LINK_STATUS, & success); if (!success) { glGetShaderInfoLog(shaderObject, 512, NULL, infoLog); std::cout << "ERROR::SHADER::" << shaderName << "::COMPILATION_FAILED " << infoLog << std::endl; } else { std::cout << shaderName << " Compiled Successfully "; } } } void cleanMemory(GLuint VBO, GLuint VAO) { // Properly deallocate all resources once they've outlived their purpose glDeleteVertexArrays(1, & VAO); gl Delete Buffers(1, & VBO); // Terminate GLFW, clearing any resources allocated by GLFW. glfwTerminate(); }
المراجع
تمت الكتابة بواسطة : محمد
آخر تحديث : لم تخضع لتحديثات بعد