LWJGL Java Game Dev

Proses Game Rendering Dengan LWJGL

May 19, 2019

Kali ini mari membahas sebuah proses game rendering pada game yang berjalan. Yang dimaksud game rendering adalah proses yang terjadi pada saat komputer menampilkan gambar kelayar monitor dari sebuah game yang berjalan.

Tulisan ini adalah lanjutan dari series tutorial game development dengan java menggunakan LWJGL. Jika kamu belum membaca pembahasan sebelumnya silahkan kunjungi pembahasanya disini.

Apa yang dimaksud dengan game rendering? Kenapa harus mempelajari game rendering. Well, jika kamu membuat sebuah game dengan LWJGL wajib hukumnya memahami proses game rendering.

Game rendering adalah prosess pengolahan data koordinat (yang sudah di bahas pada artikel sebelumnya) untuk ditampilkan kedalam sebuah layar komputer. Jika disingkatnya proses komputer melukis gambar di monitor.

Berikut ini adalah contoh implementasi dari teori game rendering yang menjadi tujuan dari pembahasan pada tulisan ini.

Jika mengikuti penjelasan pada tutorial ini perubahan warna backgroundnya di eksekusi dengan trigger keypress dari arrow up dan arrow down. Maka coba sebagai latihan buat warna background nya berubah otomatis cara random.

Akan tetapi pembahasan kali ini intinya adalah bagaimana membuat gambar segi tiga seperti contoh diatas. Mungkin kamu nanti akan bertanya - tanya kenapa untuk menggambar bentuk sederhana begitu saja perlu memahami banyak konsep dan banyak kode yang di tulis.

Tapi jika kamu sudah benar - benar paham konsepnya maka sebenarnya hal ini cukup sederhana dan lebih fleksible.

3D Representations

Sebenarnya pada game 3D itu tidak benar - benar 3D pada saat di tampilakn pada layar. Akan tetapi di tampilkan dalam 2D screen, yang mana gambar yang ditampilkan merepresentasikan visual 3D.

Dalam proses mengambar visualisasi 3D pada layar 2D OpenGL membuat konsep grapics pipeline yang step - step prosesnya seperti berikut ini :

Cartesian Coordinate

Sejak OpenGL 2.0 sudah memungkinkan untuk memprogram tahapan rendering. Tahapan dapat diprogram dengan sekumpulan program khusus yang disebut shaders. Konsep dan step OpenGL pipeline yang dapat diprogram itu kira - kira seperti berikut ini :

Cartesian Coordinate

Vertex Processing

Jadi proses rendering dimulai dari menampung kumpulan data vertices atau kumpulan titik koordinat kedalam Vertex Buffers. Yang perlu di ingat vertex adalah struktur data yang mendeskripsikan koordinat dalam ruang 2D atau 3D.

Untuk mendeskripsikan bidang vertex atau bidang koordinat dalam ruang 3D, dilakukan dengan memberikan nilai koordinat pada sumbu x, y dan z. Vertex Buffer ini adalah struktur data lain yang menampung kumpullan data koordinat yang perlu di render.

Kumpulan titik koordinat tadi akan diproses oleh vertex shader yang tujuan utamanya adalah untuk menghitung posisi yang akan diproyeksikan dari setiap koordinat kedalam ruang layar.

Shader ini juga dapat menghasilkan output lain yang terkait dengan warna atau tekstur, tetapi tujuan utamanya adalah untuk memproyeksikan simpul ke ruang layar, yaitu untuk menghasilkan titik.

Mari bahas proses geometri. Proses Geometri adalah tahap menghubungkan simpul yang telah diproses oleh vertex shader menjadi bentuk segitiga. Mengapa segitiga? Segitiga adalah seperti unit kerja dasar kartu grafis. Bentuk geometris sederhana ini nanti yang dipat digabungkan dan diubah untuk membuat bentuk 3D yang kompleks.

Rasterization & Fragment Processing

Pada proses ini kumpulan titik koordinat yang yang sudah terhubung tadi diproses menjadi data potongan pixel.

Kemudian data potongan pixel ini dikumpulkan dan di gabungkan dengan prosess yang di namakan Fragment Shader untuk membentuk gambar utuh yang sudah lengkap dengan warna dari gambar yang dibuat.

Kemudian data yang dihasilkan akan disimpan pada Framebuffer, yang mana pada FrameBuffer ini adalah hasil akhir dari prosess rendering pipeline. Didalamnya terdapat data dari setiap pixel yang perlu ditampilkan kedalam layar komputer.

Mari tulis kode untuk melakukan prosesing data untuk ditampilkan dilayar disini disebut ShaderProgram. Dan shader program ini ditulis dengan GLSL Language ( OpenGL Shading Language ) yang mana berbasis pada ANSI C.

Vertex Shader

Untuk Shader program kali ini buat pada folder resource kemudian beri nama vertex.vs extension vs ditujukan untuk vertex untuk memudahkan penamaan file. Pada dasarnya OpenGL menerima string untuk diproses agar bisa berjalan pada kartu grafis kemudian seperti ini kodenya.

#version 330
layout (location=0) in vec3 position;

void main() {
    gl_position = vec4(position, 1.0);
}

Dibaris pertama adalah kode untuk menentukan versi GLSL yang digunakan. Versi GLSL tersebut akan disesuaikan dengan versi OpenGL yang dipilih. Nah silahkan kunjungi link beriku ini untuk melihat informasi versi GLSL.

https://en.wikipedia.org/wiki/OpenGL_Shading_Language#Versions

Kemudian dibaris kedua menentukan format input untuk shader ini. Data dalam buffer OpenGL dapat berupa data apapun yang diinginkan dalam hal ini berguna untuk menentukan sudut pandang dari tampilan yang akan proses. OpenGL memiliki standar data yang ada cek disini selengkapnya

Pada kode baris kedua itu vec3 bertujuan untuk menerima data untuk nilai sumbu x, y dan z yang mana dimulai dari location=0 kemudian karena data itu tadi dari sumbu x, y dan z akan di proses dengan vec4 yang nanti hasil akhirnya akan mendapatkan tampilan 3D yang lebih kompleks.

Fragment Shader

Pada proses selanjutnya setelah di vertex shader yang mana dengan proses itu sudah mendapatkan hasil kerangka gambarnya maka sekarang dilakukan proses pemberian teksture dari kerangka yang sudah ada tadi.

Pada contoh kali ini mari membuat fragment shader yang mana disini program yang akan dibuat hanya untuk memberikan warna dari kerangka gambar yang telah dibuat melalui vertex shader tadi.

Untuk memudahkan pengelolaan program untuk vertex dan fragment berikan extension yang berbeda, disini untuk fragment berikan extensions .fs sebagai contoh berikan nama untuk program fragment shader ini dengan nama fragment.fs.

#version 330

out vec4 fragcolor;

void main() {
        fragColor = vec4(0.0, 0.5, 0.5, 1.0);
}

Struktur penulisan program fragment shader ini cukup sama dengan vertex shader. Pada kasus ini program fragment shader yang ditulis hanya untuk memberi warna menggunakan vec4.

Shader Program

Sekarang setelah menulis program untuk vertex dan fragment shader maka selajutnya adalah eperti berikut ini.

  • Buat OpenGL program.
  • Masukkan program vertex dan fragment shader.
  • Setiap shader buat fungsi khusu untuk membedakan type vertex dan fragment.
  • Compille shader sehingga bisa di baca oleh vga card.
  • Masukan shader kedalam program.
  • Selanjutnya hubungkan programnya.

Seperti yang sudah dibahas di atas mari buat sebuah class untuk menangani proses Shading ini beri nama ShaderProgram.java. Class ini berfungsi untuk membuat vertex shader dan fragment shader dalam satu buah class seperti berikut ini :

Diatas adalah contoh membuat shaderprogram yang sudah cukup untuk memproses data menjadi pixel yang siap di render kedalam layar. Setelah selesai melakukan prosess rendering maka sebaiknya menghapus memory yang sudah dipakai dikartu grafis untuk itu dalam class GameEngine panggil metod cleanup:

    @Override
    public void run() {
        try {
            init();
            gameLoop();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            cleanup();
        }
    }

    public void cleanup() {
        gameLogic.cleanup();
    }

Sekarang pada class Renderer buat method init untuk membuat instance dari ShaderProgram yang sudah di tulis diatas.

Karena tutorial ini berseri silahkan akses sumber kode yang dari tutorial sebelumnya disini agar kamu tidak kebingungan dengan pembahasan kode disini.

Nah berikut ini cara untuk membuat instance ShaderProgram dan loading vertex shader dan fragment shader yang sudah ditulis dengan GLSL sebelumnya :

private ShaderProgram shaderProgram;

public void init() throws Exception{
    shaderProgram = new ShaderProgram();
    shaderProgram.createVertexShader(Utils.loadResource("/vertex.vs");
    shaderProgram.createFragmentShader(Utils.loadResource("/fragment.fs");
    shaderProgram.link();
}

Sekarang kamu sudah punya object digunakan untuk membuat vertex dan fragment shader sekarang mari memasukan data kedalam shader yang sudah di buat tadi. Pada contoh kali ini mari menggambar bidang segitiga kedalam layar seperti berikut ini contoh visualisasinya.

Untuk untuk menampilkan visual seperti gambar diatas maka data koordinat yang perlu diberikan kepada shader program adalah seperti berikut ini :

float[] vertices = new float[]{
    0.0f, 0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
};

Sekarang untuk memasukan data koordinat tadi kedalam kartu grafis maka kamu perlu memberitahu OpenGL tentang strukturnya. Maka dari itu ada dua konsep penting yang perlu diketahui, yaitu Vertex Array Object (VAOs) dan Vertex Buffer Object (VBOs).

Vertex Buffer Object (VBOs) adalah data yang akan diolah oleh shader program. Vertex Array Object (VAOs) tempat untuk menaruh data VBO yang bisa lebih dari satu.

Hal ini sangat berguna untuk mengolah beberapa macam koordinat sehingga di dalam layar nanti akan di tampilkan banyak object. Kalau kamu bingung VBO = kambing dan VAO = kandang kambing. Dalam kandang kambing bisa kamu isi banyak kambing. Simple bukan?

Kalau sekarang kamu sudah paham dengan kandang kambing itu tadi mari mengolah data VBO. OpenGL tidak menentukan struktur data dari VBO apakah itu data yang berisi koordinat, warna, texture atau yang lainya. Kamu bebas menentukan seperti apa struktur data tersebut.

Jadi mari lanjutkan mengolah data VAO. Pertama yang perlu dilakukan adalah menyimpan data array float kedalam FloatBuffer. Hal ini dikarenakan untuk berinteraksi dengan OpenGL libaray yang mana beberbasis bahasa C, jadi kamu harus membuat data float tadi untuk bisa diolah.

FloatBuffer verticesBuffer = MemoryUntil.memAllocFloat(vertices.length);
verticesBuffer.put(vertices).flip();

Pada kode diatas itu digunakan untuk mengolah data koordinat yang sudah dibuat tadi agar bisa dikelola oleh OpenGL. Dan disini perlu mengelola memory agar dapat berinteraksi dengan OpenGL.

Untuk itu dengan menggunakan MemoryUtil class dari LWJGL kamu bisa membuat memory yang off-heap karena java object mengalokasikan memory yang disebut heap. Karena OpenGL tidak bearada dalam memory heap sehingga dengan begitu kamu perlu membuat memory diluar heap agar dapat diakses oleh OpenGL.

Pada kasus ini kamu perlu mengirim data ke GPU, jadi kamu bisa menggunakan auto-managed buffer (data dikolola secara otomatis). Tetapi untuk data yang besar nanti kamu perlu mengelola data tersebut, maka dari itulah menggunakan MemoryUtil class kamu bisa mengelola data itu yang mana bisa kamu tambah dan hapus.

Selanjutnya buat VAO dan mengikatnya sehingga nanti bisa di hapus.

vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);

Dan sekarang buat VBO dan masukan data kedalamnya.

vboId = glGenBuffers();
glBindBuffer(FL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);

Selanjutnya tentukan struktur data seperti berikut ini :

glVertexAtribPointer(0, 3, GL_FLOAT, false, 0, 0);

Parameter yang perlu kamu pahami adalah seperti berikut ini glVertexAttribPointer(index, size, type, normalized, stride, pointer)

  • index: Menentukan lokasi dimana shader memulai pengolahan koordinat.
  • size: Menentukan jumlah komponen per-atribute nilainya antara 1 sampai 4. Dalam kasus ini kamu perlu menggunakan 3 karena kamu mengolah koordinat 3D.
  • type: Menentukan type data, dalam contoh ini FLOAT.
  • normalized: Menentukan apakah data perlu dinormalisasi atau tidak.
  • stride: Menentukan batas bit antara atribute generic secara berturut - turut. Akan di bahas pada part rendering berikutnya.
  • offset: Menentukan batas pertama dari komponen dalam buffer.

Setelah selesai membuat data VBO maka kamu bisa melepaskan datanya dengan membuat menjadi 0.

// Unbind the VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);

// Ubind VAO
glBindVertexArray(0);

Setelah proses selesai maka sekarang kamu harus menghapus memory off-heap yang sudah dialokasikan oleh FloatBuffer. Hal ini dilakukan secara manual dengan mengunakan fungsi memFree.

if(verticesBuffer != null) {
    memoryUtils.memFree(verticesBuffer);
}

Itulah kode yang perlu ditulis dalam method init. Data sudah siap berada dalam kartu grafis dan bisa digunakan. Maka selanjutnya pada fungsi render buat seperti berikut ini untuk menggambar data setiap framenya.

public void render(Window window) {
    clear();

    if(window.isResized()) {
        glViewport(0, 0, window.getWidth(), window.getHeight());
        window.setResized(false);
    }

    shaderProgram.bind();

    // Bind to  the VAO
    glBindVertexArray(vaoId);
    glEnableVertexAttribArray(0);

    // Draw the vertives
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // Restore state
    glDisableVertexAttribArray(0);
    glBindVertexArray(0);

    shaderProgram.unbind();
}

Proses yang dilakukan disini adalah menghapus window kemudian jalankan shader program, gambar koordinat yang ada di VAO dan kemudian kembalikan posisi ke nilai 0.

Kamu juga perlu menambahkan fungsi cleanup pada class Renderer yang mana digunakan untuk membersihkan memori yang tidak lagi dipakai ketika game di tutup.

public void cleanup(){
    if(shaderProgram != null) {
        shaderProgram.cleanup();
    }

    glDisableVertexAttribArray(0);

    //Delete VAO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glDeleteBuffers(vboId);

    //Delete the VAO
    glBindVertexArray(0);
    glDeleteVertexArray(0);
}

Dan dari semua konsep dan kode yang dibahas diatas akan menhasilkan gambar seperti berikut ini.

Kamu mungkin berfikir ini begitu banyak sekali pekerjaan yang harus dilakukan hanya untuk menggambar segitiga yang membosankan dan kamu sekarang sangat benar. Tapi perlu diingat bahwa disini kamu mempelajari kunci dari konsepnya dan mempersiapkan infrastruktur standar untuk melakukan hal yang kompleks. Bersabar dan terus baca penjelasan selanjutnya.

Dan untuk source code dari materi ini silahkan akses disini.

https://github.com/ar-android/java-game-lwjgl/tree/a530a236c281cdb56b29c8bfb1a7ad747388334b

Subscribe to My Newsletter

Thank you for your interest in my blog. Sign up to my newsletter to stay current on the latest news and information me and to be the first to see new blog posts.