Alur aplikasi OpenGL (dan WebGL?)

Aplikasi OpenGL (dan sepertinya aplikasi berbasis grafis lainnya) memiliki sebuah alur kerja yang bisa saya bilang cukup standar. Dimulai dengan melakukan persiapan, lalu ada sebuah perulangan utama, dan diakhiri dengan rutin bersih-bersih sebelum aplikasi selesai dieksekusi. Pada perulangan utama ini, operasi yang terjadi secara umum bisa dikelompokkan menjadi tiga: baca input (mouse, keyboard, dll), proses, dan gambar.

Main Loop

Pada tahapan membaca input, seluruh masukan dari keyboard, mouse, dan sumber lainnya akan ditangkap dan digunakan untuk mengubah kondisi yang sedang dipantau. Misalnya jika tombol spasi ditekan, maka si aplikasi harus membuat pesawat dalam game mengeluarkan tembakan.

Setelah ini, semua pemrosesan akan dilakukan. Misalnya peluru yang ditembakkan mesti sampai di pojok kanan atas layar dalam waktu 1 detik. Maka perlu dihitung dimana dan kapan saja gambar peluru harus diletakkan di layar.

Pada tahapan menggambar, gambar peluru yang telah dibaca dari berkas akan disalin ke layar. Gambar akan diletakkan pada posisi yang telah dihitung sebelumnya. Begitu pula untuk objek-objek lain yang perlu digambar di layar.

Seberapa cepat perulangan utama yang menjalankan 3 tahapan di atas ini harus dijalankan? Dengan kata lain, berapa gambar yang harus dibuat setiap detiknya? Semakin banyak gambar yang dibuat setiap detiknya, akan semakin halus pergerakan animasi yang dibuat. Namun dengan demikian, proses yang dikerjakan tidak boleh berlangsung terlalu lama karena tentunya dapat menunda tahapan menggambar yang efeknya akan mengurangi jumlah gambar yang dibuat tiap detiknya.

Monitor yang ada sekarang umumnya dapat menggambar 60 kali setiap detiknya (60 frame per second atau 60 Hz). Bagaimana kalau kita targetkan agar aplikasi dapat mengikuti kecepatan monitor? Mari kita hitung. Jika dalam 1 detik kita harus menggambar 60 gambar, berarti 1 gambar harus dibuat dalam waktu 1/60 detik atau sekitar 16 milidetik (16 ms)! Membaca input, melakukan pemrosesan data, dan menggambar semuanya harus terjadi dalam waktu 16ms saja agar kita bisa meraih kecepatan menggambar 60 gambar per detik!

Apakah 60 fps adalah angka yang harus selalu dicapai? Tergantung.. semakin cepat, animasi akan semakin halus. Namun jika aplikasi yang kita buat tidak sering menampilkan aplikasi, maka batasan kecepatan penggambaran ini bisa kita perlonggar. Tuk perbandingan, film umumnya hanya memiliki 24 gambar setiap detiknya alias 24 fps.

Memulai WebGL

Setelah sebelumnya kita berkenalan sedikit dengan WebGL, mari kita mulai membuat aplikasi WebGL. Pastikan dulu browser yang Anda gunakan mendukung WebGL agar Anda dapat melihat hasil yang diharapkan.

Jika semua sudah siap, mari kita buka teks editor dan masukkan kode berikut ini.

<canvas id="canvas" width="640" height="480">WebGL tidak didukung</canvas>

Kode di atas akan membuat sebuah area gambar yang akan dijadikan tempat menggambar gambar 3 dimensi dengan WebGL.

Persiapan dari sisi HTML sudah cukup, sekarang mari beralih ke dunia Javascript untuk mengendalikan WebGL.

Pertama, dapatkan referensi ke area gambarย <canvas>ย yang kita buat sebelumnya.

<script type="text/javascript">

var canvas = document.getElementById('canvas');

Setelah itu, ambil pengendali WebGL dari area gambar tadi.

var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

Mengapa mencoba mengambil dua hal berbeda (webgl dan experimental-webgl)? Sampai saat ini, WebGL masih bersifat eksperimental sehingga beberapa browser masih belum menjadikannya hal yang “udah pasti ada”. Maka dari itu, browser2 ini menggunakan nama experimental-webgl sebagai identifikasi pengendali WebGL.

Nilai yang dihasilkan dapat kita gunakan untuk mengecek apakah browser yang dipakai mendukung WebGL atau tidak.

if (!gl) {
  alert('WebGL tidak didukung');
}

Jika nilai keluaran tidak dievaluasi menjadi true, maka bisa diambil kesimpulan si browser tidak mendukung WebGL.

Setelah mendapatkan referensi ke pengendali WebGL, kita sudah bisa memanggil fungsi-fungsi WebGL. Sebagai contoh, fungsi untuk menghapus area gambar dengan warna tertentu.

if (gl) {
  // hapus layar
  gl.clearColor(1.0, 0.5, 0.0, 1.0); // warna oranye
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}

</script>

Jika sukses, area gambar yang dibuat seharusnya akan berwarna oranye seperti berikut.

Memulai WebGL

Selamat, aplikasi WebGL super sederhana sudah jadi! ๐Ÿ˜€

Kode lengkap dari tutorial kali ini dapat dilihat diย http://fajran.github.io/webgl-tutorial/01-memulai-webgl/index.html.ย Seluruh kode sumber tutorial WebGL ini akan tersedia diย https://github.com/fajran/webgl-tutorial.

Pengenalan WebGL

Mari kita mulai ngoprek2 WebGL ๐Ÿ˜€ Mudah2an ini beneran menjadi satu set tutorial xD

Apa itu WebGL? Singkatnya WebGL adalah OpenGL yang hidup di dalam browser. OpenGL sendiri adalah satu set API yang dapat dipakai untuk membuat gambar 3 dimensi (termasuk juga 2 dimensi). Proses penggambaran yang menggunakan OpenGL biasanya akan “dibantu” dengan perangkat keras grafis (GPU) sehingga prosesnya akan jauh lebih cepat.

Kalau OpenGL aslinya adalah satu set API untuk bahasa C, maka WebGL, berhubung dia hidup di dalam browser, adalah API untuk Javascript. WebGL ini merupakan kelanjutan dari Canvas API yang hanya menyediakan fasilitas menggambar gambar 2 dimensi.

Sampai saat ini, belum semua browser mendukung WebGL. Dukungan yang disediakan juga sering bergantung dengan GPU dan driver yang dipakai. Hal ini tidak selalu berarti pengguna GPU tipe lama tidak bisa menikmati WebGL karena implementasi prosedur gambar 3 dimensi bisa saja dilakukan tanpa bantuan perangkat keras, walau efeknya proses akan berjalan lebih lambat. Pendekatan ini diambil oleh Chromium/Google Chrome yang juga menyediakan implementasi WebGL yang hanya menggunakan peranti lunak.

Untuk mencari tahu apakah browser yang Anda pakai saat ini dapat menampilkan WebGL atau tidak, kunjungi saja halaman berikut ini.

http://get.webgl.org/

Jika WebGL didukung, Anda akan menjumpai tampilan seperti berikut. Perhatikan juga ada sebuah animasi kubus berputar di halaman tersebut.

Screen Shot 2013-05-28 at 8.32.57 PM

Apakah browser yang Anda gunakan mendukung WebGL?

Memulai OpenGL ES dengan SDL

Saya ingin membuat tulisan berseri mengenai OpenGL dan inilah tulisan pertamanya ๐Ÿ˜€ mudah2an bener2 lanjut terus xD

Untuk masuk ke “dunia” OpenGL, hal pertama yang harus dibuat adalah sebuah tempat dimana kode OpenGL kita dapat bekerja. Salah satu caranya adalah dengan menggunakan SDL yang sudah sangat membantu untuk urusan membuat window, menangkap event (mouse, keyboard), dan juga membuat dunia OpenGL kosong yang siap diisi.

Berhubung sekarang dunia mobile sedang sangat meriah, saya juga akan menggunakan standar OpenGL ES yang untungnya juga dapat dijalankan di atas komputer desktop. Untuk bahasa pemrograman, saya akan menggunakan C++ dengan harapan kode akan dapat dengan mudah dipindahkan ke platform lain seperti Android maupun iOS.

Pastikan pustaka pengembangan SDL dan OpenGL ES sudah terpasang. Bagi pengguna keluarga Debian, pasang paket libsdl1.2-dev dan libgles2-mesa-dev.

$ sudo apt-get install libsdl1.2-dev libgles2-mesa-dev

Mari kita mulai..

Pertama-tama, inilah kode pembuka kita ๐Ÿ˜€

#include <SDL/SDL.h>

int main(int argc, char** argv) {
  const int width = 1024;
  const int height = 768;
  const int depth = 32;

  // Prepare SDL
  SDL_Init(SDL_INIT_EVERYTHING);
  SDL_SetVideoMode(width, height, depth,
                   SDL_HWSURFACE | SDL_GL_DOUBLEBUFFER | SDL_OPENGL);

  SDL_Event event;

  bool running = true;
  while (running) {
    // Check for incoming event
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_KEYUP &&
          event.key.keysym.sym == SDLK_ESCAPE) {
        running = false;
      }
    }

    // Swap buffer
    SDL_GL_SwapBuffers();
  }

  SDL_Quit();

  return 0;
}

Simpan ke dalam berkas, katakanlah dengan nama 01-begin.cc, lalu kompilasi dengan perintah berikut:

$ g++ -o 01-begin 01-begin.cc `sdl-config --libs --cflags`

Jika aplikasi ini dijalankan, akan muncul sebuah window dengan isi “acak”. Kita belum memerintahkan penggambaran apa2 sehingga isi window dapat berisi apa saja :D. Berikut ini yang saya dapatkan sekarang.

$ ./01-begin

Untuk mengakhiri, tekan tombol escape.

Kode di atas dapat didapat juga di https://github.com/fajran/opengles-tutorial/tree/master/01-begin

Setelah kerangka dasar siap, mari kita mulai menyentuh OpenGL ES. Agar fungsi-fungsi OpenGL ES dapat kita gunakan, maka pastikan kita sudah mengimpor header dari OpenGL ES

#include <GLES2/gl2.h>

Setelah itu kita siapkan 3 buah fungsi “utama” yang digunakan untuk menyiapkan OpenGL ES, untuk memperbarui “kondisi” OpenGL ES, dan terakhir untuk menggambar ke layar.

void init_opengl() {
  glClearColor(0xCD / 255.0, 0xD7 / 255.0, 0xB6 / 255.0, 1.0);
}

void update_opengl() {

}

void draw_opengl() {
  glClear(GL_COLOR_BUFFER_BIT);
}

Lalu kita selipkan pemanggilan 3 fungsi di atas dalam kode yang pertama kita buat. Fungsi pertama dipanggil setelah SDL siap dipakai. Lalu fungsi kedua dan ketiga dipanggil di dalam “main loop” yang akan menggambar setiap frame yang akan kita buat. Kode akhir dapat dilihat di GitHub.

Mari kita bahas sedikit apa yang baru saja ditambahkan.

Pertama, pemanggilan fungsi glClearColor digunakan untuk mengatur warna yang akan digunakan saat layar kita “hapus”. Pada contoh di atas, saya menggunakan warna hijau muda. Berhubung saya cuma tahu kode heksadesimalnya (rentang 0-255) dan fungsi tersebut meminta kode warna dalam tipe float dengan rentang 0.0 sampai 1.0, maka saya konversi dulu nilai warnanya.

Lalu untuk setiap frame yang digambar, saya perintahkan OpenGL untuk menghapus layar dengan memanggil fungsi glClear dengan memberikan paramter GL_COLOR_BUFFER_BIT agar buffer warna yang menampung gambar dihapus dan diisi dengan warna yang saya set sebelumnya.

Mari kita lihat hasilnya sekarang!

$ g++ -o 02-background 02-background.cc `sdl-config --libs --cflags` -lGLESv2
$ ./02-background

Sekian dulu tulisan pembuka ini. Untuk selanjutnya kita akan mencoba sesuatu yang bernama shader!

Semua kode yang akan saya buat dalam seri tulisan ini tersedia juga di https://github.com/fajran/opengles-tutorial dan silakan dipakai sebebas-bebasnya karena semua kode yang terlibat saya rilis dalam domain publik alias public domain.

Transparent video?

For the past few days, I have been wondering how to show a transparent video on an OpenGL scene. I can already extract video frames from a movie file using ffmpeg and display them as a video. But how about a transparent video?

Is there a compression format that supports alpha channel? How is the compression ratio, is it still good? If I simply use chroma key, how can I candle a fade-in/out images (I don’t want to deal with a complicated math formula here)?

All of my questions here were answered after reading this StackOverflow question. Well not answered per se :P, but I immediately what I need to do to show a transparent video. Basically I need to store the alpha mask. But instead of using the alpha channel in a RGBA video stream (I still need to make experiments with this though), I can use an additional area of the video to store the mask. With this, I can use the regular RGB-channel video with those already-known compression format, ratio, and what-not. But in exchange, I need to enlarge the video dimension to store the alpha mask. Hopefully the compression algorithm is clever enough to compress more this part of the video ๐Ÿ˜›

Having that, all I need to do next is to modify the already-simple fragment shader that I use. So now I need to take the first half of video and use the RGB values of it. Then take the second half and get the Red value (or Blue or Green) and use it as the alpha channel of the RGB.

varying vec2 texcoord;
uniform sampler2D textureId;

void main() {
    vec2 tc_rgb = texcoord * vec2(1.0, 0.5);
    vec2 tc_alpha = tc_rgb + vec2(0.0, 0.5);
    vec4 frame = texture2D(textureId, tc_rgb)
    vec4 alpha = texture2D(textureId, tc_alpha);
    gl_FragColor = vec4(frame.rgb, alpha.r);
}

Yippy, that’s all! Ah yes, don’t forget to enable GL_BLEND and set the blend function otherwise you will have a bad time ๐Ÿ˜›

Video transparan dengan shader

Main-main dengan OpenGL shader memang mengasikkan ๐Ÿ˜€ Katakanlah saya punya sebuah video yang ingin saya tampilkan dalam OpenGL scene. Saya bisa memakai ffmpeg tuk mengekstrak setiap frame yang ada di dalamnya. Frame ini lalu diubah menjadi OpenGL texture sehingga dapat ditampilkan dalam OpenGL scene. GPU dan drivernya zaman sekarang sudah mendukung NPOT Texture alias tekstur yang ukurannya tidak perlu angka dari dua pangkat sekian sehingga setiap frame dari video bisa langsung begitu saja digunakan tanpa harus diubah ukurannya terlebih dahulu.

Kalau cuma menampilkan yang seperti ini, vertex dan fragment shader berikut sudah cukup tuk digunakan. Katakanlah mpv berisi model-view-projection matrix, coord adalah koordinat kotak video (range 0.0-1.0), textureId adalah (tentunya) texture id dari video frame.

attribute vec3 coord;
uniform mat4 mpv;
varying vec2 texcoord;

void main() {
   gl_Position = mpv * vec4(coord, 1.0);
   texcoord = coord;
}
varying vec2 texcoord;
uniform sampler2D textureId;

void main() {
    gl_FragColor = texture2D(textureId, texcoord);
}

Beberapa hari belakangan saya mencari2 cara tuk menampilkan video yang transparan. Satu teknik yang terpikirkan langsung oleh saya adalah dengan menggunakan Chroma Key sehingga saya cukup mencari warna tertentu dan menghapusnya agar tidak ditampilkan. Namun kalau pakai cara ini, bagaimana menampilkan gambar yang tampil/hilang secara bertahap (err.. fade-in/out)?

Setelah merenung lebih lanjut, saya jadi terpikir kalau saya punya akses ke alpha channel saya bisa menggunakan nilai si alpha untuk membuat warna RGB menjadi transparan. Namun apa artinya si video harus menampung gambar dengan channel RGBA? apa format kompresi yang banyak dipakai (spesifiknya h264) itu mendukung RGBA? Untung saja saya menemukan sebuah pertanyaan di StackOverflow yang menjawab pertanyaan saya ini ๐Ÿ˜€

Intinya, video diubah menjadi seperti berikut ini.

Bagian atas video berisi channel RGB dan bagian bawah video adalah alpha masknya. Kalau sudah begini, si video sendiri bisa dikompresi dengan format apapun karena pada dasarnya si video hanya berisi RGB stream biasa. Namun kita perlu melakukan sedikit usaha tambahan pada saat menampilkannya: ambil setengah gambar atas dan setengah gambar bawah, ambil nilai RGB dari gambar atas, ambil nilai (katakanlah) R dari gambar bawah, gabungkan kedua nilai ini dg menjadikan nilai R dari gambar bawah sebagai nilai Alpha dari warna akhir.

Kalau kalimat terakhir di atas diubah menjadi fragment shader, hasilnya kira2 akan seperti berikut.

varying vec2 texcoord;
uniform sampler2D textureId;

void main() {
    vec2 tc_rgb = texcoord * vec2(1.0, 0.5);
    vec2 tc_alpha = tc_rgb + vec2(0.0, 0.5);
    vec4 frame = texture2D(textureId, tc_rgb)
    vec4 alpha = texture2D(textureId, tc_alpha);
    gl_FragColor = vec4(frame.rgb, alpha.r);
}

Voila! eiya, jangan lupa nyalakan GL_BLEND dan set blend function-nya agar alpha value-nya beneran terpakai.

Widget Qt4 dan OpenSceneGraph

Kabarnya, widget Qt4 bisa digambar di atas layar OpenGL. Jadi kepikiran gmn caranya widget2 tsb bisa juga dipasang bareng dg OpenSceneGraph. Gugling sedikit, eh ternyata udah ada yang buat!. Walau sepertinya masih ada masalah.

Saya coba donlod kodingan yg dikasih. Lalu ubah2 dikit supaya bisa nampilin kubus. Jadi deh =D

Okeh.. saatnya bereksperimen lagi..

Cuma 4.8 fps

Iseng2 nyoba bikin puluhan ribu kubus pake OpenGL. Eh taunya cuma dapet 4 fps pas bikin 30ribu kubus ๐Ÿ™

Dijalanin di atas Ubuntu 8.10 di mesin MacBook generasi paling awal: Core Duo 1.83 GHz, 2GB RAM, Intel 945GM. Compiz dimatikan.

Cobain dong..

$ sudo apt-get install mesa-common-dev libglu1-mesa-dev libsdl1.2-dev
$ wget -O kubus.c http://gist.github.com/raw/31070/7d9fa6f768d1e17a5b5c3a4fac0513c968aed191
$ wget -O Makefile http://gist.github.com/raw/31070/b33b6de85207141642433b26b294c2ce9885d5b2
$ make
$ ./kubus 30000

Pindahin pointer mouse ke tengah2 window dan tunggu sekitar 10 detik sampe keterangan fps-nya muncul.