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

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

الفرق واضح جدا .
إعداد الأوبن جي أل لل 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();
}إليك الشكل التالي و هو عبارة عن المنهاج الضروري أو المسار المعتمد إن صح القول في عملية التظليل:

المرحلة الأولى و هي عملية إنشاء الكائن و الذي سيتضمن المظلل, عن طريق إستدعاء دالة
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);
إنشاء البرنامج الفعلي:
الشكل التالي يعرض المراحل الأساسية لإعداد برنامج المظلل

المرحلة الأولى هي إنشاء برنامج عن طريق إستدعاء الدالة التالية:
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 :
هذا النوع من المتغيرات يستعمله المبرمج للكتابة على مظلل الرؤوس حيث يقوم المبرمج بالتعديل على هذا المتغير و ذلك لكل رأس .
هذا الدرس مستمر إن شاء الله من ناحية الإضافة و الدعم و تقبل الإنتقادات و تحياتي.

