بعد تحديد الأشكال المراد رسمها باستخدام OpenGL، ربما ترغب في رسمها. أشكال الرسم مع OpenGL ES 2.0 يتطلب رمزًا أكبر قليلاً مما قد تتخيل، لأن واجهة برمجة التطبيقات توفر قدرًا كبيرًا من التحكم في مسار عرض الرسومات.
يشرح هذا الدرس كيفية رسم الأشكال التي حددتها في الدرس السابق باستخدام OpenGL ES 2.0 API.
تهيئة الأشكال
قبل أن تقوم بأي رسم، يجب تهيئة وتحميل الأشكال التي تخطط لرسمها. ما لم
يتغير هيكل (الإحداثيات الأصلية) للأشكال التي تستخدمها في برنامجك خلال الدورة
التنفيذ، فينبغي عليك إعدادها
طريقة واحدة (onSurfaceCreated()
) من
عارض لكفاءة الذاكرة والمعالجة.
Kotlin
class MyGLRenderer : GLSurfaceView.Renderer { ... private lateinit var mTriangle: Triangle private lateinit var mSquare: Square override fun onSurfaceCreated(unused: GL10, config: EGLConfig) { ... // initialize a triangle mTriangle = Triangle() // initialize a square mSquare = Square() } ... }
Java
public class MyGLRenderer implements GLSurfaceView.Renderer { ... private Triangle mTriangle; private Square mSquare; public void onSurfaceCreated(GL10 unused, EGLConfig config) { ... // initialize a triangle mTriangle = new Triangle(); // initialize a square mSquare = new Square(); } ... }
رسم شكل
يتطلب رسم شكل محدد باستخدام OpenGL ES 2.0 قدرًا كبيرًا من التعليمات البرمجية، لأنك أن يقدم الكثير من التفاصيل لمسار عرض الرسومات. وعلى وجه التحديد، يجب عليك تحديد التالي:
- Vertex Shader - رمز رسومات OpenGL ES لعرض رؤوس الشكل.
- Fragment Shader - رمز OpenGL ES لعرض وجه شكل باستخدام الألوان أو وزخارفها.
- برنامج - كائن OpenGL ES يحتوي على أدوات التظليل التي تريد استخدامها للرسم شكل واحد أو أكثر.
تحتاج إلى مظلل رأس واحد على الأقل لرسم شكل وتظليل جزء واحد لتلوين هذا الشكل.
ويجب تجميع أدوات التظليل هذه ثم إضافتها إلى برنامج OpenGL ES، الذي يُستخدم بعد ذلك لرسم
الشكل. فيما يلي مثال على كيفية تحديد أدوات التظليل الأساسية التي يمكنك استخدامها لرسم شكل في
صف واحد (Triangle
):
Kotlin
class Triangle { private val vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}" private val fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}" ... }
Java
public class Triangle { private final String vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}"; private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}"; ... }
تحتوي أدوات التظليل على رمز لغة تظليل OpenGL (GLSL) الذي يجب تجميعه قبل استخدامه في بيئة OpenGL ES. لتجميع هذا الرمز، أنشئ طريقة أداة في فئة العارض:
Kotlin
fun loadShader(type: Int, shaderCode: String): Int { // create a vertex shader type (GLES20.GL_VERTEX_SHADER) // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) return GLES20.glCreateShader(type).also { shader -> // add the source code to the shader and compile it GLES20.glShaderSource(shader, shaderCode) GLES20.glCompileShader(shader) } }
Java
public static int loadShader(int type, String shaderCode){ // create a vertex shader type (GLES20.GL_VERTEX_SHADER) // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) int shader = GLES20.glCreateShader(type); // add the source code to the shader and compile it GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; }
لرسم الشكل، يجب تجميع رمز أداة التظليل وإضافتها إلى برنامج OpenGL ES. ثم نربط البرنامج. افعل ذلك في الدالة الإنشائية للكائن المرسوم، بحيث يتم ذلك فقط مرة واحدة.
ملاحظة: إنّ تجميع برامج تظليل OpenGL ES وربط البرامج أمر مُكلف. فيما يتعلق بدورات وحدة المعالجة المركزية ووقت المعالجة، لذا يجب أن تتجنب إجراء ذلك أكثر من مرة. في حال إجراء ذلك محتوى أدوات التظليل في وقت التشغيل، فيجب إنشاء التعليمات البرمجية بحيث يتم إنشاؤها مرة واحدة ثم تخزينها مؤقتًا لاستخدامها لاحقًا.
Kotlin
class Triangle { ... private var mProgram: Int init { ... val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode) val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode) // create empty OpenGL ES Program mProgram = GLES20.glCreateProgram().also { // add the vertex shader to program GLES20.glAttachShader(it, vertexShader) // add the fragment shader to program GLES20.glAttachShader(it, fragmentShader) // creates OpenGL ES program executables GLES20.glLinkProgram(it) } } }
Java
public class Triangle() { ... private final int mProgram; public Triangle() { ... int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // create empty OpenGL ES Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // add the fragment shader to program GLES20.glAttachShader(mProgram, fragmentShader); // creates OpenGL ES program executables GLES20.glLinkProgram(mProgram); } }
في هذه المرحلة، تكون جاهزًا لإضافة المكالمات الفعلية التي ترسم شكلك. رسم الأشكال باستخدام يتطلب OpenGL ES منك تحديد عدة معلَمات لإخبار مسار العرض بما تريده الرسم وكيفية رسمه. نظرًا لأن خيارات الرسم يمكن أن تختلف حسب الشكل، فمن الجيد أن يكون لديك على فئات الأشكال على منطق الرسم الخاص بها.
أنشئ طريقة draw()
لرسم الشكل. تحدد هذه التعليمة البرمجية موضع
قيم اللون إلى مظلل رأس الشكل وتظليل الأجزاء، ثم يتم تنفيذ الرسم
الأخرى.
Kotlin
private var positionHandle: Int = 0 private var mColorHandle: Int = 0 private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex fun draw() { // Add program to OpenGL ES environment GLES20.glUseProgram(mProgram) // get handle to vertex shader's vPosition member positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also { // Enable a handle to the triangle vertices GLES20.glEnableVertexAttribArray(it) // Prepare the triangle coordinate data GLES20.glVertexAttribPointer( it, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer ) // get handle to fragment shader's vColor member mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle -> // Set color for drawing the triangle GLES20.glUniform4fv(colorHandle, 1, color, 0) } // Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount) // Disable vertex array GLES20.glDisableVertexAttribArray(it) } }
Java
private int positionHandle; private int colorHandle; private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex public void draw() { // Add program to OpenGL ES environment GLES20.glUseProgram(mProgram); // get handle to vertex shader's vPosition member positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); // Enable a handle to the triangle vertices GLES20.glEnableVertexAttribArray(positionHandle); // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // get handle to fragment shader's vColor member colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // Set color for drawing the triangle GLES20.glUniform4fv(colorHandle, 1, color, 0); // Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // Disable vertex array GLES20.glDisableVertexAttribArray(positionHandle); }
بمجرد وضع كل هذه التعليمات البرمجية في مكانها، فإن رسم هذا الكائن يتطلب فقط استدعاء
طريقة draw()
من داخل طريقة onDrawFrame()
في العارض:
Kotlin
override fun onDrawFrame(unused: GL10) { ... mTriangle.draw() }
Java
public void onDrawFrame(GL10 unused) { ... mTriangle.draw(); }
عند تشغيل التطبيق، يُفترض أن يظهر على النحو التالي:
هناك بعض المشاكل في مثال الرمز هذا. بادئ ذي بدء، لن يثير إعجاب
أصدقاؤنا. ثانيًا، يتم ضغط المثلث قليلاً ويتغير شكله عندما تغير الشاشة
اتجاه الجهاز. يرجع سبب انحراف الشكل إلى حقيقة أن
للرؤوس، والتي تشمل نسب مساحة الشاشة،
يتم عرض GLSurfaceView
. ويمكنك حلّ هذه المشكلة باستخدام جهاز عرض وكاميرا.
التي ستظهر في الدرس التالي.
أخيرًا، يكون المثلث ثابتًا، وهو ممل بعض الشيء. في جلسة المعمل، درس إضافة حركة، ستصبح هذا الشكل استخدام مسار رسومات OpenGL ES بطريقة مثيرة للاهتمام بشكل أكبر.