برنامج OpenGL التعليمي لنظام Android: إنشاء مولد مجموعة Mandelbrot

نشرت: 2022-03-11

OpenGL عبارة عن واجهة برمجة تطبيقات قوية متعددة الأنظمة الأساسية تسمح بوصول قريب جدًا إلى أجهزة النظام في مجموعة متنوعة من بيئات البرمجة.

لذا ، لماذا يجب أن تستخدمه؟

يوفر معالجة منخفضة المستوى للغاية للرسومات ثنائية وثلاثية الأبعاد. بشكل عام ، سيؤدي ذلك إلى تجنب أي أخطاء نمتلكها بسبب لغات البرمجة المفسرة أو عالية المستوى. والأهم من ذلك ، أنه يوفر أيضًا وصولاً على مستوى الأجهزة إلى ميزة رئيسية: GPU.

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

مجموعة مولدات OpenGL و Mandelbrot

في هذه المقالة ، سنبني تطبيق Android بسيطًا يستخدم OpenGL لعرض محتواه على الشاشة. قبل أن نبدأ ، من المهم أن تكون على دراية بمعرفة كتابة تطبيقات Android وبناء جملة بعض لغات البرمجة المشابهة لـ C. الكود المصدري الكامل لهذا البرنامج التعليمي متاح على GitHub.

برنامج OpenGL التعليمي و Android

لإثبات قوة OpenGL ، سنكتب تطبيقًا أساسيًا نسبيًا لجهاز Android. الآن ، يتم توزيع OpenGL على Android ضمن مجموعة فرعية تسمى OpenGL للأنظمة المضمنة (OpenGL ES). يمكننا بشكل أساسي التفكير في هذا على أنه نسخة مجردة من OpenGL ، على الرغم من أن الوظائف الأساسية المطلوبة ستظل متاحة.

بدلاً من كتابة "Hello World" أساسي ، سنقوم بكتابة تطبيق بسيط مخادع: مولد مجموعة Mandelbrot. تستند مجموعة Mandelbrot في مجال الأعداد المركبة. يعد التحليل المعقد مجالًا شاسعًا بشكل جميل ، لذلك سنركز على النتيجة المرئية أكثر من الرياضيات الفعلية التي تقف وراءها.

باستخدام OpenGL ، يعد إنشاء مولد مجموعة Mandelbrot أسهل مما تعتقد!
سقسقة

دعم الإصدار

دعم إصدار opengl

عندما نقوم بإعداد التطبيق ، نريد التأكد من توزيعه فقط على أولئك الذين لديهم دعم OpenGL مناسب. ابدأ بالإعلان عن استخدام OpenGL 2.0 في ملف البيان ، بين إعلان البيان والتطبيق:

 <uses-feature android:glEsVersion="0x00020000" android:required="true" />

في هذه المرحلة ، يتوفر دعم OpenGL 2.0 في كل مكان. تكتسب OpenGL 3.0 و 3.1 توافقًا ، لكن الكتابة لأي منهما ستتجاهل ما يقرب من 65٪ من الأجهزة ، لذلك اتخذ القرار فقط إذا كنت متأكدًا من أنك ستحتاج إلى وظائف إضافية. يمكن تنفيذها عن طريق تعيين الإصدار على "0x000300000" و "0x000300001" على التوالي.

هندسة التطبيق

بنية تطبيق opengl

عند إنشاء تطبيق OpenGL هذا على Android ، سيكون لديك بشكل عام ثلاث فئات رئيسية تُستخدم لرسم السطح: MainActivity الخاص بك ، وامتداد GLSurfaceView ، وتنفيذ GLSurfaceView.Renderer . من هناك ، سنقوم بإنشاء نماذج مختلفة تغلف الرسومات.

MainActivity ، المسمى FractalGenerator في هذا المثال ، هو في الأساس مجرد إنشاء مثيل لـ GLSurfaceView وتوجيه أي تغييرات عامة أسفل الخط. إليك مثالًا سيكون في الأساس رمزك المعياري:

 public class FractalGenerator extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Create and set GLSurfaceView mGLView = new FractalSurfaceView(this); setContentView(mGLView); } //[...] @Override protected void onPause() { super.onPause(); mGLView.onPause(); } @Override protected void onResume() { super.onResume(); mGLView.onResume(); } }

سيكون هذا أيضًا هو الفصل الذي تريد وضع أي معدلات أخرى لمستوى النشاط فيه ، (مثل ملء الشاشة الغامرة).

فئة واحدة أعمق ، لدينا امتداد GLSurfaceView ، والذي سيكون بمثابة العرض الأساسي لدينا. في هذه الفئة ، قمنا بتعيين الإصدار ، وإعداد Renderer ، والتحكم في أحداث اللمس. في المُنشئ الخاص بنا ، نحتاج فقط إلى تعيين إصدار OpenGL مع setEGLContextClientVersion(int version) وإنشاء العارض الخاص بنا وتعيينه أيضًا:

 public FractalSurfaceView(Context context){ super(context); setEGLContextClientVersion(2); mRenderer = new FractalRenderer(); setRenderer(mRenderer); }

بالإضافة إلى ذلك ، يمكننا تعيين سمات مثل وضع التقديم باستخدام setRenderMode(int renderMode) . نظرًا لأن إنشاء مجموعة Mandelbrot يمكن أن يكون مكلفًا للغاية ، RENDERMODE_WHEN_DIRTY ، والذي سيعرض المشهد فقط عند التهيئة وعندما يتم إجراء مكالمات صريحة إلى requestRender() . يمكن العثور على المزيد من خيارات الإعدادات في واجهة برمجة تطبيقات GLSurfaceView .

بعد أن نحصل على المُنشئ ، سنريد على الأرجح تجاوز طريقة أخرى واحدة على الأقل: onTouchEvent(MotionEvent event) ، والتي يمكن استخدامها لإدخال المستخدم العام القائم على اللمس. لن أخوض في الكثير من التفاصيل هنا ، لأن هذا ليس المحور الرئيسي للدرس.

أخيرًا ، نصل إلى Renderer الخاص بنا ، والذي سيكون المكان الذي تحدث فيه معظم أعمال الإضاءة أو ربما التغييرات في المشهد. أولاً ، سيتعين علينا النظر قليلاً في كيفية عمل المصفوفات وكيفية عملها في عالم الرسومات.

درس سريع في الجبر الخطي

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

توفر فئة Matrix التي يوفرها OpenGL عددًا من الطرق الجاهزة لحساب المصفوفات التي سنحتاجها ، لكن فهم كيفية عملها يعد فكرة ذكية حتى عند العمل باستخدام عمليات تحويل بسيطة.

أولًا ، يمكننا أن نتحدث عن سبب استخدام متجهات ومصفوفات رباعية الأبعاد للتعامل مع الإحداثيات. يعود هذا في الواقع إلى فكرة تحسين استخدامنا للإحداثيات لتكون قادرًا على القيام بالترجمات: بينما تكون الترجمة في مساحة ثلاثية الأبعاد مستحيلة باستخدام ثلاثة أبعاد فقط ، فإن إضافة بُعد رابع يتيح القدرة.

لتوضيح ذلك ، يمكننا استخدام مقياس عام / مصفوفة ترجمة أساسية للغاية:

مقياس opengl ومصفوفة الترجمة

كملاحظة مهمة ، تكون مصفوفات OpenGL عمودية ، لذلك ستتم كتابة هذه المصفوفة كـ {a, 0, 0, 0, 0, b, 0, 0, 0, 0, c, 0, v_a, v_b, v_c, 1} ، وهو عمودي على كيفية قراءته عادةً. يمكن تبرير ذلك عن طريق التأكد من أن المتجهات ، التي تظهر في الضرب كعمود ، لها نفس تنسيق المصفوفات.

العودة إلى المدونة

مسلحين بهذه المعرفة بالمصفوفات ، يمكننا العودة إلى تصميم العارض الخاص بنا. عادة ، سننشئ مصفوفة في هذه الفئة تتكون من منتج ثلاث مصفوفات: النموذج والعرض والإسقاط. يمكن أن يسمى هذا ، بشكل مناسب ، MVPMatrix. يمكنك معرفة المزيد حول التفاصيل هنا ، حيث سنستخدم مجموعة أساسية أكثر من التحولات - مجموعة Mandelbrot عبارة عن نموذج ملء الشاشة ثنائي الأبعاد ، ولا يتطلب حقًا فكرة الكاميرا.

أولاً ، لنقم بإعداد الفصل. سنحتاج إلى تنفيذ الطرق المطلوبة لواجهة العارض: onSurfaceCreated(GL10 gl, EGLConfig config) ، onSurfaceChanged(GL10 gl, int width, int height) ، و onDrawFrame(GL10 gl) . سينتهي الفصل الكامل بالبحث عن شيء مثل هذا:

 public class FractalRenderer implements GLSurfaceView.Renderer { //Provide a tag for logging errors private static final String TAG = "FractalRenderer"; //Create all models private Fractal mFractal; //Transformation matrices private final float[] mMVPMatrix = new float[16]; //Any other private variables needed for transformations @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //Instantiate all models mFractal = new Fractal(); } @Override public void onDrawFrame(GL10 unused) { //Clear the frame of any color information or depth information GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); //Create a basic scale/translate matrix float[] mMVPMatrix = new float[]{ -1.0f/mZoom, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f/(mZoom*mRatio), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -mX, -mY, 0.0f, 1.0f}; //Pass the draw command down the line to all models, giving access to the transformation matrix mFractal.draw(mMVPMatrix); } @Override public void onSurfaceChanged(GL10 unused, int width, int height) { //Create the viewport as being fullscreen GLES20.glViewport(0, 0, width, height); //Change any projection matrices to reflect changes in screen orientation } //Other public access methods for transformations }

هناك أيضًا طريقتان من الأدوات المساعدة المستخدمة في الكود المقدم ، checkGLError و loadShaders للمساعدة في تصحيح الأخطاء واستخدام التظليل.

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

في فئات النموذج ، نكتب الفصل بأكمله - لا توجد فئات فائقة يجب استخدامها. نحتاج فقط إلى مُنشئ ونوع من طريقة الرسم التي تأخذ أي معلمات فيها.

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

 public Fractal() { // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (# of coordinate values * 4 bytes per float) squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); // initialize byte buffer for the draw list ByteBuffer dlb = ByteBuffer.allocateDirect( // (# of coordinate values * 2 bytes per short) drawOrder.length * 2); dlb.order(ByteOrder.nativeOrder()); drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); // Prepare shaders int vertexShader = FractalRenderer.loadShader( GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = FractalRenderer.loadShader( GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // create empty OpenGL Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // add the fragment shader to program GLES20.glAttachShader(mProgram, fragmentShader); // create OpenGL program executables GLES20.glLinkProgram(mProgram); }

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

لمناقشة أجزاء من هذا ، دعنا نلقي نظرة على بعض التصريحات المتغيرة:

 static float squareCoords[] = { -1.0f, 1.0f, 0.0f, // top left -1.0f, -1.0f, 0.0f, // bottom left 1.0f, -1.0f, 0.0f, // bottom right 1.0f, 1.0f, 0.0f }; // top right private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

في squareCoords ، نحدد جميع إحداثيات المربع. لاحظ أن جميع الإحداثيات على الشاشة ممثلة في شكل شبكة مع (-1,-1) في أسفل اليسار و (1,1) في أعلى اليمين.

في drawOrder ، نحدد ترتيب الإحداثيات بناءً على المثلثات التي يتكون منها المربع. يستخدم برنامج OpenGL المثلثات لتمثيل جميع الأسطح ، خاصة من أجل الاتساق والسرعة. لعمل مربع ، قم ببساطة بقطع قطري (في هذه الحالة ، 0 إلى 2 ) لإعطاء مثلثين.

من أجل إضافة كلاهما إلى البرنامج ، يجب عليك أولاً تحويلهما إلى مخزن مؤقت للبايت الخام لربط محتويات المصفوفة بواجهة OpenGL مباشرة. تقوم Java بتخزين المصفوفات ككائنات تحتوي على معلومات إضافية غير متوافقة بشكل مباشر مع مصفوفات C القائمة على المؤشر والتي يستخدمها تنفيذ OpenGL. لعلاج هذا ، يتم استخدام ByteBuffers ، والتي تخزن الوصول إلى الذاكرة الأولية للمصفوفة.

بعد أن نضع البيانات الخاصة بالرؤوس وترسم النظام ، يجب علينا إنشاء التظليل الخاص بنا.

شادر

عند إنشاء نموذج ، يجب عمل تظليلين: Vertex Shader و Fragment (Pixel) Shader. تتم كتابة جميع التظليل بلغة GL التظليل (GLSL) ، وهي لغة تستند إلى لغة C مع إضافة عدد من الوظائف المضمنة والمعدلات المتغيرة والأوليات والإدخال / الإخراج الافتراضي. على Android ، سيتم تمرير هذه كسلسلة نصية نهائية من خلال loadShader(int type, String shaderCode) ، إحدى طريقتين للمورد في العارض. دعنا أولاً نتناول الأنواع المختلفة من التصفيات:

  • const : يمكن الإعلان عن أي متغير نهائي على أنه ثابت حتى يمكن تخزين قيمته لسهولة الوصول إليه. يمكن الإعلان عن الأعداد مثل π كثوابت إذا تم استخدامها بشكل متكرر في جميع أنحاء التظليل. من المحتمل أن يقوم المترجم تلقائيًا بالإعلان عن القيم غير المعدلة على أنها ثوابت ، اعتمادًا على التنفيذ.
  • uniform : المتغيرات الموحدة هي المتغيرات التي يتم الإعلان عنها بأنها ثابتة لأي عرض فردي. يتم استخدامها بشكل أساسي كحجج ثابتة لأداة التظليل الخاصة بك.
  • varying : إذا تم الإعلان عن متغير على أنه متغير وتم تعيينه في تظليل قمة الرأس ، فسيتم استكماله خطيًا في تظليل الجزء. هذا مفيد لإنشاء أي نوع من التدرج اللوني وهو ضمني لتغييرات العمق.
  • attribute : يمكن اعتبار السمات على أنها وسيطات غير ثابتة للتظليل. تشير إلى مجموعة المدخلات الخاصة بالرأس وستظهر فقط في Vertex Shaders.

بالإضافة إلى ذلك ، يجب أن نناقش نوعين آخرين من الأوليات التي تمت إضافتها:

  • vec2 ، vec3 ، vec4 : متجهات الفاصلة العائمة ذات البعد المحدد.
  • mat2 ، mat3 ، mat4 : مصفوفات الفاصلة العائمة ذات البعد المحدد.

يمكن الوصول إلى المتجهات بواسطة مكوناتها x و y و z و w أو r و g و b و a . يمكنهم أيضًا إنشاء متجه أي حجم بمؤشرات متعددة: بالنسبة لـ vec3 a ، تُرجع a.xxyz vec4 مع القيم المقابلة لـ a .

يمكن أيضًا فهرسة المصفوفات والمتجهات كمصفوفات ، وستقوم المصفوفات بإرجاع متجه بمكون واحد فقط. هذا يعني أنه بالنسبة mat2 matrix ، matrix[0].a صالحة وستُرجع المصفوفة matrix[0][0] . عند العمل مع هؤلاء ، تذكر أنهم يعملون كأوليات وليس ككائنات. على سبيل المثال ، ضع في اعتبارك الكود التالي:

 vec2 a = vec2(1.0,1.0); vec2 b = a; bx=2.0;

هذا يترك a=vec2(1.0,1.0) و b=vec2(2.0,1.0) ، وهو ما لا يتوقعه المرء من سلوك الكائن ، حيث يعطي السطر الثاني b مؤشرًا إلى a .

في مجموعة Mandelbrot ، ستكون غالبية الكود في تظليل الأجزاء ، وهو التظليل الذي يعمل على كل بكسل. اسميًا ، تعمل تظليل الرأس على كل رأس ، بما في ذلك السمات التي ستكون على أساس كل قمة ، مثل التغييرات في اللون أو العمق. دعنا نلقي نظرة على تظليل قمة الرأس البسيط للغاية للفركتلات:

 private final String vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}";

في هذا ، gl_Position هو متغير الإخراج المحدد بواسطة OpenGL لتسجيل إحداثيات الرأس. في هذه الحالة ، نمرر في موضع لكل رأس نضع gl_Position . في معظم التطبيقات ، نضرب vPosition في MVPMatrix ، ونحول الرؤوس ، لكننا نريد أن يكون الفركتل دائمًا في وضع ملء الشاشة. سيتم إجراء جميع التحولات باستخدام نظام إحداثيات محلي.

سيكون The Fragment Shader هو المكان الذي يتم فيه تنفيذ معظم العمل لإنشاء المجموعة. سنقوم بتعيين fragmentShaderCode على ما يلي:

 precision highp float; uniform mat4 uMVPMatrix; void main() { //Scale point by input transformation matrix vec2 p = (uMVPMatrix * vec4(gl_PointCoord,0,1)).xy; vec2 c = p; //Set default color to HSV value for black vec3 color=vec3(0.0,0.0,0.0); //Max number of iterations will arbitrarily be defined as 100. Finer detail with more computation will be found for larger values. for(int i=0;i<100;i++){ //Perform complex number arithmetic p= vec2(px*px-py*py,2.0*px*py)+c; if (dot(p,p)>4.0){ //The point, c, is not part of the set, so smoothly color it. colorRegulator increases linearly by 1 for every extra step it takes to break free. float colorRegulator = float(i-1)-log(((log(dot(p,p)))/log(2.0)))/log(2.0); //This is a coloring algorithm I found to be appealing. Written in HSV, many functions will work. color = vec3(0.95 + .012*colorRegulator , 1.0, .2+.4*(1.0+sin(.3*colorRegulator))); break; } } //Change color from HSV to RGB. Algorithm from https://gist.github.com/patriciogonzalezvivo/114c1653de9e3da6e1e3 vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 m = abs(fract(color.xxx + K.xyz) * 6.0 - K.www); gl_FragColor.rgb = color.z * mix(K.xxx, clamp(m - K.xxx, 0.0, 1.0), color.y); gl_FragColor.a=1.0; }

جزء كبير من الكود هو مجرد الرياضيات والخوارزمية لكيفية عمل المجموعة. لاحظ استخدام العديد من الوظائف المضمنة: fract ، و abs ، و mix ، و sin ، و clamp ، والتي تعمل جميعها على المتجهات أو الحجميات والعودة إلى النواقل أو الحجميات. بالإضافة إلى ذلك ، يتم استخدام dot التي تأخذ وسيطات متجهة وترجع حجميًا.

الآن بعد أن تم إعداد أدوات التظليل الخاصة بنا للاستخدام ، لدينا خطوة أخيرة ، وهي تنفيذ وظيفة draw في نموذجنا:

 public void draw(float[] mvpMatrix) { // Add program to OpenGL environment GLES20.glUseProgram(mProgram); // get handle to vertex shader's vPosition member mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); //Pass uniform transformation matrix to shader GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); //Add attribute array of vertices GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer( mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // Draw the square GLES20.glDrawElements( GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); FractalRenderer.checkGlError("Test"); }

تمرر الوظيفة جميع الوسائط إلى التظليل ، بما في ذلك uniform التحويل المنتظمة وموضع attribute .

بعد تجميع جميع أجزاء البرنامج ، يمكننا أخيرًا تشغيله. شريطة أن يتم التعامل مع دعم اللمس المناسب ، سيتم رسم مشاهد ساحرة تمامًا:

برنامج Opengl التعليمي

برنامج Opengl Mandelbrot التعليمي

البرنامج التعليمي مولد ماندلبروت

دقة النقطة العائمة

إذا قمنا بالتكبير قليلاً ، نبدأ في ملاحظة انهيار في الصورة:

هذا لا علاقة له على الإطلاق بحسابات المجموعة التي تقف خلفه وكل ما يتعلق بالطريقة التي يتم بها تخزين الأرقام ومعالجتها في OpenGL. في حين تم تقديم دعم أحدث للدقة double ، فإن OpenGL 2.0 لا يدعم في الأصل أي شيء أكثر من float s. لقد قمنا بتخصيصها على وجه التحديد لتكون عوامات عالية الدقة متوفرة مع precision highp float في التظليل ، ولكن حتى هذا ليس جيدًا بما فيه الكفاية.

للتغلب على هذه المشكلة ، فإن الطريقة الوحيدة هي محاكاة double s باستخدام float s. تأتي هذه الطريقة في الواقع ضمن ترتيب حجم الدقة الفعلية لطريقة مطبقة محليًا ، على الرغم من وجود تكلفة باهظة إلى حد ما للسرعة. سيتم ترك هذا كتمرين للقارئ ، إذا رغب المرء في الحصول على مستوى أعلى من الدقة.

خاتمة

مع عدد قليل من فئات الدعم ، يمكن لبرنامج OpenGL أن يحافظ بسرعة على عرض الوقت الفعلي للمشاهد المعقدة. إن إنشاء مخطط يتكون من GLSurfaceView ، وتعيين Renderer الخاص به ، وإنشاء نموذج مع تظليل ، كل ذلك بلغ ذروته في تصور بنية رياضية جميلة. آمل أن تجد نفس القدر من الاهتمام بتطوير تطبيق OpenGL ES!