Cara Menggunakan OpenGL ES di Android Aplikasi
Indonesian (Bahasa Indonesia) translation by Yanti Baddolo (you can also view the original English article)
Hampir setiap ponsel Android yang tersedia di pasaran saat ini memiliki unit pengolahan grafis, atau GPU untuk jangka pendek. Seperti namanya, ini adalah unit perangkat keras yang didedikasikan untuk menangani perhitungan yang biasanya terkait dengan grafis 3D. Sebagai pengembang aplikasi, anda dapat menggunakan GPU untuk membuat grafik dan animasi kompleks yang berjalan pada frame rate yang sangat tinggi.
Saat ini ada dua API berbeda yang dapat anda gunakan untuk berinteraksi dengan GPU perangkat Android: Vulkan dan OpenGLES. Sementara Vulkan hanya tersedia pada perangkat yang menjalankan Android 7.0 atau lebih tinggi, OpenGL ES didukung oleh semua versi Android.
Dalam tutorial ini, saya akan membantu anda memulai dengan menggunakan OpenGL ES 2.0 di aplikasi Android.
Prasyarat
Untuk dapat mengikuti tutorial ini, anda memerlukan:
- versi terbaru dari Android Studio
- perangkat Android yang mendukung OpenGL ES 2.0 atau lebih tinggi
- versi terbaru dari Blender, atau perangkat lunak pemodelan 3D lainnya
1. Apa itu OpenGL ES?
OpenGL, yang merupakan singkatan dari Open Graphics Library,
adalah platform-independent API yang memungkinkan anda membuat grafis 3D yang
dipercepat dengan hardware.OpenGL ES, singkatan dari OpenGL untuk Embedded Systems, adalah
bagian dari API.
OpenGL ES adalah API
tingkat rendah. Dengan kata lain, ia tidak
menawarkan metode apa pun yang memungkinkan anda membuat atau memanipulasi
objek 3D dengan cepat. Sebagai
gantinya, saat bekerja dengannya, anda
diharapkan mengelola kerjaan secara manual seperti membuat simpul individu dan
wajah benda 3D, menghitung berbagai transformasi 3D, dan menciptakan berbagai
jenis shader.
Perlu juga disebutkan bahwa
android SDK dan NDK bersama-sama memungkinkan anda menulis kode terkait OpenGL
ES di Java dan C.
2. Setup Proyek
Karena API OpenGL ES adalah
bagian dari kerangka kerja Android, anda tidak perlu menambahkan ketergantungan
pada proyek anda untuk dapat menggunakannya. Dalam tutorial ini,
bagaimanapun, kita akan menggunakan perpustakaan Apache Commons IO untuk
membaca isi beberapa file teks. Karena itu, tambahkan ia sebagai ketergantungan
compile
pada module's build.gradle file:
compile 'commons-io:commons-io:2.5'
Dalam tutorial ini, bagaimanapun, kita akan menggunakan perpustakaan Apache Commons IO untuk membaca isi beberapa file teks. Karena itu, tambahkan ia sebagai ketergantungan compile pada module's build.gradle file:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
3. Buat kanvas
Kerangka Android menawarkan dua widget yang dapat berfungsi sebagai
kanvas untuk grafis 3D anda: GLSurfaceView
dan TextureView
. Kebanyakan pengembang lebih suka menggunakan GLSurfaceView,
dan memilih TextureView
hanya ketika mereka berniat
untuk overlay grafis 3D mereka yang lain view
widget. Untuk aplikasi yang akan
kita buat di tutorial ini, GLSurfaceView
akan cukup.
Menambahkan GLSurfaceView
widget ke file layout anda
tidak berbeda dengan menambahkan widget lainnya.
<android.opengl.GLSurfaceView android:layout_width="300dp" android:layout_height="300dp" android:id="@+id/my_surface_view" />
Perhatikan bahwa kita telah membuat lebar widget kita sama dengan
tingginya. Melakukan hal ini penting karena sistem koordinat OpenGL ES
berbentuk persegi Jika anda harus menggunakan kanvas persegi panjang, ingatlah untuk
menyertakan rasio aspeknya saat menghitung matriks proyeksi anda. Anda akan mempelajari
matriks proyeksi di langkah selanjutnya.
Inisialisasi GLSurfaceView
widget di dalam sebuah Aktivitas
kelasnya sesederhana memanggil findViewById()
metode dan melewati iduntuk itu.
mySurfaceView = (GLSurfaceView)findViewById(R.id.my_surface_view);
Selain itu, kita harus memanggil metode setEGLContextClientVersion()
untuk secara eksplisit menentukan versi OpenGL ES yang akan kita gunakan
untuk menggambar di dalam widget.
mySurfaceView.setEGLContextClientVersion(2);
4. Buat Objek 3D
Meskipun dimungkinkan untuk membuat objek 3D di Java dengan mengkodekan
kordinat X, Y, dan Z dari semua simpulnya, hal itu sangat rumit. Menggunakan alat pemodelan
3D malah jauh lebih mudah. Menggunakan alat pemodelan 3D malah jauh lebih mudah. Blender adalah
salah satu alat tersebut. Ia bersifat open source, powerful, dan sangat mudah
dipelajari.
Jalankan Blender dan tekan X untuk menghapus default cube. Selanjutnya, tekan Shift-A dan pilih Mesh> Torus. Kita sekarang memiliki objek 3D yang cukup kompleks yang terdiri dari 576 titik.



Agar bisa menggunakan torus di aplikasi Android kita, kita harus mengekspornya sebagai file OBJ Wavefront. Karena itu, pergi ke File> Export> Wavefront (obj). Pada layar berikutnya, beri nama pada file OBJ, pastikan bahwa Triangulate Faces dan pilihan Keep Order Vertex dipilih, dan tekan tombol Ekspor OBJ .



Anda sekarang dapat menutup Blender dan pindahkan fileOBJ ke folder Assest proyek Android Studio anda.
5. Parsing File OBJ
Jika anda belum menyadarinya, file OBJ yang kami buat di langkah sebelumnya adalah file teks, yang bisa dibuka menggunakan editor teks apapun.



Dalam file, setiap baris yang dimulai dengan "v" mewakili satu
titik. Demikian pula, setiap baris yang dimulai dengan "f" mewakili
satu wajah segitiga tunggal. Sementara masing-masing garis verteks berisi koordinat X, Y, dan Z dari
sebuah simpul, masing-masing garis wajah berisi indeks tiga simpul, yang
membentuk wajah bersama. Itu saja yang perlu anda ketahui untuk mengurai file
OBJ.
Sebelum memulai, buat kelas Java baru yang disebut Torus dan tambahkan dua Daftar
objek, satu untuk simpul dan satu untuk wajah, sebagai variabel anggotanya.
public class Torus { private List<String> verticesList; private List<String> facesList; public Torus(Context context) { verticesList = new ArrayList<>(); facesList = new ArrayList<>(); // More code goes here } }
Cara termudah untuk membaca semua baris individu dari file OBJ adalah dengan menggunakan kelas Scanner
dan metode nextLine()
. Sementara perulangan melalui garis dan mengisi dua daftar, anda dapat menggunakan kelas String
metode startsWith()
untuk memeriksa apakah baris saat ini dimulai dengan "v" atau "f".
// Open the OBJ file with a Scanner Scanner scanner = new Scanner(context.getAssets().open("torus.obj")); // Loop through all its lines while(scanner.hasNextLine()) { String line = scanner.nextLine(); if(line.startsWith("v ")) { // Add vertex line to list of vertices verticesList.add(line); } else if(line.startsWith("f ")) { // Add face line to faces list facesList.add(line); } } // Close the scanner scanner.close();
6. Buat Objek Buffer
Anda tidak dapat melewati daftar simpul dan wajah dengan metode yang tersedia
di OpenGL ES API secara langsung. Anda harus terlebih dahulu mengubahnya menjadi
objek penyangga. Untuk menyimpan data koordinat vertex, kita memerlukan objek FloatBuffer
. Untuk data wajah, yang hanya terdiri dari indeks simpul, objek ShortBuffer
sudah cukup.
Dengan demikian, tambahkan variabel anggota berikut ke kelas Torus:
private FloatBuffer verticesBuffer; private ShortBuffer facesBuffer;
Untuk menginisialisasi buffer, pertama kita harus membuat obyekByteBuffer
menggunakan metode allocateDirect ()
. Untuk buffer simpul, alokasikan empat byte untuk setiap koordinat, dengan koordinat bilangan floating-point. Setelah objek ByteBuffer
telah dibuat, anda bisa mengubahnya menjadi FloatBuffer
dengan memanggil
metode asFloatBuffer ()
.
// Create buffer for vertices ByteBuffer buffer1 = ByteBuffer.allocateDirect(verticesList.size() * 3 * 4); buffer1.order(ByteOrder.nativeOrder()); verticesBuffer = buffer1.asFloatBuffer();
Demikian juga, buat objek ByteBuffer
lain untuk penyangga wajah. Kali ini, alokasikan dua byte untuk setiap indeks
vertex karena indeksnya unsigned short
literal. Juga, pastikan anda menggunakan metode asShortBuffer()
untuk mengubah objek ByteBuffer
ke ShortBuffer
.
// Create buffer for faces ByteBuffer buffer2 = ByteBuffer.allocateDirect(facesList.size() * 3 * 2); buffer2.order(ByteOrder.nativeOrder()); facesBuffer = buffer2.asShortBuffer();
Mengisi simpul simpul melibatkan
perulangan melalui konten verticesList
, penggalian X, Y, dan
Z koordinat dari setiap item, dan memanggil metode Put()
untuk
memasukkan data ke dalam buffer. Karena verticesList
hanya
berisi string, kita harus menggunakan parseFloat()
untuk mengubah koordinat dari string ke nilai float
.
for(String vertex: verticesList) { String coords[] = vertex.split(" "); // Split by space float x = Float.parseFloat(coords[1]); float y = Float.parseFloat(coords[2]); float z = Float.parseFloat(coords[3]); verticesBuffer.put(x); verticesBuffer.put(y); verticesBuffer.put(z); } verticesBuffer.position(0);
Perhatikan bahwa dalam kode di atas kita telah
menggunakan metode position()
untuk mereset posisi
buffer.
Mengisi wajah penyangga sedikit berbeda. Anda harus menggunakan metode parseShort()
untuk mengubah setiap indeks vertex menjadi nilai pendek. Selain itu, karena indeks dimulai dari satu dan bukan nol, anda harus ingat untuk mengurangi satu dari mereka sebelum memasukkannya ke dalam buffer.
for(String face: facesList) { String vertexIndices[] = face.split(" "); short vertex1 = Short.parseShort(vertexIndices[1]); short vertex2 = Short.parseShort(vertexIndices[2]); short vertex3 = Short.parseShort(vertexIndices[3]); facesBuffer.put((short)(vertex1 - 1)); facesBuffer.put((short)(vertex2 - 1)); facesBuffer.put((short)(vertex3 - 1)); } facesBuffer.position(0);
7. Buat Shader
Untuk bisa membuat objek 3D kita, kita harus membuat vertex shader dan
shader fragmen untuknya. Untuk saat ini, anda bisa memikirkan shader
sebagai program yang sangat sederhana yang ditulis dalam bahasa mirip C yang
disebut OpenGL Shading Language, atau GLSL untuk jangka pendek.
Sebuah shader vertex, seperti yang anda duga, bertanggung jawab untuk menangani simpul objek 3D. Shader fragmen, juga disebut pixel shader, bertanggung jawab untuk mewarnai piksel objek 3D.
Langkah 1: Buat Vertex Shader
Buat file baru yang disebut vertex_shader.txt di dalam forlder res/raw projek anda.
Sebuah vertex shader harus memiliki sebuah atribut
variabel global di dalamnya untuk menerima data posisi vertex dari kode Java anda. Selain itu, tambahkan uniform
variabel global untuk menerima matriks proyeksi tampilan dari kode Java.
Di dalam Fungsi main()
vertex shader, anda harusmengatur nilai gl_position, sebuah GLSL built-invariabel yang menentukan posisi akhir vertex. Untuk saat ini, anda cukup mengatur nilainya ke produk uniform
dan atribut
variabel global.
Dengan demikian, tambahkan kode berikut ke file:
attribute vec4 position; uniform mat4 matrix; void main() { gl_Position = matrix * position; }
Langkah 2: Buat Shader
Fragmen
Buat file baru yang disebut fragment_shader.txt di dalam forlder res/raw projek anda.
Agar tutorial singkat ini, sekarang kita akan membuat shader fragmen yang sangat minimalis yang hanya memberi warna oranye ke semua piksel. Untuk menetapkan warna ke piksel, di dalam Fungsi main()
shader fragmen, anda bisa menggunakan gl_FragColor
built-in variable.
precision mediump float; void main() { gl_FragColor = vec4(1, 0.5, 0, 1.0); }
Dalam kode di atas, baris pertama yang menentukan ketepatan bilangan floating-point penting karena shader fragmen tidak memiliki ketepatan standar untuk keduanya.
Langkah 3: Kompilasi
Shader
Kembali di kelas Torus
, anda sekarang harus menambahkan kode untuk mengkompilasi dua shader yang anda buat. Sebelum melakukannya, anda harus mengubahnya dari sumber daya mentah menjadi string. Kelas IOUtils
yang merupakan bagian dari perpustakaanApache Commons IO, memiliki metode toString()
untuk melakukan hal itu. Kode berikut menunjukkan cara menggunakannya:
// Convert vertex_shader.txt to a string InputStream vertexShaderStream = context.getResources().openRawResource(R.raw.vertex_shader); String vertexShaderCode = IOUtils.toString(vertexShaderStream, Charset.defaultCharset()); vertexShaderStream.close(); // Convert fragment_shader.txt to a string InputStream fragmentShaderStream = context.getResources().openRawResource(R.raw.fragment_shader); String fragmentShaderCode = IOUtils.toString(fragmentShaderStream, Charset.defaultCharset()); fragmentShaderStream.close();
Kode shader harus ditambahkan ke objek shader OpenGL ES. Untuk membuat objek shader baru, gunakan metode glCreateShader()
dari kelas GLES20
Bergantung pada jenis objek shader yang ingin anda buat, anda bisa lewat GL_VERTEX_SHADER
atau GL_FRAGMENT_SHADER
untuk itu. Metode mengembalikan bilangan bulat yang berfungsi sebagai referensi ke objek shader. Objek shader yang baru dibuat tidak mengandung kode apapun. Untuk menambahkan kode shader ke objek shader, anda harus menggunakan metode glShaderSource()
.
Kode berikut membuat objek shader untuk shader vertex dan shader fragmen:
int vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER); GLES20.glShaderSource(vertexShader, vertexShaderCode); int fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER); GLES20.glShaderSource(fragmentShader, fragmentShaderCode);
Kita sekarang bisa melewati objek shader ke metode glCompileShader()
untuk mengkompilasi kode yang dikandungnya.
GLES20.glCompileShader(vertexShader); GLES20.glCompileShader(fragmentShader);
8. Buat sebuah Program
Saat merender objek 3D, anda tidak menggunakan shader
secara langsung. Sebagai gantinya, anda memasangnya ke
program dan menggunakan program. Oleh karena itu,
tambahkan variabel anggota ke kelas Torus
untuk menyimpan referensi ke program ES OpenGL.
private int program;
Untuk membuat program baru, gunakan metode glCreateProgram ()
. Untuk
memasang objek shader simpul dan fragmen ke objek tersebut, gunakan metode glAttachShader
()
.
program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); GLES20.glAttachShader(program, fragmentShader);
Pada titik ini, anda dapat menghubungkan program dan mulai menggunakannya. Untuk melakukannya, gunakan metode glLinkProgram()
dan glUseProgram()
.
GLES20.glLinkProgram(program); GLES20.glUseProgram(program);
9. Gambar Objek 3D
Dengan shader dan buffer siap, kita memiliki semua
yang kita butuhkan untuk menarik torus kita. Tambahkan metode baru
ke kelas Torus
yang disebut draw:
public void draw() { // Drawing code goes here }
Pada langkah awal, di dalam vertex shader, kita mendefinisikan position
variabel untuk menerima data posisi vertex dari kode Java. Sekarang saatnya mengirim data posisi
simpul ke sana. Untuk melakukannya, kita harus terlebih dahulu mendapatkan pegangan ke position variabel dalam kode Java kita menggunakan metode glGetAttribLocation()
. Selain itu, pegangan harus diaktifkan dengan menggunakan metode glEnableVertexAttribArray()
.
Dengan demikian, tambahkan kode berikut di dalam metode seri()
:
int position = GLES20.glGetAttribLocation(program, "position"); GLES20.glEnableVertexAttribArray(position);
Untuk menunjuk posisi
Pegang buffer simpul kita, kita harus menggunakan metode glVertexAttribPointer()
. Selain simpul simpul itu sendiri, metode ini mengharapkan jumlah koordinat per simpul, jenis koordinat, dan offset byte untuk setiap simpul. Karena kita memiliki tiga koordinat per simpul dan setiap koordinat adalah float
, byte offset harus 3*4
.
GLES20.glVertexAttribPointer(position, 3, GLES20.GL_FLOAT, false, 3 * 4, verticesBuffer);
Shader verteks kami juga mengharapkan matrik proyeksi tampilan. Meskipun matriks semacam itu tidak selalu diperlukan, dengan menggunakan satu memungkinkan anda memiliki kontrol yang lebih baik mengenai bagaimana objek 3D anda diberikan.
Matriks proyeksi tampilan hanyalah produk dari matriks tampilan dan proyeksi. Matriks tampilan memungkinkan anda menentukan lokasi kamera dan titik yang dilihatnya. Matriks proyeksi, di sisi lain, memungkinkan Anda untuk tidak hanya memetakan sistem koordinat persegi OpenGL ES ke layar segi empat dari perangkat Android, namun juga menentukan bidang dekat dan jauh dari melihat frustum.
Untuk membuat matriks, anda bisa membuat tiga array float
ukuran 16
:
float[] projectionMatrix = new float[16]; float[] viewMatrix = new float[16]; float[] productMatrix = new float[16];
Untuk menginisialisasi matriks proyeksi, anda bisa menggunakan metode frustumM()
dari kelas Matriks
. Ia mengharapkan lokasi dari plane kiri, kanan, bawah, atas, dekat, dan jauh. Karena kanvas kita sudah persegi, anda bisa menggunakan nilainya -1
dan 1
untuk bagian kiri dan kanan, dan bagian bawah dan puncak pesawat. Untuk plane jepret yang dekat dan jauh, silakan bereksperimen dengan nilai yang berbeda.
Matrix.frustumM(projectionMatrix, 0, -1, 1, -1, 1, 2, 9);
Untuk menginisialisasi matriks tampilan, gunakan metode setLookAtM()
. Ia mengharapkan posisi kamera dan titik yang dilihatnya. Anda bebas bereksperimen dengan nilai yang berbeda.
Matrix.setLookAtM(viewMatrix, 0, 0, 3, -4, 0, 0, 0, 0, 1, 0);
Akhirnya, untuk menghitung matriks produk, gunakan metode multiplyMM()
.
Matrix.multiplyMM(productMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
Untuk melewatkan matriks produk ke vertex shader, anda harusmendapatkan pegangannya matriks
variabel menggunakan metode glGetUniformLocation ()
. Begitu anda memiliki pegangan, anda bisa mengarahkannya ke matriks produk menggunakan metode glUniformMatrix (
).
int matrix = GLES20.glGetUniformLocation(program, "matrix"); GLES20.glUniformMatrix4fv(matrix, 1, false, productMatrix, 0);
Anda pasti sudah memperhatikan bahwa kita masih belum menggunakan
penyangga wajah. Itu berarti kita masih belum memberi tahu OpenGL ES bagaimana
menghubungkan simpul untuk membentuk segitiga, yang akan berfungsi sebagai
wajah objek 3D kita
Metode glDrawElements()
memungkinkan anda menggunakan wajah penyangga untuk membuat segitiga. Sebagai argumennya, ia mengharapkan jumlah indeks simpul, jenis masing-masing indeks, dan buffer wajah.
GLES20.glDrawElements(GLES20.GL_TRIANGLES, facesList.size() * 3, GLES20.GL_UNSIGNED_SHORT, facesBuffer);
Terakhir, jangan lupa untuk menonaktifkannya atribut
handler yang anda aktifkan sebelumnya untuk melewatkan data vertex ke vertex shader
GLES20.glDisableVertexAttribArray(position);
10. Buat Renderer
Widget GLSurfaceView
kita membutuhkan objek GLSurfaceView.Renderer
untuk bisa membuat grafis 3D. Anda bisa menggunakan setRenderer()
untuk mengaitkan penyaji dengan itu.
mySurfaceView.setRenderer(new GLSurfaceView.Renderer() { // More code goes here });
Di dalam Metode onSurfaceCreated()
penyaji, anda harus menentukan seberapa sering grafik 3D harus diberikan. Untuk saat ini, mari kita membuat hanya ketika perubahan grafis 3D. Untuk melakukannya, lewatkan RENDERMODE_WHEN_DIRTY
konstan untuk metode setRenderMode()
. Selain itu, inisialisasi sebuah instance baru dari obyek Torus
.
@Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { mySurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); torus = new Torus(getApplicationContext()); }
Di dalam Metode onSurfaceChanged()
penyaji, anda dapat menentukan lebar dan tinggi area pandang anda dengan menggunakan metode glViewport()
.
@Override public void onSurfaceChanged(GL10 gl10, int width, int height) { GLES20.glViewport(0,0, width, height); }
Di dalam metode onDrawFrame()
renderer, tambahkan panggilan ke metode draw()
dari kelas Torus
untuk benar-benar menarik torus.
@Override public void onDrawFrame(GL10 gl10) { torus.draw(); }
Pada titik ini, anda dapat menjalankan aplikasi anda untuk melihat torus oranye.



Kesimpulan
Sekarang anda tahu cara menggunakan OpenGL ES di aplikasi Android. Dalam tutorial ini, anda juga belajar cara mengurai file OBJ Wavefront dan mengekstrak simpul dan data wajah darinya. Saya sarankan anda menghasilkan beberapa objek 3D lagi menggunakan Blender dan mencoba merendernya di aplikasi.
Meskipun kita hanya berfokus pada OpenGL ES 2.0, ketahuilah bahwa OpenGL ES 3.x kompatibel dengan OpenGL ES 2.0. Itu berarti bahwa jika anda lebih memilih menggunakan OpenGL ES 3.x di aplikasi anda, anda cukup mengganti kelas GLES20
dengan kelas GLES30
atau GLES31
.
Untuk mempelajari lebih lanjut tentang OpenGL ES, anda bisa merujuknya halaman referensi. Dan untuk mempelajari lebih lanjut tentang pengembangan aplikasi Android, pastikan untuk melihat beberapa tutorial kami yang lain di sini di Envato Tuts+!
Cara Memulai Dengan Android's Native Development Kit
Android Things: Peripheral Input/Output
Cara Mengamankan Aplikasi Android
Mengkodekan Aplikasi Android Dengan Flutter dan Dart