ملف GLSL : دورة OpenGL لغة c++ الدرس الثامن


ملف GLSL سيزداد حجمه عند تخصيص بعض المتجهات بحسب ما قمنا بالحديث عنه في درس uniforms. وذلك من شأنه أن يعيق فهم وقراءة الشيفرة التي سنقوم بوضعها في كل جزء من دورتنا.[1] وبالتالي يتعين علينا بناء ملف GLSL لكل من Vertex Shader و Fragment Shader لإضفاء مزيدًا من التنظيم على المصادر.[1]على سبيل المثال , لقد قمنا بتخصيص بضعًا من الدوال التي جردنا عملياتها لتسهيل التنظيم وفي فصل ملف GLSL العديد من المزايا التي تمكننا من تحليل شيفرة البرنامج أيا كانت.[1] وبالتالي يسهل حصر الأخطاء مع التخلي عن رموز \n التي يتم فرض استخدامها من قبل متغيرات char.[1]


 


إضافة ملف GLSL



سنعمل على إضافة header خاص يعمل على قراءة كلا الملفين من القرص الصلب. نعطي الآن العنوان GLSL.h تمام مثل الصورة التالية:

ملف GLSL
صورة يظهر فيها محاولة إضافة ملف GLSL في بيئة عمل Visual Studio.

بعد القيام بتلك المهمة سنجد أن هنالك ملف جديد سيتم تضمينه في شيفرة c++. ولكن قبل ذلك لا بد لنا من ملئ بعض الخصائص الفريدة في لغة C++ والتي تمكننا من قراءة الملفات من الخارج.

بداية نقوم بتعريف Class جديد بعنوان OurShaders تمامًا مثل الشيفرة التالية:

#pragma once

#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
#include <glew.h>; // we call glew to activate OpenGL


class OurShaders
{
public:
    // This is our program ID
    GLuint Program;
    // Constructors
    OurShaders(const GLchar* vertexSourcePath, const GLchar* fragmentSourcePath);


    // To use our program
    void Use();
};
#endif

 

يطلق على SHADER H مصطلح preprocessor directives ومن المستحسن تضمينها في هذا الـ Header.[1] وذلك لإعلام المترجم بضرورة قراءة المكونات الداخلية للملف حتى إن لم يتم تضمينه في شيفرة main او غيرها.[1]

 

سنجري تطويرا على ملف GLSL ليحقق أهدافنا من هذا الدرس. على سبيل المثال , الآن نعمل على إرفاق كافة العناوين القادمة من برنامج الظلال. بالتالي يتطلب الـ constructor أماكن وجود ملف GLSL وهما vertex shader و fragment shader.[1]

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

 


بناء الــ Constructor



يتطلب منا بناء الــ Constructor إدراك بعض الأساسيات حولها قبل الإستمرار في هذا الدرس.[2] ويمكنك أخذ لمحة سريعة عن دور هذه الدالة الأساسية في بناء وتكوين القيم الإفتراضية للكائنات.[2]

// 1. Receive vertex & fragment source from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensures ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

بعد آخر إجراءات على ملف GLSL أضفنا القيم السابقة في الــ constructor التابع للكلاس. وهي متغيرات من نوع string في مكتبة c++. حيث يدل الــ Class ifstream على كل مدخل يتم قراءته من ملف مرفق أو خارجي.[3]

تمكننا أدوات لغة سي القياسية من حصر شيفرة فتح الملفات try & catch ليتم اقتناص الأخطاء وإظهارها بطريقة أو بأخرى. وفي حال قمت بالتخلي عن هذا النمط من فحص وقراءة الملفات , من المحتمل ظهور بعض الأخطاء في القراءة.


إعداد قراءة ملف GLSL



ستكشف لك الشيفرة التالية بعض الطرق التقليدية في جلب الملفات:

try
        {
            // Open files from path
            vShaderFile.open(vertexSourcePath);
            fShaderFile.open(fragmentSourcePath);
            std::stringstream vShaderStream, fShaderStream;
            // Read file’s by buffers & streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close handlers
            vShaderFile.close();
            fShaderFile.close();
            // Convert stream to GLchar array
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::
                endl;
        }
        const GLchar* vShaderCode = vertexCode.c_str();
        const GLchar* fShaderCode = fragmentCode.c_str();

 

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


ترجمة ووصل الظلال



بعد تشكيل شيفرة قراءة ملف GLSL من الــ constructor سنتمكن الآن من تحضير شيفرة الترجمة ووصل برنامج الظلال. لا تختلف عن أي شروحات سابقة إنما فقط تعمل على تنفيذ قراءة كلا الملفين من خارج البرنامج.

الآن نعمل على إضافة شيفرة vertex Shader لتصبح على النحو التالي:

// 2. Compile shaders
        GLuint vertex, fragment;
        GLint success;
        GLchar infoLog[512];
        // Vertex Shader as the same in original code
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        // Compile and catch errors if found
        glGetShaderiv(vertex, GL_COMPILE_STATUS, & success);
        if (!success)
        {
            glGetShaderInfoLog(vertex, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog
                << std::endl;
        };

بينما تصبح شيفرة fragment Shader على النحو التالي:

// Same this for Fragment Shader
        // Shader Program
        this->Program = glCreateProgram();
        glAttach Shader(this->Program, vertex);
        glAttach Shader(this->Program, fragment);
        glLinkProgram(this->Program);
        // Compile and catch errors if found
        glGetProgramiv(this->Program, GL_LINK_STATUS, & success);

        if (!success)
        {
            glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog <<std::endl;
        }

نتطرق الآن إلى آخر وحدة من مهام الــ Constructor وهي التخلص من ملفات shaders.

//Delete shaders
        glDeleteShader(vertex);
        glDeleteShader(fragment);

 


توصيل البرنامج


عند الإنتهاء من بناء الــ constructor يتعين علينا استكمال الدالة Use ليتم وصل ملف GLSL. وبالتالي تصبح شيفرة Use على النحو التالي:

void OurShaders::Use()
{
    glUseProgram(this->Program);
}

 


نداء ملف GLSL



الآن وبعد أن انتهينا من إعداد ملف GLSL بكافة مكوناته أصبح لدينا قدرة على الاتصال بــ OurShaders Class. لكن قبل ذلك يتعين علينا نقل شيفرة كل من ملف Vertex Shader و Fragment Shader إلى مكان آمن في القرص الصلب.

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

سيتم إنشاء ملفين الأول يحمل الإسم vert.vs والثاني بعنوان frag.fs. ومن ثم نقوم بنقل كل من الشيفرة الخاصة لدى ملف GLSL.

 

من فضلك , كن حذرا بشأن الإمتدادات المتعلقة بنظام ويندوز. حيث يجب الكشف عن نهاية الملفات مثل txt او exe او pdf. يمكنك مشاهدة مقطع يوتيوب عابر حول طريقة فتح الإمتداد.

 

يتطلب نداء ملف GLSL تضمين الصفحة أولاً ومن ثم استخدام الــ Class الذي قمنا بانشائه. ولذلك بعد عملية التضمين سنعمل على إنشاء مؤشر من نوع OurShaders , والسبب في ذلك هو فصل الشيفرة ببعض الوظائف (functions) ما يعيق عملية الإتصال دون متغيرات من نوع Pointer.

بالتالي نعين متغير أعلى صفحة main.cpp ونقوم بتسميته بــ ourShaders تمامًا مثل الشيفرة التالية:

OurShaders* ourShaders;

يتطلب إرفاق الملفات تعيين أماكنها الصحيحة في القرص الصلب. وقد قمنا بتخزينها بملفات المشروع ليتم الإتصال بها على النحو التالي:

ourShaders = new OurShaders("C:/Users/Mmutawe/source/repos/Test OpenGL/Test OpenGL/shaders/vert.vs", "C:/Users/Mmutawe/source/repos/Test OpenGL/Test OpenGL/shaders/frag.fs");

بعد نجاح عملية الاتصال يجب ربط دالة Use في منطقة العرض والإستدعاء الخاصة بمكتبة OpenGL. وقد قمنا بوضع دالة منفصلة تحقق عملية الإتصال. تصبح نتيجة العرض كما في الصورة التالية:

ربط ملف GLSL
صورة يظهر فيها نتيجة العرض بعد ربط ملف GLSL.

تبدو النتائج قليلة بعض الشيء , بل ربما مماثلة للدرس السابق. لكننا بالفعل حققنا عملية ربط GLSL من ملفات خارجية , وبذلك سيسهل العمل عند بناء نماذج ثلاثية الأبعاد واستدعاء الكاميرات.

 

من فضلك , قم بتغيير الــ Path  الخاص لكل من vertex Shader و fragment Shader لكي يعمل الكود على حاسوبك.

 

 

 

 

 

 

المراجع
  1. [1]^ كتاب ـــــــ offline learn OpenGL created by Joey de Vries.
  2. [2]^ الجزء الثامن عشر ______ الصفوف في لغة C++ قسم ـــــــ البرمجة.
  3. [3]^Introduction to C / C++ Programming File I/O.
  4. [4]^C++ Files.

اترك تعليقاً

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