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.