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

ملف GLSL يزداد حجمه عند تخصيص بعض المتجهات بحسب ما يستدعي من وظائف uniforms.

حيث أن ذلك من شأنه أن يعيق فهم وقراءة الشيفرة كلما ازداد استخدام ظلال GLSL.

وبالتالي يتعين علينا بناء ملف GLSL لكل من Vertex Shader و Fragment Shader لتنظيم قراءة الشيفرة.

على سبيل المثال ، فقد قمنا بتخصيص بضعًا من الدوال التي جردنا عملياتها لتسهيل عملية التنظيم.

حيث أن في فصل ملف GLSL العديد من المزايا ومنها حصر الأخطاء مع التخلي عن الرموز التي يتم فرض استخدامها من قبل متغيرات char.[1]

 

 

إضافة ملف GLSL

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

\"ملف

بعد القيام بتلك المهمة نجد أن هنالك ملف جديد سوف يتم تضمينه في شيفرة 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
\" << 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
\" << 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 من ملفات خارجية ، وبذلك سيسهل العمل عند بناء نماذج ثلاثية الأبعاد واستدعاء الكاميرات.

 

المراجع

  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.

اترك تعليقاً

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