Menggambar bentuk

Setelah menentukan bentuk yang akan digambar dengan OpenGL, Anda mungkin ingin menggambarnya. Menggambar bentuk dengan OpenGL ES 2.0 perlu kode sedikit lebih banyak dari yang Anda bayangkan, karena API memberikan kontrol yang besar terhadap pipeline rendering grafis.

Tutorial ini menjelaskan cara menggambar bentuk yang Anda tentukan pada tutorial sebelumnya menggunakan OpenGL ES 2.0 API.

Menginisialisasi bentuk

Sebelum menggambar, Anda harus menginisialisasi dan memuat bentuk yang akan digambar. Kecuali jika struktur (koordinat asli) dari bentuk yang Anda gunakan dalam program berubah selama berjalan, Anda harus menginisialisasinya dalam metode onSurfaceCreated() dari perender untuk efisiensi memori dan pemrosesan.

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();
        }
        ...
    }
    

Menggambar bentuk

Menggambar bentuk yang ditetapkan menggunakan OpenGL ES 2.0 memerlukan jumlah kode yang signifikan, karena Anda harus memberikan banyak detail ke pipeline rendering grafis. Secara khusus, Anda harus menentukan hal berikut:

  • Vertex Shader - Kode grafis OpenGL ES untuk merender verteks bentuk.
  • Fragment Shader - Kode OpenGL ES untuk merender tampilan bentuk dengan warna atau tekstur.
  • Program - Objek OpenGL ES yang berisi shader yang ingin Anda gunakan untuk menggambar satu atau beberapa bentuk.

Anda memerlukan minimal satu vertex shader untuk menggambar bentuk dan satu fragment shader untuk mewarnai bentuk tersebut. Shader ini harus dikompilasi dan ditambahkan ke program OpenGL ES, yang kemudian digunakan untuk menggambar bentuk. Berikut adalah contoh cara menentukan shader dasar yang dapat Anda gunakan untuk menggambar bentuk di class 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;" +
            "}";

        ...
    }
    

Shader berisi kode OpenGL Shading Language (GLSL) yang harus dikompilasi sebelum menggunakannya di lingkungan OpenGL ES. Untuk mengompilasi kode ini, buat metode utilitas di class perender:

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;
    }
    

Untuk menggambar bentuk, Anda harus mengompilasi kode shader, menambahkannya ke objek program OpenGL ES, lalu menautkan program. Lakukan hal ini pada konstruktor objek yang telah digambar, sehingga cukup dilakukan sekali.

Catatan: Mengompilasi shader OpenGL ES dan program penautan akan memberatkan dalam hal siklus CPU dan waktu pemrosesan, jadi Anda harus menghindari melakukan ini lebih dari sekali. Jika tidak mengetahui konten shader pada waktu proses, Anda harus membuat kode agar hanya dibuat satu kali, lalu disimpan dalam cache untuk digunakan nanti.

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);
        }
    }
    

Pada tahap ini, Anda siap menambahkan panggilan sebenarnya yang menggambar bentuk Anda. Menggambar bentuk dengan OpenGL ES mengharuskan Anda menentukan beberapa parameter untuk memberi tahu pipeline rendering apa yang ingin Anda gambar dan cara menggambarnya. Karena opsi menggambar dapat bervariasi berdasarkan bentuknya, sebaiknya class bentuk Anda berisi logika gambarnya sendiri.

Buat metode draw() untuk menggambar bentuk. Kode ini menetapkan nilai posisi dan warna untuk vertex shader dan fragment shader bentuk, kemudian menjalankan fungsi gambar.

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);
    }
    

Setelah semua kode ini diterapkan, menggambar objek ini hanya memerlukan panggilan ke metode draw() dari dalam metode onDrawFrame() perender:

Kotlin

    override fun onDrawFrame(unused: GL10) {
        ...

        triangle.draw()
    }
    

Java

    public void onDrawFrame(GL10 unused) {
        ...

        triangle.draw();
    }
    

Saat Anda menjalankan aplikasi, maka akan terlihat seperti ini:

Gambar 1. Segitiga digambar tanpa proyeksi atau tampilan kamera.

Ada beberapa masalah terkait contoh kode ini. Pertama, gambar tidak akan membuat teman-teman Anda terkesan. Kedua, segitiga sedikit terjepit dan berubah bentuk saat Anda mengubah orientasi layar perangkat. Alasan bentuk miring adalah karena verteks objek belum dikoreksi untuk proporsi area layar tempat GLSurfaceView ditampilkan. Anda dapat memperbaiki masalah tersebut menggunakan proyeksi dan tampilan kamera pada tutorial berikutnya.

Terakhir, segitiga ini tidak bergerak, yang terkesan agak membosankan. Pada tutorial Menambahkan motion, buatlah bentuk ini berputar dan buat penggunaan yang menarik dari pipeline grafis OpenGL ES.