English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
OpenGL ES هو مجموعة فرعية من API OpenGL ثلاثي الأبعاد، مُصمم للهواتف المحمولة، PDAs وأجهزة الألعاب المنزلية وغيرها من الأجهزة المتكاملة. Ophone يدعم حاليًا OpenGL ES 1.0، OpenGL ES 1.0 مبنية على معيار OpenGL 1.3، OpenGL ES 1.1 مبنية على معيار OpenGL 1.5. يهدف هذا المقال إلى تقديم الخطوات الأساسية لرسم الرسوم باستخدام OpenGL ES.
يتكون محتوى هذا المقال من ثلاثة أجزاء. أولاً، الحصول على واجهة البرمجة لـ OpenGL ES من خلال EGL؛ ثانياً، شرح مفاهيم أساسية لبناء برنامج 3D؛ وأخيراً، مثال على تطبيق.
OpenGL ES هو في الأساس وحدة تحكم حالة لخطوة الرسوم، بينما EGL هو الطبقة الخارجية المستخدمة للمراقبة هذه الحالات والحفاظ على حافظة الصور وأوجه الرسوم الأخرى. الشكل 1 هو رسم توضيحي لنظام EGL. تصميم EGL لمنصة النافذة يعتمد على واجهات OpenGL المألوفة المستخدمة على Microsoft Windows (WGL) و UNIX (GLX)، ويقاربها. يتم تخزين حالة خطوة الرسوم لـ OpenGL ES في سياق إدارة EGL. يتم إنشاء وإدارة وإزالة حافظة الصور وأوجه الرسوم الرسومية عبر API EGL. كما يتحكم EGL ويقدم الوصول إلى عرض الأجهزة والتعديلات المحتملة على تكوين الرسوم.
الشكل 1
يحتاج OpenGL ES إلى سياق الرسوم وواجهة الرسوم. يحتوي سياق الرسوم على معلومات حالة OpenGL ES، ويستخدم واجهة الرسوم للرسم. قبل كتابة OpenGL ES يجب إجراء العمليات التالية باستخدام EGL:
استعلام الأجهزة عن معرفات العرض الداعمة وإعدادها.
إنشاء واجهة الرسوم، رسم رسوم OpenGL ES.
إنشاء سياق الرسوم. يتطلب EGL إنشاء سياق OpenGL ES للتواصل مع واجهة الرسوم.
في Ophone يشمل EGL أربعة كلاسات، وهي EGLDisplay: معرف العرض، EGLConfig: فئة التكوين، EGLContext: فئة سياق الرسوم، وفئة EGLSurface: فئة العرض القابل للرسم.
يمكن اعتبار EGL كطبقة وسط بين OpenGL ES ونظام النوافذ المحلي. نظام النوافذ المحلي يشير إلى نظام النوافذ X على GNU/Linux أو Quartz على Mac OS X. قبل أن يحدد EGL نوع واجهة الرسوم، يجب أن يتواصل EGL مع نظام النوافذ الأساسي. لأن أنواع النوافذ المختلفة على أنظمة التشغيل المختلفة تختلف، يقدم EGL نوع نوافذ شفاف، وهو EGLDisplay. إنه ي抽象 أنواع مختلفة من أنظمة النوافذ. لذا يجب أولاً إنشاء وتحديث مثيل EGLDisplay.
// طريقة ثابتة لـ EGLContext للحصول على مثيل EGL EGL10 egl = (EGL10)EGLContext.getEGL(); // إنشاء EGLDisplay، حيث يحصل EGL_DEFAULT_DISPLAY على نوع النظام المحلي للنوافذ المفضل EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); // يتم الحصول على رقم إصدار EGL عند تحفيز EGLDisplay int[] version = new int[2]; egl.eglInitialize(dpy, version);
كل EGLDisplay يجب تحفيزها قبل الاستخدام. يمكن الحصول على إصدار تنفيذ EGL في نفس الوقت الذي يتم فيه تحفيز EGLDisplay. من خلال إصدار النسخة، يمكن كتابة برنامج متوافق بشكل جيد من خلال استخدام API OpenGL ES المناسب، مما يمكن من التكيف مع المزيد من الأجهزة وتقديم أقصى قدر من قابلية التحويل.
boolean eglInitialize(EGLDisplay display, int[] major_minor)
الـdisplay هو نموذج صالح من EGLDisplay. عند إكمال الاستدعاء للوظيفة، سيتم تخصيص major_minor لرقم إصدار EGL الحالي. مثل EGL1.0، major_minor[0] هو 1، و major_minor[1] هو 0. يحتوي EGLSurface على جميع المعلومات المتعلقة بوجهة الرسوم EGL. هناك طريقتان لاستعلام معلومات تكوين EGLSurface، الأولى هي استعلام جميع معلومات التكوين والاختيار من بينها الأنسب؛ والثانية هي تحديد معلومات التكوين مسبقًا، ويقدم النظام أفضل تطابق. عادة ما يتم استخدام الطريقة الثانية. يمكن للمستخدم من خلال specSpec تحديد التكوينات التي يرغب في الحصول عليها، وتقوم الوظيفة eglChooseConfig بتقديم قائمة أفضل تكوينات من خلال المعامل Configs. بعد الحصول على Configs، يتم استدعاء eglCreateContext لإنشاء سياق الرسوم، وتقوم هذه الوظيفة بتقديم هيكل EGLContext. يتم إنشاء واجهة الرسوم EGLSurface من خلال وظيفة eglCreateWindowSurface. يمكن لتطبيق واحد إنشاء عدة EGLContext. يتم ربط سياق الرسوم المحدد بوجهة الرسوم باستخدام eglMakeCurrent. تستخدم وظائف الاستعلام eglGetCurrentContext، eglGetCurrentDisplay و eglGetCurrentSurface للحصول على سياق الرسوم الحالي، معالج العرض الحالي ووجهة الرسوم الحالية في النظام. في النهاية، تقدم طريقة الاستدعاء الثابتة لـ EGLContext getGL واجهة البرمجة OpenGL ES. يلخص هذا الجزء من البرنامج المذكور أعلاه.
EGL10 egl = (EGL10)EGLContext.getEGL(); EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2]; egl.eglInitialize(dpy, version); int[] configSpec = { EGL10.EGL_RED_SIZE, 5, EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5, EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE ; EGLConfig[] configs = new EGLConfig[1]; int[] num_config = new int[1]; egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config); EGLConfig config = configs[0]; EGLContext context = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, null); EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, sHolder, null); egl.eglMakeCurrent(dpy, surface, surface, context); GL10 gl = (GL10)context.getGL();
نقاط بناء الرسومات ثلاثية الأبعاد
النقطة هي الأساس لبناء نموذج ثلاثي الأبعاد. الحسابات الداخلية لـ OpenGL ES تعتمد على النقاط. يمكن أيضًا استخدام النقاط لتقديم مواقع المصادر الضوئية، ومواقع الأجسام. عادة ما نستخدم مجموعة من الأعداد العشر لتقديم النقطة. على سبيل المثال، يمكن تمثيل زوايا مربع من خلال أربعة نقاط:
float vertices[] = { -1.0f, 1.0f, 0.0f, //左上 -1.0f, -1.0f, 0.0f, //左下 1.0f, -1.0f, 0.0f, //右下 1.0f, 1.0f, 0.0f, //右上 ;
لتحسين الأداء، يجب حفظ مصفوفة الأعداد العشر في مصفوفة بايتة. لذا، هناك عملية كما يلي:
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); FloatBuffer vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0);
ByteOrder.nativeOrder() هو الحصول على ترتيب الأحرف المحلي. لدي OpenGL ES وظائف لتشغيل خطوط الرسم الرسومية، في الحالة الافتراضية، حالة استخدام هذه الوظائف معطلة. يمكن تشغيل وإيقاف هذه الوظائف باستخدام glEnableClientState وglDisableClientState.
// يتم تحديد الحاجة إلى تشغيل مجموعة النقاط الزاوية
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// يوضح أن نوع المجموعة هو GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// بعد الحاجة، قم بإغلاق مجموعة النقاط الزاوية
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
الجوانب
الجوانب هي الخط الذي يربط بين نقطتين، هي حافة وجه المثلث.
المثلثات
المثلثات هي حلقات مغلقة من الجوانب. يجب أن تكون المثلثات في OpenGL ES مثلثات قوية، أي أن أي نقطتين يتم اختيارها داخل المثلثة، إذا كانت قطعة الخط بينهما في داخل المثلثة، فإن المثلثة هذه هي مثلث قوي. عند رسم المثلثات يجب تحديد اتجاه التشغيل، يوجد اتجاهان - الساعة العكسية للساعة واتجاه الساعة. يحدد الاتجاه اتجاه المثلثة، أي الوجه الأمامي والخلفي. يمكن تجنب الرسم للأجزاء المغطاة لتحسين أداء البرنامج.
// إعداد اتجاه CCW كـ "الوجه الأمامي"، CCW هو CounterClockWise، الساعة العكسية
glFrontFace(GL_CCW);
// إعداد اتجاه CW كـ "الوجه الأمامي"، CW هو ClockWise، الساعة العكسية للساعة
glFrontFace(GL_CW);
التشغيل
بعد شرح هذه المفاهيم، الآن يجب علينا القيام بأهم العمل - التشغيل. التشغيل يتحول المكونات المحددة للجسم بالتوازي إلى صورة في منطقة التخزين للإطارات. الصورة والنقاط الزاوية لها علاقة وثيقة. هذه العلاقة تتم من خلال نمط الرسم. من المراتب الشائعة في الرسم GL_POINTS،GL_LINE_STRIP،
GL_LINE_LOOP،GL_LINES،GL_TRIANGLES،GL_TRIANGLE_STRIP،GL_TRIANGLE_FAN. سنتحدث عن كل منها.
GL_POINTS: كل نقطة زاوية تعتبر نقطة مستقلة، نقطة n تعتبر نقطة n، يتم رسم n نقطة.
GL_LINES: كل نقطة زاوية تعتبر قطعة خط مستقلة، بين نقطة 2n-1 ونقطة 2n يتم تعريف n قطع خط، يتم رسم N/2 قطعة خط. إذا كان N عددًا فرديًا، يتم تجاهل النقطة الأخيرة.
GL_LINE_STRIP: يرسم مجموعة من الخطوط المتصلة من القمة الأولى إلى القمة الأخيرة، يتم تحديد الخط nth من القمم nth وn+1، ويُرسم N-1 خطًا بالكامل.
GL_LINE_LOOP: يرسم مجموعة من الخطوط المتصلة من القمة الأولى إلى القمة الأخيرة، ثم يرسم القمة الأخيرة مع القمة الأولى. يتم تحديد الخط nth من القمم nth وn+1، ثم يتم تحديد الخط الأخير من القمة N و1، ويُرسم N خطًا بالكامل.
GL_TRIANGLES: يتم اعتبار كل ثلاثة قمم كمثلث مستقل. تعرف القمة 3n-2، 3n-1 و3n المثلث nth، ويُرسم N/3 مثلثات بالكامل.
GL_TRIANGLE_STRIP: يرسم مجموعة من المثلثات المتصلة. للنقطة المثلى n، تعرف القمة n، n+1 وn+2 المثلث nth; للنقطة المثلى n، تعرف القمة n+1، n وn+2 المثلث nth، ويُرسم N-2 مثلثات بالكامل.
GL_TRIANGLE_FAN: يرسم مجموعة من المثلثات المتصلة. يتم تحديد المثلث من قبل القمة الأولى والقمم التي تليها. تعرف القمة 1، n+1 وn+2 المثلث nth، ويُرسم N-2 مثلثات بالكامل.
وظائف الرسم:
void glDrawArrays(int mode, int first, int count)
void glDrawElements(int mode, int count, int type, Buffer indices)
يُنشأ glDrawArrays سلسلة من العناصر الجيومترية، باستخدام عناصر الجدول من first إلى first + count - 1، وmode هو نمط الرسم.
يستخدم glDrawElements عددًا count من العناصر لتحديد سلسلة من العناصر، وtype هو نوع البيانات في جدول indices، وmode هو نمط الرسم، ويخزن جدول indices النقاط.
قيمة مؤشر النقطة.
مثال على التطبيق
استخدام المحتوى المقدم أعلاه لإنشاء برنامج رسم كرة ثلاثية الأبعاد على Ophone. الصورة النهائية تبدو كالتالي:
الشكل 2 مثال كروي
البرنامج الرئيسي للرسم:
static private FloatBuffer vertex;//顶点对应的字节缓冲 static private FloatBuffer normal;//法向量对应的字节缓冲 float[] lightPos = new float[] {10.0f, 10.0f, 10.0f, 1.0f };//光源的坐标 private static final int STEP = 24; //}} private static final float RADIUS = 1.0f; // قطر protected void init(GL10 gl) { gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // إعداد لون الخلفية gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0); gl.glEnable(GL10.GL_LIGHTING); // تمكين الإضاءة gl.glEnable(GL10.GL_LIGHT0); // تشغيل مصدر الضوء gl.glClearDepthf(1.0f); // إعداد مخزن العمق gl.glDepthFunc(GL10.GL_LEQUAL); // إعداد وظيفة مقارنة مخزن العمق، GL_LEQUAL تعني أن قيمة عمق البكسل الجديدة أقل أو مساوية لقيمة عمق البكسل الحالية عند إجراء اختبار العمق gl.glEnable(GL10.GL_DEPTH_TEST); // تمكين مخزن العمق gl.glEnable(GL10.GL_CULL_FACE); gl.glShadeModel(GL10.GL_SMOOTH); // إعداد نمط الظلال GL_SMOOTH } protected void drawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); GLU.gluLookAt(gl, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 0.0f); // drawSphere(gl, RADIUS, STEP, STEP); // رسم كرة } public static void gluLookAt (GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
هي تأخذ ثلاث مجموعات من الإحداثيات، وهي eye،center وup. eye تعني موقع عيننا في "نظام الإحداثيات العالمي"، center تعني الإحداثيات للنقطة التي ينظر إليها العين، و up تعني اتجاه المشاهد نفسه، إذا قمنا بمقارنة نقطة الرؤية بمثابة عيننا، فإن up تعني إذا كنا ننظر بصورة مستقيمة أو مقلوبة أو في زاوية معينة، فهنا نحن ننظر بصورة مستقيمة، لذا هي {0،1،0}.
private static void drawSphere(GL10 gl, float radius, int stacks, int slices) { vertex=allocateFloatBuffer( 4* 6 * stacks * (slices+1) ); normal=allocateFloatBuffer( 4* 6 * stacks * (slices+1) ); int i, j, triangles; float slicestep, stackstep; stackstep = ((float)Math.PI) / stacks; slicestep = 2.0f * ((float)Math.PI) / slices; for (i = 0; i < stacks; ++i) { float a = i * stackstep; float b = a + stackstep; float s0 = (float)Math.sin(a); float s1 = (float)Math.sin(b); float c0 = (float)Math.cos(a); float c1 = (float)Math.cos(b); float nv; for (j = 0; j <= slices; ++j) { float c = j * slicestep; float x = (float)Math.cos(c); float y = (float)Math.sin(c); nv=x * s0; normal.put(nv); vertex.put( nv * radius); nv=y * s0; normal.put(nv); vertex.put( nv * radius); nv=c0; normal.put(nv); vertex.put( nv * radius); nv=x * s1; normal.put(nv); vertex.put( nv * radius); nv=y * s1; normal.put(nv); vertex.put( nv * radius); nv=c1; normal.put(nv); vertex.put( nv * radius); } } normal.position(0); vertex.position(0); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertex); gl.glNormalPointer(GL10.GL_FLOAT, 0, normal); gl.glEnableClientState (GL10.GL_VERTEX_ARRAY); gl.glEnableClientState (GL10.GL_NORMAL_ARRAY); triangles = (slices + 1) * 2; for(i = 0; i < stacks; i++) gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, i * triangles, triangles); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_NORMAL_ARRAY); } private static FloatBuffer allocateFloatBuffer(int capacity){ ByteBuffer vbb = ByteBuffer.allocateDirect(capacity); vbb.order(ByteOrder.nativeOrder()); return vbb.asFloatBuffer(); }
الخلاصة:
هذا المقال يقدم لمحة عامة عن مفاهيم وكيفية رسم الرسوم باستخدام OpenGL ES في Ophone. هناك الكثير من المواد الأخرى في OpenGL ES مثل النصائح، الإضاءة والمواد، المزج، الضباب، الشاشات، الإنعكاس، تحميل نماذج 3D وما إلى ذلك. يمكن استخدام دوال OpenGL ES لرسم تطبيقات الرسوم والواجهات المستخدمة للألعاب.