الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

لغة التظليل بدعم أوبن جي أل GLSL

بدأه 3ds max2010 في 6 سبتمبر 2010 · 5 رد · 2,199 مشاهدة · في OpenGL
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

بسم الله الرحمن الرحيم

GLSL أو ما يعرف بلغة التضليل بدعم مكتبة الرسوميات جي أل , فهي عبارة عن لغة عالية الكفاءة في مجالات التضليل و أيظا هي جزء لا يتجزء من مكتبة الرسوميات OpenGL , و لديها إصدارات و تحديثات للثغرات كأي لغة برمجة أخرى و لديها منهاج أو بالأحرى أسلوب مماثل للغة السي C , كما يمكنك القول أنها الأسلوب الأمثل لإنشاء مشهد عالي التصيير.

هذا مشهد تم تصييره بالصيغة العادية أي بدون تدخل لل glsl :

64880.jpg

أما هذا المشهد تم تضييره بإستعمال glsl :

64881.jpg

الفرق واضح جدا .

إعداد الأوبن جي أل لل GLSL :

عادة يقوم المبرمج بإنشاء مظللين هما مظلل الرؤوس VERTEX و مظلل الأجزاء FRAGMENT قصد إستعمالهما في مشروعه الرسومي , المهمة سهلة للغاية و تتطلب شيء من التركيز , تماما كما في لغة السي فكل ما يتم إعداده في مترجم السي

يتم إعداد glsl بصورة مشابة تماما لشيفرات مكتوبة بلغة c , و يتم ترجمة الملف بإنفصال و بدقة كاملة , فميكانيزمة ال glsl مشابهة تماما للمترجم gnu .

الملاحظة 1 ( سيتم التعامل مع لغة التضليل بإستعمال مكتبة opengl من ناحية و الإمتدادات من ناحية أخرى).

الملاحظة 2 ( توفر مكتبة GLEW تسهيلة للوصول إلى الإمتدادات من دون تعريفها أو إستخدام آخر الإصدارات المتوفرة و المدعومة على جهازك ,فقط يتوجب عليك تثبيت أو تنصيب السواقة (driver) الخاص بجهازك, هذا إن كنت لم تتعامل مع الإمتدادات من قبل).

الملاحظة 3 ( الإمتدادات الخاصة بلغة التضليل هي على التوالي :

GL_ARB_Vertex_Shader GL_ARB_Fragment_Shader

).

الملاحظة 4 ( إن إمتدادات ARB تعمل على جميع بطاقات الرسوميات ).

هذا مثال لإضهار الدعم للإمتدادات ARB :

#include <GL/glew.h>
#include <GL/glut.h>
void main(int argc, char **argv) {
glutInit(&argc, argv);
...
glewInit();
if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
printf("Ready for GLSL\n");
else {
printf("Not totally ready :( \n");
exit(1);
}
setShaders();
glutMainLoop();
}

لمعرفة إن كان جهازك يتوفر على آخر إصدار من مكتبة OpenGL 4.0

#include <GL/glew.h>
#include <GL/glut.h>
void main(int argc, char **argv) {
glutInit(&argc, argv);
...
glewInit();
if (glewIsSupported("GL_VERSION_4_0"))
printf("Ready for OpenGL 4.0\n");
else {
printf("OpenGL4.0 not supported\n");
exit(1);
}
setShaders();
glutMainLoop();
}

إليك الشكل التالي و هو عبارة عن المنهاج الضروري أو المسار المعتمد إن صح القول في عملية التظليل:

oglshader.gif

المرحلة الأولى و هي عملية إنشاء الكائن و الذي سيتضمن المظلل, عن طريق إستدعاء دالة

GLuin glCreateShader(Type)

هذه الدالة تقوم بإنشاء مظلل و يتم تمرير البارميتر type كأحد التعريفات التالية:

GL_VERTEX_SHADER
أو
GL_FRAGMENT_SHADER

و فيما يخص الإمتدادات نستدعي الدالة

GLhandleARB glCreateShaderObjectARB(GLenum shaderType);

و البارميتر shaderType سيأخذ أحد التعريفات التالية:

GL_VERTEX_SHADER_ARB
أو
GL_FRAGMENT_SHADER_ARB

و

و الآن بإمكانك إنشاء العديد من المظللات حسب ما تريده و إضافتها إلى تطبيقك و لهذا عليك أن تتذكر أن هناك دالة رءيسية (main) واحدة لإعداد الرؤوس و الأخرى لإعداد الأجزاء و هذا في كل برناج على حدى.

المرحلة التالية هي إضافة بعض الشيفرات المصدرية و الشيفرة المصدرية للمظلل ستأخذ شكل مصفوفة من نوع string.

الدالة التي ستقوم بإعداد الشيفرة المصدرية هي :

void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings);

البارميتر shader يمثل مقبض نحو المظلل.

البارميتر numOfStrings هو عدد الكلمات من نوع string في المصفوفة .

البارميتر strings يمثل المصفوفة .

البارميتر lenOfStrings هو طول الstring.

و من ناحية الإمتدادات , فالدالة التي تقوم بإعداد الشيفرة المصدرية هي

void glShaderSourceARB(GLhandleARB shader, int numOfStrings, const char **strings, int *lenOfStrings);

و أخيرا يجب ترجمة المظلل عن طريق إستدعاء الدالة مع نوع المقبض الممرر

void glCompileShader(GLuint shader);

و من ناحية الإمتدادات:

void glCompileShaderARB(GLhandleARB shader);

إنشاء البرنامج الفعلي:

الشكل التالي يعرض المراحل الأساسية لإعداد برنامج المظلل

oglprogram.gif

المرحلة الأولى هي إنشاء برنامج عن طريق إستدعاء الدالة التالية:

GLuint glCreateProgram(void);

بإمكانك إنشاء العديد من البرامج كيف ما تريد و بإمكانك الإنتقال من برنامج إلى آخر و حتى الرجوع إلى الخلف كتثبيت لخاصية ما عن طريق تصيير إطار واحد (single frame) مثلا أنك تريد إنشاء إبريق مع مظللات الإنعكاسات و الإنكسارات أو حتى إعداد مكعب معروض لل background بإستعمال خواص ثابتة من خواص OpenGL .

من ناحية الإمتدادات فالدالة المسؤولة عن إنشاء برنامج هي :

GLhandleARB glCreateProgramObjectARB(void);

المرحلة التالية تتضمن ربط المظللات المنشأة بالبرنامج المنشأ (ملاحظة : المظللات ليست بحاجة بأن تكون مترجمة لحظة الربط )

لأجل الربط بين المظلل و البرنامج نستدعي دالة الربط :

void glAttachShader(GLuint program, GLuint shader);

البراميتر program يمثل تعريف البرنامج.

البراميتر shader يمثل مقبض المظلل.

فيما يخص الإمتدادت نستدعي الدالة:

void glAttachObjectARB(GLhandleARB program, GLhandleARB shader);

إذا كان لديك الزوجان (الرأس و الجزء) للمظللات , فأنت بحاجة لربط كلاهما بالبرنامج و قد يكون لديك الكثير من المظللات من نفس النوع ( الرأس أو الجزء) مضموم مع نفس البرنامج.

المرحلة الأخيرة هي ربط البرنامج و يرجى التقيد بالخطوات.

الدالة التالية تقوم بربط البرنامج:

void glLinkProgram(GLuint program);

و فيما يخص الإمتدادت نستدعي الدالة:

void glLinkProgramARB(GLhandleARB program);

بعد عملية الربط يصبح مصدر المظللات بالكامل معدل و تصبح المظللات أيظا مترجمة من دون الحاجة إلى أخذها للبرنامج . و كما هو موضع على الشكل الأعلى , بعد ربط البرنامج , هناك دالة للتحميل الفعلي و إستعمال البرنامج :

void glUseProgram(GLuint prog);

البراميتر prog يمثل مقبض البرنامج الذي تريد إستعماله , يتم تمرير القيمة 0 لإرجاع لخاصية ثابتة أي أن هناك خاصية مثبة قيد التنفيذ.

و قد يكون لديك العديد من البرامج مربوطة و مقروؤة للإستعمال كما تريد.

ملاحظة : إذا كان لديك برنامج في طور الإستعمال و بعدها يربط ثانية , فإنه يصبح متموضع بشكل آلي في الإستعمال الثاني , ففي هذه الحالة أنت لست بحاجة لإستدعاء الدالة glUseProgram مرة ثانية.

فيما يخص الإمتدادت نستدعي الدالة:

void glUseProgramObjectARB(GLhandleARB prog);

مثال:

هذه الشيفرة تحتوي على المراحل الموصوفة سابقا :

void setShaders() {
char *vs,*fs;
v = glCreateShader(GL_VERTEX_SHADER);
f = glCreateShader(GL_FRAGMENT_SHADER);
vs = textFileRead("toon.vert");
fs = textFileRead("toon.frag");
const char * vv = vs;
const char * ff = fs;
glShaderSource(v, 1, &vv,NULL);
glShaderSource(f, 1, &ff,NULL);
free(vs);free(fs);
glCompileShader(v);
glCompileShader(f);
p = glCreateProgram();
glAttachShader(p,v);
glAttachShader(p,f);
glLinkProgram(p);
glUseProgram(p);
}

في ما يخص الإمتدادات:

void setShaders() {
char *vs,*fs;
v = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
f = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
vs = textFileRead("toon.vert");
fs = textFileRead("toon.frag");
const char * vv = vs;
const char * ff = fs;
glShaderSourceARB(v, 1, &vv,NULL);
glShaderSourceARB(f, 1, &ff,NULL);
free(vs);free(fs);
glCompileShaderARB(v);
glCompileShaderARB(f);
p = glCreateProgramObjectARB();
glAttachObjectARB(p,v);
glAttachObjectARB(p,f);
glLinkProgramARB(p);
glUseProgramObjectARB(p);
}

إقصاء بعض المشاكل: مفهوم (The InfoLog)

إن تصحيح أو معالجة (Debugging) المظللات صعبة نوعا ما , ليست هناك ما يعادل (printf) أو ما شابه أو قد لن يكون, خلال تطوير وسائل مع إمكانيات التصحيح قد يكون من المحتمل أن يتم ذلك مستقبليا. صحيح أنه يمكنك استخدام بعض الحيل الآن ولكن هذه ليست هيئة بأي وسيلة. الكل لم يتم فقدانه ويتم توفير بعض الدوال لمعرفة ما إذا كانت تعليماتك البرمجية تم تجميعها و ربطها بنجاح.

الحالات الخاصة بمراحل المترجم قد تتحصل عليها من السيد أوبن جي أل OpenGL مع الدالة التالية :

void glGetShaderiv(GLuint object, GLenum type, int *param);

البارميتر object هو مقبض للكائن , فقد يكون المظلل أو البرنامج.

البارميتر type يأخذ التعريف GL_COMPILE_STATUS.

البارميتر param يمثل مؤشر للقيمة المرجعة إذا كان صح فإن الإرجاع GL_TRUE أو العكس GL_FALSE لأي إشكال.

و بالإمكان الإستعلام عن حالات مرحلة الربط بإستدعاء التالية:

void glGetProgramiv(GLuint object, GLenum type, int *param);

البارميتر object هو مقبض للكائن , فقد يكون المظلل أو البرنامج.

البارميتر type يأخذ التعريف GL_LINK_STATUS.

البارميتر param يمثل مؤشر للقيمة المرجعة إذا كان صح فإن الإرجاع GL_TRUE أو العكس GL_FALSE لأي إشكال.

بالنسبة للإمتدادات:

oid glGetObjectParameterivARB(GLhandleARB object, GLenum type, int *param);

البارميتر type يأخذ أحد التعريفان GL_OBJECT_LINK_STATUS_ARB أو GL_OBJECT_COMPILE_STATUS_ARB.

البارميتر param يمثل مؤشر للقيمة المرجعة إذا كان صح فإن الإرجاع 1 أو العكس 0 لأي إشكال.

هناك الكثير من الطرق لحماية البارميتر الثاني type .

إذا ما تم التبليغ عن أخطاء فمن الممكن أخذ المعلومات الحالية مع النمط InfoLog و هذه الأخيرة تخزن معلومات حول آخر العمليات المنجزة و تماما مثل الأخطاء و التحذيرات كما في مرحلة الترجمة للغة سي c و المشاكل خلال مرحلة الربط. و ال InfoLog قد تخبرك أيظا عن ما إذا كانت المظللات التي أنشأتها تعمل على برمجياتك.

هذا يعني أن العتاد لا يدعم بعض الميزات التي تستعملها .

في الغالب ليس هناك تخصيصات لرسائل InfoLog , مثلا مختلف السواقات و العتاد قد تسبب العديد من Logs .

و من أجل الحصول على InfoLog للمظلل أو برنامج معين في ال OpenGL نستدعي أحد الدوال التالية :

void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log);
أو
void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log);

البارميتر الثاني maxLen يمثل العدد الأقصى للحروف من نوع "chars" لإسترادها من InfoLog.

البراميتر الثالث len يمثل مؤشر نحو الطول الفعلي لل InfoLog المستوردة ( و يمكن إعتبارها قيمة مرجعة أيظا لأننا هنا نتعامل مع المؤشرات ).

البارميتر الرابع log يمثل السجل log بحد ذاته.

بالنسبة للإمتدادات:

void glGetInfoLogARB(GLhandleARB object, int maxLen, int *len, char *log);

في هذه المرحلة سيكون لخصائص ال glsl مواصفات رائعة, لذا يستوجب معرفة طول ال InfoLog لإسترادها , و للوصول إلى هذا النوع من البيانات نستدعي الدالة التالية:

في ما يخص المظلل
void glGetShaderiv(GLuint object, GLenum type, int *param);
و فيما يخص البرنامج
void glGetProgramiv(GLuint object, GLenum type, int *param);

البارميتر object هو مقبض للكائن , فقد يكون المظلل أو البرنامج.

البراميتر type يأخذ التعريف التالي GL_INFO_LOG_LENGTH.

البراميتر param يمثل القيمة المرجعة, و هو طول ال InfoLog .

و فيما يخص الإمتدادات :

void glGetObjectParameterivARB(GLhandleARB object, GLenum type, int *param);

البراميتر type يأخذ التعريف GL_OBJECT_INFO_LOG_LENGTH_ARB.

المثال التالي هو عبارة عن شيفرة تعرض محتويات ال InfoLog :

	void printShaderInfoLog(GLuint obj)
	{
	    int infologLength = 0;
	    int charsWritten  = 0;
	    char *infoLog;

		glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

	    if (infologLength > 0)
	    {
	        infoLog = (char *)malloc(infologLength);
	        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
			printf("%s\n",infoLog);
	        free(infoLog);
	    }
	}

	void printProgramInfoLog(GLuint obj)
	{
	    int infologLength = 0;
	    int charsWritten  = 0;
	    char *infoLog;

		glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

	    if (infologLength > 0)
	    {
	        infoLog = (char *)malloc(infologLength);
	        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
			printf("%s\n",infoLog);
	        free(infoLog);
	    }
	}

و فيما يخص الإمتدادات :

	void printInfoLog(GLhandleARB obj)
	{
	    int infologLength = 0;
	    int charsWritten  = 0;
	    char *infoLog;

	    glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
						 &infologLength);

	    if (infologLength > 0)
	    {
		infoLog = (char *)malloc(infologLength);
		glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
			printf("%s\n",infoLog);
		free(infoLog);
	    }
	}

الإفراغ و التنظيف في GLSL:

في ما سبق تم عرض الدالة التي تقوم بإرفاق المظلل مع البرنامج و الأن نعرض الدالة التي تقوم بفصل المظلل عن البرنامج :

void glDetachShader(GLuint program, GLuint shader);

البراميتر الأول program يمثل البرنامج الذي سيتم تفكيكه عن المظلل.

البراميتر الثاني shader يمثل المظلل الذي سيتم تفكيكه عن البرنامج.

في ما يخص الإمتدادات :

void glDetachObjectARB(GLhandleARB program, GLhandleARB shader);

بإمكان إلغاء المظلل أو البرنامج بإستدعاء الدالة التالية :

void glDeleteShader(GLuint id);
أو
void glDeleteProgram(GLuint id);

البراميتر id يمثل مقبض المظلل أو البرنامج المراد إلغاءه.

فيما يخص الإمتدادات, فإنه هناك دالة وحيدة تقوم بعمل الإثنين معا:

void glDeleteObjectARB(GLhandleARB id);

البراميتر id يمثل إما مقبض المظلل أو إما مقبض البرنامج المراد إلغاءه.

في الحالات يبقى فيها المظلل مرفق مع البرامج , و أراد التطبيق ( بمعنى آخر المبرمج, لأن المبرمج هو من يحدد سير التطبيق ) إلغاءه, فإن ذلك لن يحدث و لكن فقط سيتم تعيينه كجزء قد يلغى لاحقا.

الإتصال بين الأوبن جي أل و المظللات:

إن أي تطبيف في الأوبن جي أل لديه عدة طرق للتواصل مع المظللات.

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

لذا مهم يكن , فإن إستعمال حالة الأوبن جي أل ليس دائما وسيلة جيدة لإعداد قيم المظللات للعمل عليها. على سبيل المثال لنعتبر أن هناك تظليل يتطلب متغير X للإخبار عن الوقت الممضي ( أو الوقت الذي أستغرق في شيء معين ) لأجل تحسين بعض التحريك . فلا توجدتسمية مناسبة لمتغير في حالة الأوبن جي أل لهذا الغرض, و بالفعل بإمكانك إستعمال إضاءة عامة ( lights specular ) و الزاوية الخاصة بالإضاءة ( cutoff angle ) لهذا الغرض .

و لحسن الحظ فإن ال GLSL يخصص تعريف للمتغيرات التي يعرفها المستعمل ( أو المبرمج ) لتطبيق الأوبن جي أل للإتصال مع المظلل و تدعى بالمحددات أيظا.

و لهذا , فإن ال GLSL يتوفر على نوعين من المتغيرات هما:

    * Uniform
    * Attribute

إن المتغيرات المعرفة في المظللات بإستعمال هذه المحددات هي للقراءة فقط و بقدر ما يكون المظلل ذو عناية.

و أيظا هناك طريقة أخرى لإرسال قيم المتغيرات إلى المظلل و ذلك بإستخدام الإكساءات ( textures ), فالإكساء ليس بالضرورة أن يكون معرف كصورة, لأنه قد يكون معرف كمصفوفة , حين إستعمال المظلل فإن المبرمج هو الوحيد الذي سيقرر كيف سيعد بيانات الإكساء.

المتغيرات من نوع Uniform :

المتغير من نوع Uniform قد يكون لديه قيمة تتغير على حسب المشهد primitive فقط. أي أن قيمته لا تتغير بين مجال ( glBegin / glEnd ) و هذا يعني أنه لا يكمن إستعمالها للرؤوس من نوع attributes , لذا إن المتغيرات التي هي من نوع Uniform مناسبة جدا للقيم التي تبقى ثابتة و لا تتغير خلال المشهد primitive أو الإطار frame أو أي حدث خلال المشهد كله. و بالإمكان القراءة من المتغرات التي هي من نوع Uniform لكلا مظللات الرؤوس و الإكساء و ليس للكتابة عليها.

الشيء الأول الذي يتوجب عمله هو تجهيز مكان ذاكري للمتغير و هذا بعد إتمام مرحلة ربط البرنامج ( ملاحظة : إن بعض السواقات قد ترغمك على إستعمال البرنامج و لإستعمال البرنامج نستدعي الدالة التالية glUseProgram أو glUseProgramObjectARB و ذلك قبل الحصول على مكان ذاكري للمتغير ).

الدالة التي ستقوم بحجز مكان ذاكري للمتغير من توع Uniform , و إرجاع إسم كتعريف في المظلل :

         GLint glGetUniformLocation(GLuint program, const char *name);

البراميتر program هو مقبض للبرنامج.

البراميتر name مؤشر على قيمة تدل على إسم المتغير.

فيما يخص الإمتدادات :

         GLint glGetUniformLocationARB(GLhandleARB program, const char *name);

القيمة المرجعة من الدالة glGetUniformLocation هو موقع المتغير و الذي بإمكاننا إستعماله لنعين قيمة ما عليه, و الآن لدينا عائلة من الدوال تقوم بإعداد متغيرات من نوع Uniform , و يجري إستخدامها إعتمادا على نوع بيانات المتغير :

void glUniform1f(GLint location, GLfloat v0);
void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
أو
GLint glUniform{1,2,3,4}fv(GLint location, GLsizei count, GLfloat *v);

البراميتر location يمثل الموقع.

v0,v1,v2,v3 هي قيم من نوع float.

البراميتر count هو عدد العناصر في المصفوفة.

البراميتر v هو مؤشر لمصفوفة من نوع float.

بالنسبة للإمتدادات :

void glUniform1fARB(GLint location, GLfloat v0);
void glUniform2fARB(GLint location, GLfloat v0, GLfloat v1);
void glUniform3fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
void glUniform4fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
أو
GLint glUniform{1,2,3,4}fvARB(GLint location, GLsizei count, GLfloat *v);

و أيظا هناك دوال خاصة بالمتغيرات من نوع integer , بحيث سيحل محل الرمز "f" الرمز "i" . و لإستعمال المتغيرات البسيطة نستعمل الدالة glUniform1i و الدالة glUniform1iARB فيما يخص الإمتدادات , أو إذا كانت عينات من مضفوفة ما نستعمل الدالة glUniform1iv أو الدالة glUniform1ivARB فيما يخص الإمتدادات.

ملاحظة: ليس هناك دوال مخصصة لمتغيرات من نوع bools أو boolean vectors .

هناك دالة مخصصة للمتغيرات من نوع مصفوفة :

                GLint glUniformMatrix{2,3,4}fv(GLint location, GLsizei count, GLboolean transpose, GLfloat *v);

البراميتر location يمثل الموقع.

البراميتر count يمثل عدد المصفوفات , تعيينه بالرقم 1 يدل على مصفوفة وحيدة .

البراميتر transpose لقلب قيم المصفوفة, فالقيمة 1 تشير إلى أن قيم المصفوفة محددة في row major order و القمية 0 لل column major order.

البراميتر v يمقل مصفوفة من نوع floats.

فيما يخص الإمتدادات:

        GLint glUniformMatrix{2,3,4}fvARB(GLint location, GLsizei count, GLboolean transpose, GLfloat *v);

ملاحظة: القيم المعدة بهذه الدوال تبقى ثابتة و محافظة على قيمها إلى غاية ربط البرنامج من جديد.

هذا مثال :

لنفرض أن التظليل سيستعمل المتغيرات التالية:

	uniform float specIntensity;
	uniform vec4 specColor;
	uniform float t[2];
	uniform vec4 colors[3];

هذه شيفرة تقوم بإعداد المتغيرات:

	GLint loc1,loc2,loc3,loc4;
	float specIntensity = 0.98;
	float sc[4] = {0.8,0.8,0.8,1.0};
	float threshold[2] = {0.5,0.25};
	float colors[12] = {0.4,0.4,0.8,1.0,
				  0.2,0.2,0.4,1.0,
				  0.1,0.1,0.1,1.0};

	loc1 = glGetUniformLocation(p,"specIntensity");
	glUniform1f(loc1,specIntensity);

	loc2 = glGetUniformLocation(p,"specColor");
	glUniform4fv(loc2,1,sc);

	loc3 = glGetUniformLocation(p,"t");
	glUniform1fv(loc3,2,threshold);

	loc4 = glGetUniformLocation(p,"colors");
	glUniform4fv(loc4,3,colors);

فيما يخص الإمتدادات:

	GLint loc1,loc2,loc3,loc4;
	float specIntensity = 0.98;
	float sc[4] = {0.8,0.8,0.8,1.0};
	float threshold[2] = {0.5,0.25};
	float colors[12] = {0.4,0.4,0.8,1.0,
				  0.2,0.2,0.4,1.0,
				  0.1,0.1,0.1,1.0};

	loc1 = glGetUniformLocationARB(p,"specIntensity");
	glUniform1fARB(loc1,specIntensity);

	loc2 = glGetUniformLocationARB(p,"specColor");
	glUniform4fvARB(loc2,1,sc);

	loc3 = glGetUniformLocationARB(p,"t");
	glUniform1fvARB(loc3,2,threshold);

	loc4 = glGetUniformLocationARB(p,"colors");
	glUniform4fvARB(loc4,3,colors);

أظن أن الأمر واضح.

المتغيرات من نوع Attribute :

إذا كنت تريد إعطاء قيمة ما لكل رأس ( Vertex ) فهنا سيتوجب عليك إستعمال المتغيرات التي تأخذ النوع Attribute , و بصيغة أخرى فإن المتغيرات من نوع Attribute بالإمكان تحديثها في أي وقت , و يمكن الوصول إلى المتغيرات من نوع Attribute فقط لقراءتها ( وليس للكتابة عليها ) في حالة مظللات الرؤوس. و السبب أنها قد تحتوي على بيانات الرؤوس و هذا لا ينطبق على مظللات الأجزاء.

و الأن هذه دالة لإعداد مكان ذاكري للمتغيرات من نوع Attribute :

    GLint glGetAttribLocation(GLuint program,char *name);

البراميتر program هو مقبض للبرنامج.

البراميتر name هو إسم المتغير.

في ما يخص الإمتدادات:

       GLint glGetAttribLocationARB(GLhandleARB program,char *name);

تقوم هذه الدلة glGetAttribLocation بإرجاع موقع الذاكرة , و لذا سنستعمل هذا الموقع و نعين عليه قيمة بإستدعاء الدالة التالية:

void glVertexAttrib1f(GLint location, GLfloat v0);
void glVertexAttrib2f(GLint location, GLfloat v0, GLfloat v1);
void glVertexAttrib3f(GLint location, GLfloat v0, GLfloat v1,GLfloat v2);
void glVertexAttrib4f(GLint location, GLfloat v0, GLfloat v1,,GLfloat v2, GLfloat v3);
أو
GLint glVertexAttrib{1,2,3,4}fv(GLint location, GLfloat *v);

هذه العائلة واضحة و هي تماما كالمتغيرات من نوع Uniform.

بالنسبة للإمتدادات:

void glVertexAttrib1fARB(GLint location, GLfloat v0);
void glVertexAttrib2fARB(GLint location, GLfloat v0, GLfloat v1);
void glVertexAttrib3fARB(GLint location, GLfloat v0, GLfloat v1,GLfloat v2);
void glVertexAttrib4fARB(GLint location, GLfloat v0, GLfloat v1,,GLfloat v2, GLfloat v3);
أو
GLint glVertexAttrib{1,2,3,4}fvARB(GLint location, GLfloat *v);

هناك بعض الدوال للمتغيرات من نوع integers و بعض الأنواع الأخرى.

مثال: لنفترض أننا قمنا بتعريف متغير من نوع float attribute و بإسم height , فالمرحلة التي يتوجب القيام بها هي ربط البرنامج:

	loc = glGetAttribLocation(p,"height");

و في دالة العرض نعد التالي:

	glBegin(GL_TRIANGLE_STRIP);

		glVertexAttrib1f(loc,2.0);
		glVertex2f(-1,1);

		glVertexAttrib1f(loc,2.0);
		glVertex2f(1,1);

		glVertexAttrib1f(loc,-2.0);
		glVertex2f(-1,-1);

		glVertexAttrib1f(loc,-2.0);
		glVertex2f(1,-1);

	glEnd();

فيما يخص الإمتدادات نضيف فقط الآحقة ARB إلى مجموعة الدوال.

و بالإمكان أيظا دعم المتغيرات من نوع attribute لمصفوفة النقاط , فالشيء الأول هو تفعيل مصفوفة الرؤوس التي هي من نوع attribute عن طريق إستدعاء الدالة التالية:

         void glEnableVertexAttribArray(GLint loc);

البراميتر loc يمثل موقع المتغير.

فيما يخص الإمتدادات :

        void glEnableVertexAttribArrayARB(GLint loc);

ثم تأتي مرحلة إعداد مؤشر مع المصفوفة بالبيانات عن طريف إستدعاء الدالة التالية :

       void glVertexAttribPointer(GLint loc, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);

البراميتر loc يمثل موقع المتغير.

البراميتر size هو عدد المركبات لكل عنصر, على سبيل المثال : 1 لل float و 2 لل vec2 و 3 لل vec3 و هكذا.

البراميتر type نوع المتغير , على سبيل المثال GL_FLOAT .

البراميتر normalized إذا ما تم تعيينه بالقيمة 1 هذا يعني أن قيم المصفوفة يتم تطبيعها ( normalized ) .

البراميتر stride يمثل البعد بين كل عنصر و عنصر مجاور له.

البراميتر pointer هو مؤشر للمصفوفة التي تحتوي على البيانات.

فيما يخص الإمتدادات:

          void glVertexAttribPointerARB(GLint loc, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);

إليك هذه الشيفرة:

في البداية نقوم بإعداد المراحل , و لتكن لدينا مصفوفتان واحدة للرؤوس و الأخرى مصفوفة من نوع attribute , و لنعتبر أن المتغير heights قد تم تعريفة من قبل :

	float vertices[8] = {-1,1, 1,1, -1,-1, 1,-1};
	float heights[4] = {2,2,-2,-2};

	...

	loc = glGetAttribLocation(p,"height");

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableVertexAttribArray(loc);

	glVertexPointer(2,GL_FLOAT,0,vertices);
	glVertexAttribPointer(loc,1,GL_FLOAT,0,0,heights);

أنواع البيانات و المتغيرات:

يتوفر ال GLSL على بيانات المتغيرات التالية المتداولة :

    * float
    * bool
    * int

و يتوفر ال GLSL على متجهات Vectors ب 2 أو 3 أو 4 مركبات متوافقة مع كل نوع من الأنواع السابقة float و int و bool :

    * vec{2,3,4} a vector of 2,3,or 4 floats
    * bvec{2,3,4} bool vector
    * ivec{2,3,4} vector of integers

المصفوفات المربعة من فئة 2x2, 3x3 أو 4x4 و كثيرا ما تستخدم بشكل أساسي في الرسوميات المتقدمة.

    * mat2
    * mat3
    * mat4

و هناك مجموعة من أنواع خاصة متاحة للوصول إلى الإكساءات. و تسمى العينات samplers و تتطلب الوصول إلى قيم الإكساء, و هي معروفة أيظا بإسم ال texels.

أنواع البيانات لتبسيط الإكساء هي :

    * sampler1D - for 1D textures
    * sampler2D - for 2D textures
    * sampler3D - for 3D textures
    * samplerCube - for cube map textures
    * sampler1DShadow - for shadow maps
    * sampler2DShadow - for shadow maps

في ال GLSL يتم إعداد المصفوفات تماما مثلما في لغة C .

و يتوفر ال GLSL أيظا على البنيات Structures :

	struct dirlight {
		vec3 direction;
		vec3 color;
	};

المتغيرات :

إعداد المتغيرات في ال GLSL بالبيانات أو القيم هو مشابه تماما في لغة C:

	float a,b;
	int c = 2;
	bool d = true;

الإعلان عن متغيرات أخرى هو مشابه لنفس النمط في لغة C , و لكن هناك إختلاف بين ال GLSL و الC من ناحية التهيئة. فال GLSL تعتمد إعتمادا كبيرا على المنشئ constructor بخصوص التهيئة .

	float b = 2;		// incorrect, there is no automatic type casting

	float e = (float)2;// incorrect, requires constructors for type casting

	int a = 2;
	float c = float(a); // correct. c is 2.0

	vec3 f;		// declaring f as a vec3
	vec3 g = vec3(1.0,2.0,3.0); // declaring and initializing g

و بالإمكان تهيئة متغير ما عن طريق متغير مهيء من قبل:

	vec2 a = vec2(1.0,2.0);
	vec2 b = vec2(3.0,4.0);

	vec4 c = vec4(a,b) // c = vec4(1.0,2.0,3.0,4.0);

	vec2 g = vec2(1.0,2.0);
	float h = 3.0;

	vec3 j = vec3(g,h);

و أيظا فيما يخص المصفوفات :

	mat4 m = mat4(1.0) // initializing the diagonal of the matrix with 1.0

	vec2 a = vec2(1.0,2.0);
	vec2 b = vec2(3.0,4.0);

	mat2 n = mat2(a,b); // matrices are assigned in column major order

	mat2 k = mat2(1.0,0.0,1.0,0.0); // all elements are specified

و الأن إلى البنيات :

	struct dirlight {		// type definition
		vec3 direction;
		vec3 color;
	};

	dirlight d1;
	dirlight d2 = dirlight(vec3(1.0,1.0,0.0),vec3(0.8,0.8,0.4));

و يوفر ال GLSL على أشياء تعد في قمة الإبداع و جعلها أكثر وضوحا:

	vec4 a = vec4(1.0,2.0,3.0,4.0);
	أو
	float posX = a.x;
	float posY = a[1];
	أو
	vec2 posXY = a.xy;
أو	
	float depth = a.w

بالإمكان إستخدام المجاهيل x و y و z و w للوصول إلى مركبات لشعاع ما. و فيما يخص مركبات اللون فنستعمل المجاهيل r و g و b و a . وفيما بخص الإكساءات نستعمل المجاهيل s,t,p,q .

و مثال عن البنيات للوصول إلى متغيراتها تماما كما في لغة ال c :

d1.direction = vec3(1.0,1.0,1.0);

الحالات و الدوال :

كل الحالات if-else مشابهة للغة السي c و الحلقات for و while و do-while هي أيظا كذلك:

		if (bool expression) 
		...
	else
		...


	for (initialization; bool expression; loop expression)
		...

	while (bool expression)
		...

	do 
		...
	while (bool expression)

و تتيح لغة ال GLSL بعض القفزات :

# continue - available in loops, causes a jump to the next iteration of the loop

# break - available in loops, causes an exit of the loop

# discard

يتم إستعمال الخاصية discard فقط في حالات مظللات الأجزاء .فهي تسبب إنهاء لمظلل للجزء الحالي ( current fragment ) بدون الكتابة على ذاكرة الإيطار ( frame buffer ) أو العمق ( depth ) .

الدوال :

كما في لغة C يتم كتابة شيفرة المظلل بداخل دالة معدة له, فعلى الأقل كل نوع من المظللات يجب أن يكون لدية دالة مخصصة له و هي الدالة الرئيسية main :

	void main()

إن ما يقوم به المبرمج في لغة c عادة هو إعداد دالة بمدخلات و مخراج وقيم ترجعها الدالةو لكن ما الجديد في ال GLSL. هناك تعديل على البرامترات :

in دليل على أن البراميتر عبارة عن مدخل.

out دليل على أن البراميتر عبارة عن مخرج.

inout لكلاهما.

إذا لم يتم تحديد أحد هذه المحددات فإن المحدد الإفتراضي سيكون in.

و الدالة قد تكون ذات تحميل ذائد ( overloaded ) مادامت قائمة البرامترات مختلفة.

التكرار غير معروف في مواصفات ال GLSL .

و هذه شيفرة لمثال عن الدوال:

	vec4 toonify(in float intensity) {

		vec4 color;

		if (intensity > 0.98)
			color = vec4(0.8,0.8,0.8,1.0);
		else if (intensity > 0.5)
			color = vec4(0.4,0.4,0.8,1.0);	
		else if (intensity > 0.25)
			color = vec4(0.2,0.2,0.4,1.0);
		else
			color = vec4(0.1,0.1,0.1,1.0);		

		return(color);
	}

المتغيرات من نوع Varying :

هذا النوع من المتغيرات يستعمله المبرمج للكتابة على مظلل الرؤوس حيث يقوم المبرمج بالتعديل على هذا المتغير و ذلك لكل رأس .

هذا الدرس مستمر إن شاء الله من ناحية الإضافة و الدعم و تقبل الإنتقادات و تحياتي.

3

تخيل, هل سيكون مستقبل الأوبن جي أل مع البشرية هكذا

future.jpg
#2

السلام عليكم

عموما لقد نسيت شيئا هاما و هو :

ما هو مظلل الجزء ؟

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

مثال :

تخيل أنه لديك بطاقة رسوميات تقدم عرض في الشاشة بمقاس 1024 * 768 و تريد برمجة مشهد رسومي و ذلك بإلإستعانة بمظلل الجزء فالنتيجة التي سنصل إليها هي أن عدد الحسابات التي سينجزها تطبيقك 1024 * 768 = 786432 مرة أي لكل بكسل و ذلك في كل لحظة عرض للمشهد.

المداخل الخاصة بمظلل الجزء:

gl_Color
gl_FragCoord
gl_SecondaryColor
gl_TexCoord[n]
gl_FogFragCoord

المخارج :

gl_FragColor
gl_FragDepth
gl_FragData[n]

ما هو مظلل الرأس :

هو تظليل يقوم بمعالحة الرؤوس قبل تقديمها لمظلل الجزء فمظلل الرأس هو المسؤول عن حسابات الناظم و اللون و إحداثيات الإكساء و مصفوفة الإسقاط و التحويلات و الكثير و هذا لكل رأس.

المداخل الخاصة بمظلل الرأس:

gl_Vertex
gl_Color
gl_Normal
gl_MultiTexCoordn
gl_SecondaryColor
gl_FogCoord

المخارج :

gl_Position
gl_FrontColor
gl_BackColor
gl_FrontSecondaryColor
gl_BackSecondaryColor
gl_TexCoord[n]
gl_FogFragCoord
gl_PointSize
gl_ClipVertex

و هذه قائمة من الدوال المسؤولة عن التحويلات و الإسقاطات ووو :

gl_ModelViewMatrix
gl_ModelViewMatrixInverse
gl_ModelViewMatrixTranspose
gl_ModelViewMatrixInverseTranspose
---
gl_ProjectionMatrix
gl_ProjectionMatrixInverse
gl_ProjectionMatrixTranspose
gl_ProjectionMatrixInverseTranspose
---
gl_ModelViewProjectionMatrix
gl_ModelViewProjectionMatrixInverse
gl_ModelViewProjectionMatrixTranspose
gl_ModelViewProjectionMatrixInverseTranspose
---
gl_TextureMatrix[n]
gl_TextureMatrixInverse[n]
gl_TextureMatrixTranspose[n]
gl_TextureMatrixInverseTranspose[n]
---
gl_NormalMatrix

تخيل, هل سيكون مستقبل الأوبن جي أل مع البشرية هكذا

future.jpg
#3

مقال متميز أخي ، وكنت انتظر مثل هذا من زمن طويل ، فاريد من يساعدني على أن أتعلّم GLSL ،

في الحقيقة ، سأبدأ بالملاحظات وهي مجرد ملاحظات صغيرة وتنظيمية لتساعد المبتدئ على أن يستفيد من المقالة بشكل أفضل .

- أنت تريد أن تدعم " أسلوب الامتدادات " و " الأسلوب القياسي " في OpenGL وهذا سيضيف تشويش على المبتدئ دون داعي ، فكما تعلم ، الامتدادات هي مبادرة من المصنعين ، وبعدها تتحول إلى مواصفات معتمدة ، فيتم إزالة الاضافة ARB من اسماء تلك الامتدادات ، ويتم ترتيبها وضمها للمواصفات . الآن OpenGL 2.1 اصبحت مدعومة من الكثير و الكثير من كروت الشاشة ، فلماذا نحرص على دعم بعض امتدادات OpenGl 1.5 أو OpenGL 1.4 ؟ هذا سيضيف عبء على القارئ ولن نستفيد شيئاً من دعم امتدادات قديمة أصبحت " في طي النسيان " و بعضها أصبح مهجوراً وتم اسقاطه من OpenGL .

post-42837-029372000 1283880775_thumb.pn

لذلك أرى أن التركيز على OpenGL 2.0 وما بعدها سيجعل العبء على الكاتب أقل ، والقارئ سيكون مركز أكثر ، اما من لا يملك كرت شاشة يدعم OpenGL 2.0 ، فهو ليس " بلاعب " و " مبرمج ألعاب " .

- المقالة طويلة نوعاً ما ، و ارى أنه من الأفضل تجزئتها لاربعة أجزاء ، كل جزء في موضوع مستقل .

- كتابة أول برنامج لك في GLSL ( عرض مثال بسيط جداً يرسم مثلث بلون أحمر بدون أي اضافات ) .

- تتبع البرنامج باستخدام InfoLog .

- مفهوم المتغيرات من نوع uniform و attribute ، وطرق التواصل بين GLSL و OpenGL ( مثال بسيط أيضاً ) .

- المزيد من التطبيق على GLSL واستخدام بعض المتغيرات و الدوال التي ذكرتها في آخر موضوعك .

- من الأفضل كتابة المصطلح الانجليزي بجانب كل تعريب ، فمثلاً مظللات الأجزاء كما سمّيتها ، من الأفضل أن يعرف القارئ ماذا تقصد بها ، حتى لا ينشغل القارئ في البحث عن ما تقصده .

- لعل بعض التفصيلات الدقيقة مثل : سرد جميع أشكال الدالة glUniform قد تأخذ مساحة دون فائدة كبيرة ، فهناك مراجع مختصة مثل OpenGL SDK تعنى بهذا ، لذلك تركيز المقال على شكل واحد لأحد الدوال أرى أنه أكثر فائدة ، ويمكن إضافة ملاحظة في آخر المقالة أو حاشية لتقوم بالغرض وتوجيه القارئ نحو المرجع المناسب .

لدي أسئلة ، فأنا مبتدئ للأسف :( :

- gl_color ، أشاهدها أحياناً تكتب مع vertex shader وأحياناً مع fragment shader ، هل تعرف لماذا ؟ كيف يمكن أن نرى gl_color مع vertex shader ؟ مع أنها مختصة بالبكسلات ..

- هل هناك فرق كبير بين OpenGl 4.0 و OpenGL 2.0 من ناحية التعامل مع " المظللات " / GLSL ... أقصد هل هناك فرق في عملية ربط OpenGL مع GLSL مثلاً ،

اعذرني على كثرة الملاحظات والأسئلة .. فالموضوع ممتع بشكل لا يمكن أن أتحمّله :D ..

جزيت خيراً ..

المرفقات
glsl.png
2

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#4

السلام عليكم

اقتباس

- gl_color ، أشاهدها أحياناً تكتب مع vertex shader وأحياناً مع fragment shader ، هل تعرف لماذا ؟ كيف يمكن أن نرى gl_color مع vertex shader ؟ مع أنها مختصة بالبكسلات ..

صحيح أخي , إن إستعمال دالة gl_color يحضى لدا كلا المظللين الرأس و الجزء و يشتركان في نفس المهمة و الوظيفة .

مثال على إستعمالها في مظلل الجزء :

هل رأيت ألعاب دراغون بول زاد و بعض الألعاب التي تم تصنيعها على أنها ثنائية الأبعاد و لكنها في الحقيقة ثلاثية الأبعاد , هذا كله بإستخدام دالة gl_color حيث يقوم المبرمج بتحديد قيمة ثابتة لدا كل بكسل و ذلك عن طريق حسابات مبدئية متوافقة مع إسقاط موقع الإنارة و موقع الرؤية مع الرأس في مظلل الرأس.

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

و إستعمالها ب في مظلل الرأس vertex shader يستدعي أنك لن تغير على البكسلات لحظة حسابها في مظلل الرأس.

اقتباس

كيف يمكن أن نرى gl_color مع vertex shader ؟

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

اقتباس

- هل هناك فرق كبير بين OpenGl 4.0 و OpenGL 2.0 من ناحية التعامل مع " المظللات " / GLSL ... أقصد هل هناك فرق في عملية ربط OpenGL مع GLSL مثلاً ،

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

و عملية الربط مماثلة جدا للمكتبات الحديثة و القديمة .

أتمنى التوفيق.

1

تخيل, هل سيكون مستقبل الأوبن جي أل مع البشرية هكذا

future.jpg
#5

gl_color إذاً يمكن أن نستخدمها مع vertex ! معلومة ممتازة ، بارك الله فيك .

اقتباس
و عملية الربط مماثلة جدا للمكتبات الحديثة و القديمة .

نعم ، كنت أسأل عن عملية الربط ..

بارك الله فيك .

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#6

بارك الله فيك على الموضوع القيم . الذي لا غنى عنه

انا قرأت هذا الموضوع سابقا في develppez لكني مررت عليه مرور الكرام .

مواضيع مشابهة