
Bloom
Karena kisaran kecerahan terbatas yang tersedia untuk monitor konvensional, tugas menampilkan sumber cahaya terang secara meyakinkan dan permukaan yang terang benderang sulit menurut definisi. Salah satu metode umum untuk menyorot area terang pada monitor adalah teknik yang menambahkan lingkaran cahaya di sekitar objek terang, memberi kesan "penyebaran" cahaya di luar sumber cahaya. Hasilnya, pengamat memberi kesan kecerahan tinggi pada area yang diterangi atau sumber cahaya tersebut.
Efek yang dijelaskan dari halo dan keluarnya cahaya di luar sumber dicapai dengan teknik pasca-pemrosesan yang disebut
bloom . Menerapkan efek menambahkan lingkaran cahaya karakteristik ke semua area terang dari adegan yang ditampilkan, yang dapat dilihat pada contoh di bawah ini:
Bloom menambahkan petunjuk visual yang khas pada gambar tentang kecerahan signifikan dari objek yang ditutupi oleh lingkaran cahaya dari efek yang diterapkan. Diterapkan secara selektif dan sampai batas tertentu (yang sayangnya tidak dapat ditanggulangi oleh banyak game), efeknya dapat secara signifikan meningkatkan ekspresif visual dari pencahayaan yang digunakan dalam adegan, serta menambah drama dalam situasi tertentu.
Teknik ini bekerja bersama dengan rendering
HDR hampir sebagai tambahan yang jelas. Rupanya, karena ini, banyak orang secara keliru mencampurkan kedua istilah ini dengan pertukaran penuh. Namun, teknik ini sepenuhnya independen dan digunakan untuk tujuan yang berbeda. Dimungkinkan untuk menerapkan bloom menggunakan buffer bingkai default dengan kedalaman warna 8bit, seperti menerapkan rendering HDR tanpa menggunakan bloom. Satu-satunya hal adalah bahwa rendering HDR memungkinkan Anda untuk menerapkan efek dengan cara yang lebih efisien (kita akan melihatnya nanti).
Untuk menerapkan mekar, adegan yang diterangi pertama kali dibuat dengan cara biasa. Selanjutnya, buffer warna HDR dan buffer warna yang hanya berisi bagian-bagian cerah dari adegan diekstraksi. Gambar bagian terang yang diekstraksi ini kemudian buram dan dilapis di atas gambar HDR asli dari pemandangan itu.
Untuk membuatnya lebih jelas, kami akan menganalisis langkah demi langkah proses. Jadikan adegan yang mengandung 4 sumber cahaya terang yang ditampilkan sebagai kubus berwarna. Semuanya memiliki nilai kecerahan dalam kisaran 1,5 hingga 15,0. Jika buffer warna adalah output ke HDR, hasilnya adalah sebagai berikut:
Dari penyangga warna HDR ini, kami mengekstrak semua fragmen yang kecerahannya melebihi batas yang telah ditentukan. Ternyata gambar hanya berisi area yang terang:
Selanjutnya, gambar area terang ini buram. Tingkat keparahan efek pada dasarnya ditentukan oleh kekuatan dan jari-jari filter blur yang diterapkan:
Gambar buram yang dihasilkan dari area terang adalah dasar dari efek akhir lingkaran cahaya di sekitar objek terang. Tekstur ini hanya dicampur dengan gambar asli HDR dari adegan tersebut. Karena area yang terang buram, ukurannya meningkat, yang akhirnya memberikan efek visual luminositas yang melampaui batas sumber cahaya:
Seperti yang Anda lihat, bloom bukanlah teknik yang paling canggih, tetapi mencapai kualitas visual yang tinggi dan keandalan tidak selalu mudah. Sebagian besar, efeknya tergantung pada kualitas dan jenis filter blur yang diterapkan. Bahkan perubahan kecil dalam parameter filter dapat secara dramatis mengubah kualitas akhir peralatan.
Jadi, tindakan di atas memberi kita algoritma langkah-demi-langkah dari efek post-processing untuk efek bloom. Gambar di bawah ini merangkum tindakan yang diperlukan:
Pertama-tama, kita membutuhkan informasi tentang bagian-bagian terang dari pemandangan berdasarkan nilai ambang yang diberikan. Ini yang akan kita lakukan.
Ekstrak highlight
Jadi, sebagai permulaan, kita perlu mendapatkan dua gambar berdasarkan adegan kita. Akan naif untuk merender dua kali, tetapi gunakan metode
Multiple Render Target (
MRT ) yang lebih canggih: kami menentukan lebih dari satu output dalam shader fragmen akhir, dan berkat ini, dua gambar dapat diekstraksi dalam satu pass! Untuk menentukan di mana buffer warna yang akan dihasilkan oleh shader, penata
tata letak digunakan:
layout (location = 0) out vec4 FragColor; layout (location = 1) out vec4 BrightColor;
Tentu saja, metode ini hanya akan berfungsi jika kita telah menyiapkan beberapa buffer untuk penulisan. Dengan kata lain, untuk mengimplementasikan beberapa output dari fragmen shader, frame buffer yang digunakan saat ini harus mengandung cukup banyak buffer warna yang terhubung. Jika kita beralih ke pelajaran tentang
frame buffer , maka dapat diingat bahwa ketika mengikat tekstur sebagai buffer warna, kita dapat menunjukkan
nomor lampiran warna . Sampai sekarang, kami tidak perlu menggunakan lampiran selain
GL_COLOR_ATTACHMENT0 , tetapi kali ini
GL_COLOR_ATTACHMENT1 akan berguna, karena kami membutuhkan dua tujuan untuk merekam sekaligus:
Juga, dengan memanggil
glDrawBuffers , Anda perlu memberi tahu secara eksplisit kepada OpenGL bahwa kami akan menghasilkan beberapa buffer. Jika tidak, perpustakaan masih akan hanya output ke lampiran pertama, mengabaikan operasi tulis ke lampiran lainnya. Sebagai argumen ke fungsi, array pengidentifikasi lampiran yang digunakan dari enumerasi yang sesuai dilewatkan:
unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, attachments);
Untuk buffer bingkai ini, setiap shader fragmen yang menentukan specifier
lokasi untuk outputnya akan menulis ke buffer warna yang sesuai. Dan ini adalah berita bagus, karena dengan cara ini kami menghindari render pass yang tidak perlu untuk mengekstrak data tentang bagian-bagian terang dari adegan - Anda dapat melakukan semuanya sekaligus dalam satu shader:
#version 330 core layout (location = 0) out vec4 FragColor; layout (location = 1) out vec4 BrightColor; [...] void main() { [...]
Dalam fragmen ini, bagian yang berisi kode khas untuk menghitung pencahayaan dihilangkan. Hasilnya ditulis ke output pertama shader - variabel
FragColor . Selanjutnya, warna fragmen yang dihasilkan digunakan untuk menghitung nilai kecerahan. Untuk ini, terjemahan tertimbang dalam skala abu-abu dilakukan (dengan perkalian skalar, kami mengalikan komponen vektor yang sesuai dan menambahkannya bersama-sama, yang mengarah ke nilai tunggal). Kemudian, ketika kecerahan fragmen dari ambang tertentu terlampaui, kami merekam warnanya dalam output kedua shader. Untuk kubus menggantikan sumber cahaya, shader ini juga dijalankan.
Setelah menemukan algoritme, kita dapat memahami mengapa teknik ini bekerja sangat baik dengan rendering HDR. Rendering dalam format HDR memungkinkan komponen warna melampaui batas atas 1.0, yang memungkinkan Anda untuk secara lebih fleksibel mengatur ambang kecerahan di luar interval standar [0., 1.], memberikan kemampuan untuk menyempurnakan bagian pemandangan mana yang dianggap cerah. Tanpa menggunakan HDR, Anda harus puas dengan ambang kecerahan dalam interval [0., 1.], yang cukup dapat diterima, tetapi mengarah ke cutoff kecerahan yang lebih "tajam", yang sering membuat bunga mekar terlalu mengganggu dan mencolok (bayangkan diri Anda di lapangan salju yang tinggi di pegunungan) .
Setelah shader dieksekusi, dua buffer target akan berisi gambar normal adegan, serta gambar yang hanya berisi area terang.
Gambar area terang sekarang harus diproses menggunakan blur. Anda dapat melakukannya dengan filter
kotak (
kotak ) sederhana, yang digunakan di bagian postprocessing dari pelajaran
frame buffer . Tetapi hasil yang jauh lebih baik diperoleh dengan
penyaringan Gauss .
Gaussian Blur
Pelajaran postprocessing memberi kami ide kabur menggunakan rata-rata warna sederhana dari fragmen gambar yang berdekatan. Metode blur ini sederhana, tetapi gambar yang dihasilkan mungkin terlihat lebih menarik. Gaussian blur didasarkan pada kurva distribusi berbentuk lonceng dengan nama yang sama: nilai fungsi yang tinggi terletak lebih dekat ke pusat kurva dan jatuh ke kedua sisi. Secara matematis, kurva Gaussian dapat diekspresikan dengan parameter yang berbeda, tetapi bentuk umum kurva tetap sebagai berikut:

Buram dengan bobot berdasarkan nilai-nilai kurva Gauss terlihat jauh lebih baik daripada filter persegi panjang: karena kenyataan bahwa kurva memiliki area yang lebih besar di sekitar pusatnya, yang sesuai dengan bobot yang lebih besar untuk fragmen di dekat pusat inti filter. Sebagai contoh, dengan inti 32x32, kita akan menggunakan faktor pembobotan yang lebih kecil, semakin jauh fragmennya dari yang pusat. Karakteristik filter inilah yang memberikan hasil Gaussian blur yang lebih memuaskan secara visual.
Implementasi filter akan membutuhkan array dua dimensi dari koefisien pembobotan, yang dapat diisi berdasarkan ekspresi dua dimensi yang menggambarkan kurva Gaussian. Namun, kami akan segera menghadapi masalah kinerja: bahkan inti blur yang relatif kecil dalam fragmen 32x32 akan membutuhkan 1024 sampel tekstur untuk setiap fragmen dari gambar yang diproses!
Untungnya bagi kita, ekspresi kurva Gaussian memiliki karakteristik matematika yang sangat nyaman - keterpisahan, yang memungkinkan untuk membuat dua ekspresi satu dimensi dari satu ekspresi dua dimensi yang menggambarkan komponen horizontal dan vertikal. Ini akan memungkinkan pengaburan pada gilirannya dalam dua pendekatan: secara horizontal, dan kemudian secara vertikal dengan set bobot yang sesuai dengan masing-masing arah. Gambar yang dihasilkan akan sama dengan ketika memproses algoritma dua dimensi, tetapi akan membutuhkan daya pemrosesan prosesor video yang jauh lebih sedikit: alih-alih 1024 sampel dari tekstur, kita hanya perlu 32 + 32 = 64! Ini adalah inti dari penyaringan Gaussian dua jalur.

Bagi kami, semua ini berarti satu hal: pengaburan satu gambar harus dilakukan dua kali, dan di sini penggunaan objek penyangga bingkai akan berguna. Kami menerapkan apa yang disebut teknik ping-pong: ada beberapa objek penyangga bingkai dan isi buffer warna dari satu framebuffer dirender dengan beberapa pemrosesan ke dalam buffer warna dari framebuffer saat ini, kemudian sumber framebuffer dan penerima framebuffer-interchange dan proses ini diulang beberapa kali. Bahkan, frame buffer saat ini untuk menampilkan gambar hanya diaktifkan, dan dengan itu, tekstur saat ini dari mana pengambilan sampel dilakukan untuk rendering. Pendekatan ini memungkinkan Anda untuk mengaburkan gambar asli dengan menempatkannya di buffer frame pertama, lalu mengaburkan konten buffer frame pertama, menempatkannya di frame kedua, kemudian mengaburkan frame kedua, menempatkannya di frame pertama dan seterusnya.
Sebelum beralih ke kode tuning penyangga bingkai, mari kita lihat kode Gaussian blur shader:
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D image; uniform bool horizontal; uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); void main() {
Seperti yang Anda lihat, kami menggunakan sampel koefisien yang agak kecil dari kurva Gaussian, yang digunakan sebagai bobot untuk sampel secara horizontal atau vertikal relatif terhadap fragmen saat ini. Kode memiliki dua cabang utama yang membagi algoritma menjadi lintasan vertikal dan horizontal berdasarkan nilai seragam
horizontal . Offset untuk setiap sampel diatur sama dengan ukuran texel, yang didefinisikan sebagai kebalikan dari ukuran tekstur (nilai tipe
vec2 yang dikembalikan oleh fungsi
teksturSize ()).
Buat dua bingkai penyangga yang berisi satu penyangga warna berdasarkan tekstur:
unsigned int pingpongFBO[2]; unsigned int pingpongBuffer[2]; glGenFramebuffers(2, pingpongFBO); glGenTextures(2, pingpongBuffer); for (unsigned int i = 0; i < 2; i++) { glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]); glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0 ); }
Setelah kami mendapatkan tekstur HDR adegan dan mengekstraksi tekstur area terang, kami mengisi buffer warna dari salah satu pasangan framebuffers yang disiapkan dengan tekstur kecerahan dan memulai proses ping-pong sepuluh kali (lima kali vertikal, lima horizontal):
bool horizontal = true, first_iteration = true; int amount = 10; shaderBlur.use(); for (unsigned int i = 0; i < amount; i++) { glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]); shaderBlur.setInt("horizontal", horizontal); glBindTexture( GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal] ); RenderQuad(); horizontal = !horizontal; if (first_iteration) first_iteration = false; } glBindFramebuffer(GL_FRAMEBUFFER, 0);
Pada setiap iterasi, kami memilih dan memasang salah satu buffer bingkai berdasarkan pada apakah iterasi ini akan kabur secara horizontal atau vertikal, dan buffer warna framebuffer lain kemudian digunakan sebagai tekstur input untuk blur shader. Pada iterasi pertama, kita harus secara eksplisit menggunakan gambar yang mengandung area terang (
brightnessTexture ) - jika tidak, kedua framebuffer ping-pong akan tetap kosong. Setelah sepuluh lewat, gambar asli berbentuk lima kali dikaburkan oleh filter Gaussian penuh. Pendekatan yang digunakan memungkinkan kita untuk dengan mudah mengubah tingkat blur: semakin banyak iterasi ping-pong, semakin kuat blur.
Dalam kasus kami, hasil blur terlihat seperti ini:
Untuk menyelesaikan efek, tetap hanya untuk menggabungkan gambar buram dengan gambar HDR asli dari pemandangan.
Pencampuran tekstur
Memiliki tekstur HDR dari adegan yang ditampilkan dan tekstur kabur dari area yang terlalu terang, yang Anda butuhkan untuk mewujudkan efek mekar atau cahaya yang terkenal adalah menggabungkan kedua gambar ini. Shader fragmen terakhir (sangat mirip dengan yang disajikan dalam pelajaran tentang format
HDR ) melakukan hal itu - ia secara campuran menggabungkan dua tekstur:
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D scene; uniform sampler2D bloomBlur; uniform float exposure; void main() { const float gamma = 2.2; vec3 hdrColor = texture(scene, TexCoords).rgb; vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; hdrColor += bloomColor;
Apa yang harus dicari: pencampuran dilakukan sebelum menerapkan
pemetaan nada . Ini akan menerjemahkan dengan benar kecerahan tambahan dari efek ke dalam rentang LDR (
Rentang Dinamis Rendah ), sambil mempertahankan distribusi kecerahan relatif dalam adegan.
Hasil pemrosesan - semua area terang menerima efek cahaya yang nyata:
Kubus yang menggantikan sumber cahaya sekarang terlihat jauh lebih terang dan lebih baik menyampaikan kesan sumber cahaya. Adegan ini cukup primitif, karena penerapan efek antusiasme khusus tidak akan menyebabkan, tetapi dalam adegan kompleks dengan pencahayaan yang bijaksana, mekar yang disadari secara kualitatif dapat menjadi elemen visual penting yang menambah drama.
Kode sumber untuk contoh ada di
sini .
Saya perhatikan bahwa pelajaran menggunakan filter yang cukup sederhana dengan hanya lima sampel di setiap arah. Dengan membuat lebih banyak sampel dalam radius yang lebih besar atau dengan melakukan beberapa iterasi filter, Anda dapat secara visual meningkatkan efeknya. Juga, perlu dikatakan bahwa secara visual kualitas dari keseluruhan efek secara langsung tergantung pada kualitas dari algoritma blur yang digunakan. Dengan meningkatkan filter, Anda dapat mencapai peningkatan signifikan dan seluruh efek. Misalnya, hasil yang lebih mengesankan ditunjukkan oleh kombinasi beberapa filter dengan ukuran inti yang berbeda atau kurva Gaussian yang berbeda. Berikut ini adalah sumber daya tambahan dari Kalogirou dan EpicGames yang membahas cara meningkatkan kualitas bloom dengan memodifikasi Gaussian blur.
Sumber Daya Tambahan