8.7 مكتبة openGl و GLUT كتاب لينكس الشامل >>

8.7 مكتبة openGl و GLUT

مقدمة

تعتبر مكتبة openGL أفضل مكتبة للرسم ثلاثي الأبعاد عالي الدقة خصوصا للرسومات المعدة مسبقا والآن مع تطور بطاقات العرض وسرعة الأجهزة بدأت تطرح أيضا كمكتبة للرسم التفاعلي هذا اضافة لكونها مكتبة مفتوحة وتعمل على أكثر من منصة cross-platform، ولكنها مكتبة تفتقر لوظائف تتبع لوحة المفاتيح وعمل نوافذ وقوائم وطباعة نصوص (لأن هذا خارج أهداف تصميمها) لهذا تستخدم م مكتبة أخرى للقيام بذلك مثل اكس (في لينكس) أو GDI في ويندوز ولكن هذا يجعل جزء من الكود يجب أن يكتب لكل نظام تشغيل على حداً وهذا مخالف لأهداف تصميم openGL.

لهذا جاءت glut وهي مكتبة cross-platform أيضا تقوم بالتعامل مع النوافذ ولوحة المفاتيح والفأرة ... إلخ وبهذا يكون البرنامج كاملا cross-platform.

البداية

يبدأ البرنامج ب #include<GL/glut.h> و #include<GL/gl.h> ويمكنك أيضا إضافة (إذا كنت تنوي استعمالها) #include<GL/glu.h > ويجب أن تنتبه إلى أن الاسم الدليل GL بالحروف الكبيرة ويجب إضافة هذه المكتبات برنامج عند تصنيف البرنامج وذلك باضافة الخيارات التالية إلى خيارات الربط -lopengl -lglu -lglut في سطر الأوامر بعد gcc أو g++ أو حتى في linking options إذا كنت تعمل من بيئة تطور متكاملة وبهذا تصبح ملفاتك مرتبطة بالمكتبات libopengl.so libglu.so libglut.so أو في ويندوز مع تطبيق SGI للمكتبة opengl.dll glu.dll glut.dll

warningتحذير

اذا كنت تستخدم تعريفات من شركة nVidia انتبه هناك خطأ شائع بأن يكون الربط مع مكتبتهم بدل المكتبة القياسية

warningتحذير

في نظام ويندوز هناك نوعان من هذه المكتبات النوع الأول من الشركة الواضعة للمشروع sgi وأخرى من مايكروسوفت الخيارات التي ذكرناها هي لإصدار sgi أما خيارات مايكروسوفت هي -lopengl32 -lglu32 -lglut32 وتعتمد ملفات opengl32.dll glu32.dll glut32.dll بدلا من opengl.dll glu.dll glut.dll

لتسهيل تذكر الأسماء الأساسية اعلم أن الثوابت تكون كلها كبيرة يفصل بين كلماتها _ وتبدأ باسم المكتبة مثل GLUT_RGB وأن أسماء الوظائف تبدأ باسم المكتبة بحروف صغيرة ثم بأحرف استهلالية كبيرة ولايفصل بينها _ مثل glutInitWindowSize ويلحق بها أحيانا البعد الذي تتعامل فيع (اما 2 أو 3 أو 4) ونوعه (اما صحيح i كسري أو f أو بضعف الدقة d) مثلا glVertex2i(int x,int y) اما إذا كنا سنضع مؤشر على عنوان الإحداثيات وليس الإحداثيات نفسها نضيف v مثلا glVertex3fv(float *fptr) حيث fptr هي مؤشر إلى مصفوفة array بها ثلاث عناصر x و yو z float fptr[3]={x,y,z} أما الأنواع فهي تبدأ باسم المكتبة بأحرف كبيرة ثم الباقي بأحرف صغيرة مثلا GLfloat

لنبدأ بكتابة البرنامج

يبدأ البرنامج بتهيئة مكتبة glut ب glutInit( &argc, argv ); ثم بتحديد طور العرض مثلا glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); لتحديد GLUT_RGBA بدلا من GLUT_INDEX الأولى تعني ألوان حقيقية والأخرى تعني ألوان محدودة ومفهرسة لتحديد GLUT_DOUBLE بدلا من GLUT_SINGLE في الأولى كل ما ترسمه ينقل إلى الشاشة مباشرة فتبدو الشاشة وكأنها ترمش وفي الثانية ترسم في ذاكرة مؤقتة buffer أولا ثم تظهر دفعة واحدة على الشاشة عند الرغبة باستدعاء glutSwapBuffers(); أما GLUT_DEPTH فهي تستخدم تقنية zbuffer لكي تخفي أجزاء من الرسم التي التي يوجد شيء أمامها وهي ضرورية عند الرسم ثلاثي الأبعاء، ولتشغيل هذه الطريقة عليك استدعاء glEnable(GL_DEPTH_TEST); في مكان ما قبل بدء الرسم. بعد ذلك يأتي glutDisplayFunc( my_display ); حيث my_display هي الوظيفة التي تقوم أنت بكتابتها لتقوم برسم الأشياء التي تريد. ثم يأتي دور glutMainLoop(); التي تنتظر حتى تغلق النافذة أو ينتهي البرنامج

// include the GL lib's
#include<GL/gl.h>
#include<GL/glu.h>
#include<GL/glut.h>

void mydisplay() {
	// drawing calls goes here
	glClearColor(1.0,1.0,1.0,1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	glFlush();
	glutSwapBuffers();
}
int main( int argc,char *argv[]) {
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
	glutInitWindowPosition(0,0);
	glutInitWindowSize(320,240);
	glutCreateWindow("My 1st openGL");
	glutDisplayFunc( mydisplay );
	glutMainLoop();
	
	// return success	
	return 0;
}

هذا البرنامج يظهر نافذة بحجم 320x240 مووضوعة في الزاوية اليسرى العليا للشاشة وقد حددنا هذا ب glutInitWindowPosition(0,0); glutInitWindowSize(320,240); وفتحنا نافذة بهذه المواصفات تحمل عنوان My 1st openGL ب glutCreateWindow لا تحتوي سوى على مساحة بيضاء وذلك بتحديد لون المسح إلى أبيض glClearColor(1.0,1.0,1.0,1.0); ثم مسح الذاكرة المؤقتة بذلك اللون ب glClear(GL_COLOR_BUFFER_BIT); وفي المستقبل عند رسم رسومات ثلاثية الأبعاد نستخدم glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); ثم قلنا للمكتبة gl أن تكتب كل شيء في الذاكرة المؤقتة glFlush(); قبل أن ننقل ما فيها إلى الشاشة glutSwapBuffers(); ولرسم شيء حقيقي نضع الكود الخاص بذلك بين glColor و glFlush وأسهل الطرق هي استعمال الهيئة التالية:

glBegin(GL_LINE_LOOP)
	glVertex2i(50,50);
	glVertex2i(100,50);
	glVertex2i(100,100);
	glVertex2i(50,100);
glEnd();

بحث نستبدل glVertex2i بالبعد الذي تريد ونستبدل GL_LINE_LOOP بأي شيء من الجدول
GL_POINTS رسم كل نقطة
GL_LINES ترسم الخط الواصل بين النقطة الأولى والثانية ثم الثالثة والرابعة وهكذا
GL_LINE_STRIP ترسم خط بين كل نقط والتي قبلها
GL_LINE_LOOP ترسم كما في التي قبلها مع وصل النقطة الأخيرة بالأولى
GL_LINE_POLYGON كما في التي قبلها ولكن ترسم المضلع بدلا من حدوده
GL_QUADS كل أربع نقاط وكأنها مضلع لوحده مثلا 1و2و3و4 مضلع ثم 5و6و7و8 مضلع ثاني وكذا
GL_TRIANGLES كل ثلاث نقاط وكأنها مضلع لوحده
GL_TRIANGLE_STRIP النقاط الثلاث الأولى ترسم مضلع وكل نقطة جديدة مع آخر اثنتين قبلها تشكل المضلع الجديد
ولتلوين الشكل نذكر اللون قبل النقطة واذا ذكرنا أكثر من لون في نفس الشكل سيعمل تدرج نستخدم glColor3f(R,G,B); حيث RGB هي قيم من 0.0 إلى 1.0 glColor3ub(R,G,B); حيث RGB هي قيم من 0 إلى 255 ويمكن تحديد حجم النقطة مثلا ب glPointSize(1.5);

وللتفاعل مع المستخدم توفر مكتبة glut هذه الوظائف التي يمكنك أن تكتبها وتضع فيها كيف يتصرف البرنامج عند حدوث حدث معين
DisplayFunc() يتم مناداته لتحديث الشاشة
ReshapeFunc(w,h) عند تغيير حجم النافذة
KeyboardFunc(key,x,y) عند الضغط على لوحة الفاتيح
SpecialFunc(key,x,y) عند الضغط على الأزرار الخاصة في لوحة الفاتيح مثل الأسهم
MouseFunc(btn,state,x,y) النقر بالفأرة على موقع معين
MotionFunc(x,y) تحريك الفأرة
IdleFunc() يتم تنفيذه بشكل متكرر عندما لا يعمل شيء
TimerFunc(value) يتم تنفيذه بشكل متكرر خلال فترات محددة
يتم تحديدها باستعمال

glutDisplayFunc(my_display);
glutReshapeFunc(my_resize);
glutKeyboardFunc(my_key);
glutSpecialFunc(my_key2);
glutMouseFunc(my_mouse);
glutMotionFunc(my_mouse2);
glutIdleFunc(my_idle);
glutTimerFunc(msec,my_timer,value);
وقد رأينا كيف نستخدم glutDisplayFunc

واذا كنت تتسائل عن الأبعاد التي نرسم فيها، فاعلم أن openGL تستخدم مصفوفتان رئيسيتان الأولى هي GL_PROJECTIION وهي مصفوفة الاسقاط ( عالم الكاميرا) والأخرى هي GL_MODELVIEW والتي تحدد عالم المجسم نفسه وقبل أن نحددهما نستدعي glViewport(0,0,w,h); وبذلك نخبره بأن لايرسم خارج هذه الحدود ذات البعدين وتعتبر النقطة 0,0 هي النقطة السفلى اليسرى. الآن نحدد المصفوفة التي نريد التعامل معها وهي مصفوفة الإسقاط (الكاميرا) بأمر glMatrixMode(GL_PROJECTIION); ونخبره باستعمال المصفوفة المحايدة ب glLoadIdentity(); ثم نحدد الأبعاد التي نريد glOrtho(minX,maxX,minY,maxY,minZ,maxZ); مثلا glOrtho(-25,25,-25,25,-25,25); تحدد مكعب مركزه 0,0,0 وطوله 25 لترسم بداخله. ولكن هذا الكلام يبقى صحيحا إلى أن تغيير حجم النافذة لذا يجب أن تضع مثل هذا الكود داخل ReshapeFunc الذي أسميا my_resize ، ويمكنك استبدال glOrtho ب glFrustum أو gluPerspective ثم ننتقل إلى طور المجسم ب glMatrixMode(GL_MODELVIEW); ونبدأ بالرسم ولتغيير موضع الكاميرا يمكنك استدعاء gluLookAt(camX,camY,camZ,atX,atY,atZ,0,1,0); ويمكن تطبيق عمليات المصفوفة على كلتا المصفوفتين من ازاحة بمقدار x,y,z من خلال glTransltef(x,y,z); وتكبير من خلال glScalef(x,y,z); وادارة بمقدار deg درجة حول المحور x,y,z ب glRotatef(deg,x,y,z); هذا مثال يوضح ذلك

// include the GL lib's
#include<GL/gl.h>
#include<GL/glu.h>
#include<GL/glut.h>
int W,H;
// the 8 verteces of a cube of ide lenght 40
float vertices[][3]={
	{-20,-20, 20},{20,-20, 20},{20,20, 20},{-20,20, 20},
	{-20,-20,-20},{20,-20,-20},{20,20,-20},{-20,20,-20}
}
// the indices to form the cube
unsigned int indeces[24]={ /* 24 = 4 points in 6 sides */
	0,1,2,3, /* the 1st side by connect 0-1-2-3-0 */
	1,5,6,2,
	4,5,6,7,
	4,0,3,7,
	3,2,6,7,
	0,1,5,4
}
float colors[6][3]={
	{1.0,0.0,0.0},{0.0,1.0,0.0},
	{0.0,0.0,1.0},{1.0,1.0,0.0},
	{0.0,1.0,1.0},{1.0,0.0,1.0}
}

void my_resize(int w,int h) {
	glViewport(0,0,w,h);
	glMatrixMode(GL_PROJECTIION);
	glLoadIdentity();
	glOrtho(-25,25,-25,25,-25,25);
	glMatrixMode(GL_MODLEVIEW);
	W=w; H=h; 
}
void my_display() {
	int i,j,k=0;
	glClearColor(1.0,1.0,1.0,1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBegin(GL_QUADS)
		for (int i=0;i<6;++i) {
		   glColor3fv(colors[i]);
		   for (int j=0;j<4;++j,++k)
		   glVertex3fv(vertecs[indeces[k]]);
		}
	glEnd();
	glFlush();
	glutSwapBuffers();
}
int main( int argc,char *argv[]) {
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
	glutInitWindowPosition(0,0);
	glutInitWindowSize(320,240);
	glutCreateWindow("My 1st openGL");
	glEnable(GL_DEPTH_TEST);
	glutDisplayFunc( my_display );
	glutReshapeFunc( my_resize );
	glutMainLoop();
	
	// return success	
	return 0;
}

glFrustum(minX,maxX,minY,maxY,minZ,maxZ);
gluPerspective(fovY,aspct,ner,far);

float ptr[16]={...} // matrix
glLoadMatrix(ptr);
glMultMatrix(ptr); // Current*ptr (from the right)
glPushMatrix();
glPopMatrix();
--
glNormal3fv(ptr);
glShadeMode(GL_FLAT);
--
glEnableClientState(GL_VERTEX_ARRAY); //GL_COLOR_ARRAY & GL_NORMAL_ARRAY
glVertexPointer(3,GL_FLOAT,0,ptr); // 3 for xyz
glColorPointer(3,GL_FLOAT,0,ptr);
glNormalPointer(GL_FLOAT,0,ptr);
glDrawElements(GL_GUADS,indN,GL_UNSIGNED_INTEGER,ind_ptr);
--
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
float lpos={x,y,z,1.0};
float ldir={x,y,z,0.0};
float diff={1,0,0,1};
float amb ={1,0,0,1};
float spc={1,1,1,1};
glLightfv(GL_LIGHT0,GL_POSITION,lpos);
glLightfv(GL_LIGHT0,GL_DIRECTION,ldir);
glLightfv(GL_LIGHT0,GL_AMBIENT,amb);
glLightfv(GL_LIGHT0,GL_DIFFUSE,diff);
glLightfv(GL_LIGHT0,GL_SPECULAR,spc);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,global_amb_ptr);
glLightModelfv(GL_LIGHT_MODEL_DIFFUSE,global_diff_ptr);
glLightModelfv(GL_LIGHT_MODEL_SPECULAR,global_spc_ptr);
glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,amb_ptr);

<< السابق كتاب لينكس الشامل التالي >>