Pelajari OpenGL. Pelajaran 6.3 - Pencahayaan Berbasis Gambar. Iradiasi difus

OGL3 Pencahayaan berdasarkan gambar atau IBL ( Image Based Lighting ) adalah kategori metode pencahayaan berdasarkan bukan pada akuntansi untuk sumber cahaya analitis (dibahas dalam pelajaran sebelumnya ), tetapi mempertimbangkan seluruh lingkungan objek yang diterangi sebagai satu sumber cahaya kontinu. Dalam kasus umum, dasar teknis dari metode tersebut terletak pada pemrosesan peta kubik lingkungan (disiapkan di dunia nyata atau dibuat berdasarkan adegan tiga dimensi) sehingga data yang disimpan dalam peta dapat langsung digunakan dalam perhitungan pencahayaan: pada kenyataannya, setiap texel dari peta kubik dianggap sebagai sumber cahaya . Secara umum, ini memungkinkan Anda untuk menangkap efek pencahayaan global dalam adegan, yang merupakan komponen penting yang menyampaikan "nada" keseluruhan dari pemandangan saat ini dan membantu objek yang diterangi menjadi lebih baik "tertanam" di dalamnya.

Karena algoritma IBL memperhitungkan pencahayaan akun dari lingkungan "global" tertentu, hasilnya dianggap sebagai simulasi pencahayaan latar belakang yang lebih akurat atau bahkan perkiraan kasar pencahayaan global. Aspek ini membuat metode IBL menarik dalam hal memasukkan PBR ke dalam model, karena menggunakan cahaya sekitar dalam model pencahayaan memungkinkan objek terlihat jauh lebih benar secara fisik.


Untuk memasukkan pengaruh IBL ke dalam sistem PBR yang sudah dijelaskan, kami kembali ke persamaan reflektansi yang sudah dikenal:

Lo(p, omegao)= int limit Omega(kd fracc pi+ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p, omegai)n cdot omegaid omegai


Seperti dijelaskan sebelumnya, tujuan utamanya adalah menghitung integral untuk semua arah radiasi yang masuk wi belahan bumi  Omega . Dalam pelajaran terakhir, perhitungan integral tidak membebani, karena kita tahu sebelumnya jumlah sumber cahaya, dan, oleh karena itu, semua beberapa arah kejadian cahaya yang sesuai dengan mereka. Pada saat yang sama, integral tidak dapat diselesaikan dengan snap: vektor jatuh wi dari lingkungan dapat membawa kecerahan energi non-nol. Akibatnya, untuk penerapan praktis dari metode ini diperlukan untuk memenuhi persyaratan berikut:
  • Anda harus menemukan cara untuk mendapatkan kecerahan energi adegan untuk vektor arah sewenang-wenang wi ;
  • Perlu bahwa solusi integral dapat terjadi secara real time.

Nah, poin pertama diselesaikan dengan sendirinya. Sebuah petunjuk solusi telah menyelinap di sini: salah satu metode untuk mewakili iradiasi pemandangan atau lingkungan adalah peta kubik yang telah mengalami pemrosesan khusus. Setiap texel dalam peta semacam itu dapat dianggap sebagai sumber pemancar yang terpisah. Dengan mengambil sampel dari peta semacam itu menurut vektor sembarang wi kita dengan mudah mendapatkan kecerahan energi pemandangan ke arah ini.

Jadi, kita mendapatkan kecerahan energi adegan untuk vektor yang berubah-ubah wi :

vec3 radiance = texture(_cubemapEnvironment, w_i).rgb; 

Hebatnya, bagaimanapun, menyelesaikan integral mengharuskan kita untuk membuat sampel dari peta lingkungan bukan dari satu arah, tetapi dari semua kemungkinan di belahan bumi. Dan - untuk setiap fragmen yang diarsir. Jelas, untuk tugas waktu nyata ini praktis tidak praktis. Metode yang lebih efektif adalah dengan menghitung bagian dari operasi integrand terlebih dahulu, bahkan di luar aplikasi kita. Tetapi untuk ini, Anda harus menyingsingkan lengan baju Anda dan menyelam lebih dalam ke esensi ekspresi reflektifitas:

Lo(p, omegao)= int limit Omega(kd fracc pi+ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p, omegai)n cdot omegaid omegai



Dapat dilihat bahwa bagian-bagian dari ekspresi berhubungan dengan difus kd dan cermin ks Komponen BRDF bersifat independen. Anda dapat membagi integral menjadi dua bagian:

Lo(p, omegao)= int limit Omega(kd fracc pi)Li(p, omegai)n cdot omegaid omegai+ int limit Omega(ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p, omegai)n cdot omegaid omegai



Pembagian seperti itu menjadi beberapa bagian akan memungkinkan kita untuk berurusan dengan masing-masing secara individual, dan dalam pelajaran ini kita akan membahas bagian yang bertanggung jawab untuk pencahayaan difus.

Setelah menganalisis bentuk integral atas komponen difus, kita dapat menyimpulkan bahwa komponen difusi Lambert pada dasarnya konstan (warna). s indeks bias kd dan  pi konstan dalam kondisi integrand) dan tidak tergantung pada variabel lain. Mengingat fakta ini, kita dapat menempatkan konstanta di luar tanda integral:

Lo(p, omegao)=kd fracc pi int limit OmegaLi(p, omegai)n cdot omegaid omegai



Jadi kita mendapatkan integral yang hanya bergantung pada wi (Diasumsikan itu p sesuai dengan pusat peta kubik lingkungan). Berdasarkan rumus ini, Anda dapat menghitung atau, bahkan lebih baik, melakukan pra-perhitungan peta kubik baru yang menyimpan hasil perhitungan integral komponen difus untuk setiap arah sampel (atau peta texel) wo menggunakan operasi konvolusi.

Konvolusi adalah operasi penerapan beberapa perhitungan untuk setiap elemen dalam set data, dengan mempertimbangkan data semua elemen lain dalam set. Dalam hal ini, data tersebut adalah kecerahan energi dari pemandangan atau peta lingkungan. Jadi, untuk menghitung satu nilai di setiap arah sampel di peta kubik, kita harus memperhitungkan nilai yang diambil dari semua arah sampel yang mungkin di belahan bumi yang terletak di sekitar titik sampel.

Untuk menggabungkan peta lingkungan, Anda harus menyelesaikan integral untuk setiap arah yang dihasilkan dari sampel wo dengan melakukan beberapa sampel terpisah di sepanjang arah wi milik belahan bumi  Omega , dan rata-rata kecerahan energi total. Belahan, atas dasar pengambilan sampel arah wi berorientasi sepanjang vektor wo mewakili arah tujuan yang konvolusi saat ini sedang dihitung. Lihatlah gambar untuk pemahaman yang lebih baik:



Peta kubik yang sudah dihitung sebelumnya yang menyimpan hasil integrasi untuk setiap arah sampel wo juga dapat dianggap sebagai menyimpan hasil penjumlahan semua iluminasi difus tidak langsung dalam adegan, insiden pada permukaan tertentu yang berorientasi sepanjang arah wo . Dengan kata lain, peta kubik seperti itu disebut peta irradiansi, karena peta lingkungan kubik pra-konvolusional memungkinkan Anda untuk langsung mencicipi besarnya iradiasi pemandangan, yang datang dari arah yang sewenang-wenang. wo , tanpa perhitungan tambahan.
Ekspresi yang menentukan kecerahan energi juga tergantung pada posisi titik pengambilan sampel p yang kami ambil tergeletak tepat di tengah peta iradiasi. Asumsi ini membebankan batasan dalam arti bahwa sumber dari semua pencahayaan difus tidak langsung juga akan menjadi peta lingkungan tunggal. Dalam adegan yang heterogen dalam pencahayaan, ini dapat menghancurkan ilusi realitas (terutama dalam adegan dalam ruangan). Mesin rendering modern memecahkan masalah ini dengan menempatkan objek bantu khusus di tempat - probe refleksi . Setiap objek tersebut terlibat dalam satu tugas: ia membentuk peta iradiasinya sendiri untuk lingkungan terdekatnya. Dengan teknik ini, iradiasi (dan kecerahan energi) pada titik arbitrer p akan ditentukan oleh interpolasi sederhana antara sampel refleksi terdekat. Tetapi untuk tugas saat ini, kami setuju bahwa peta lingkungan diambil dari pusatnya, dan kami akan menganalisis sampel refleksi dalam pelajaran selanjutnya.
Di bawah ini adalah contoh peta kubik lingkungan dan peta iradiasi (berdasarkan mesin gelombang ) yang berasal darinya, yang rata-rata kecerahan energi lingkungan untuk setiap arah keluaran wo .

Jadi, kartu ini menyimpan hasil konvolusi pada setiap texel (sesuai dengan arahnya wo ), dan secara lahiriah peta seperti itu tampak seperti menyimpan warna rata-rata peta lingkungan. Sampel ke segala arah dari peta tersebut akan mengembalikan nilai iradiasi yang berasal dari arah ini.

PBR dan HDR


Dalam pelajaran sebelumnya , telah disebutkan secara singkat bahwa untuk operasi yang benar dari model pencahayaan PBR, sangat penting untuk mempertimbangkan kisaran kecerahan HDR dari sumber cahaya yang ada. Karena model PBR pada input menerima parameter dengan satu atau lain cara berdasarkan kuantitas dan karakteristik fisik yang sangat spesifik, masuk akal untuk mengharuskan kecerahan energi dari sumber cahaya sesuai dengan prototip aslinya. Tidak masalah bagaimana kami menjustifikasi nilai spesifik dari fluks radiasi untuk setiap sumber: kami membuat perkiraan rekayasa kasar atau beralih ke kuantitas fisik - perbedaan karakteristik antara lampu ruang dan matahari akan sangat besar dalam hal apa pun. Tanpa menggunakan rentang HDR , tidak mungkin untuk secara akurat menentukan kecerahan relatif dari berbagai sumber cahaya.

Jadi, PBR dan HDR adalah teman selamanya, ini bisa dimengerti, tetapi bagaimana fakta ini berhubungan dengan metode pencahayaan berbasis gambar? Dalam pelajaran terakhir, ditunjukkan bahwa mengubah PBR ke kisaran rendering HDR mudah. Masih ada satu "tetapi": karena penerangan tidak langsung dari lingkungan didasarkan pada peta kubik lingkungan, diperlukan cara untuk melestarikan karakteristik HDR dari pencahayaan latar belakang ini di peta lingkungan.

Sampai sekarang, kami telah menggunakan peta lingkungan yang dibuat dalam format LDR (seperti skybox ). Kami menggunakan sampel warna dari mereka dalam rendering apa adanya dan ini cukup dapat diterima untuk bayangan langsung objek. Dan sama sekali tidak cocok ketika menggunakan peta lingkungan sebagai sumber pengukuran yang dapat diandalkan secara fisik.

RGBE - format gambar HDR


Kenali format file gambar RGBE. File dengan ekstensi " .hdr " digunakan untuk menyimpan gambar dengan rentang dinamis yang luas, mengalokasikan satu byte untuk setiap elemen triad warna dan satu byte lagi untuk eksponen umum. Format ini juga memungkinkan Anda untuk menyimpan peta lingkungan kubik dengan rentang intensitas warna di luar kisaran LDR [0., 1.]. Ini berarti bahwa sumber cahaya dapat mempertahankan intensitas mereka yang sebenarnya, diwakili oleh peta lingkungan seperti itu.

Jaringan ini memiliki cukup banyak peta lingkungan gratis dalam format RGBE, diambil dalam berbagai kondisi nyata. Ini adalah contoh dari situs arsip sIBL :


Anda mungkin akan terkejut dengan apa yang Anda lihat: setelah semua, gambar yang terdistorsi ini sama sekali tidak terlihat seperti peta kubik biasa dengan rincian yang diuraikan menjadi 6 wajah. Penjelasannya sederhana: peta lingkungan ini diproyeksikan dari bola ke pesawat - pemindaian sama persegi panjang diterapkan. Ini dilakukan untuk dapat menyimpan dalam format yang tidak mendukung mode penyimpanan kartu kubik apa adanya. Tentu saja, metode proyeksi ini memiliki kekurangan: resolusi horizontal jauh lebih tinggi daripada vertikal. Dalam kebanyakan kasus aplikasi dalam rendering, ini adalah rasio yang dapat diterima, karena biasanya detail menarik dari lingkungan dan pencahayaan terletak persis di bidang horizontal, dan bukan di bidang vertikal. Nah, plus untuk semuanya, kita perlu kode konversi kembali ke peta kubik.

Mendukung format RGBE di stb_image.h


Mengunduh format gambar ini sendiri membutuhkan pengetahuan tentang spesifikasi format , yang tidak sulit, tetapi masih melelahkan. Untungnya bagi kami , perpustakaan pemuatan gambar stb_image.h , diimplementasikan dalam satu file header, mendukung memuat file RGBE, mengembalikan array angka floating-point - apa yang kita butuhkan untuk keperluan kita! Menambahkan perpustakaan ke proyek Anda, memuat data gambar sangat sederhana:

 #include "stb_image.h" [...] stbi_set_flip_vertically_on_load(true); int width, height, nrComponents; float *data = stbi_loadf("newport_loft.hdr", &width, &height, &nrComponents, 0); unsigned int hdrTexture; if (data) { glGenTextures(1, &hdrTexture); glBindTexture(GL_TEXTURE_2D, hdrTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "Failed to load HDR image." << std::endl; } 

Perpustakaan secara otomatis mengonversi nilai dari format HDR internal ke angka 32-bit real biasa, dengan tiga saluran warna secara default. Cukup untuk menyimpan data gambar HDR asli dalam tekstur titik-mengambang 2D normal.

Ubah pemindaian dengan sudut yang sama menjadi peta kubik


Pemindaian empat persegi panjang yang sama dapat digunakan untuk secara langsung memilih sampel dari peta lingkungan, namun, ini akan membutuhkan operasi matematika yang mahal, sementara mengambil dari peta kubik normal akan praktis bebas dalam kinerja. Justru dari pertimbangan ini bahwa dalam pelajaran ini kita akan membahas konversi gambar persegi panjang yang sama menjadi peta kubik, yang akan digunakan nanti. Namun, metode pengambilan sampel langsung dari peta sama-sama persegi panjang menggunakan vektor tiga dimensi juga akan ditampilkan di sini, sehingga Anda dapat memilih metode kerja yang cocok untuk Anda.

Untuk mengkonversi, Anda perlu menggambar kubus berukuran unit, mengamatinya dari dalam, memproyeksikan peta sama persegi panjang di wajahnya, dan kemudian mengekstrak enam gambar dari wajah sebagai wajah peta kubik. Vertex shader pada tahap ini cukup sederhana: ia hanya memproses simpul dari kubus sebagaimana adanya, dan juga meneruskan posisi yang tidak direformasi ke shader fragmen untuk digunakan sebagai vektor sampel tiga dimensi:

 #version 330 core layout (location = 0) in vec3 aPos; out vec3 localPos; uniform mat4 projection; uniform mat4 view; void main() { localPos = aPos; gl_Position = projection * view * vec4(localPos, 1.0); } 

Dalam shader fragmen, kami menaungi setiap wajah kubus seolah-olah kami mencoba membungkus kubus dengan lembut dengan lembaran dengan peta persegi panjang yang sama. Untuk melakukan ini, arah sampel ditransfer ke shader fragmen diambil, diproses oleh sihir trigonometri khusus, dan, pada akhirnya, pemilihan dibuat dari peta sama-persegi panjang seolah-olah itu sebenarnya peta kubik. Hasil seleksi langsung disimpan sebagai warna fragmen dari permukaan kubus:

 #version 330 core out vec4 FragColor; in vec3 localPos; uniform sampler2D equirectangularMap; const vec2 invAtan = vec2(0.1591, 0.3183); vec2 SampleSphericalMap(vec3 v) { vec2 uv = vec2(atan(vz, vx), asin(vy)); uv *= invAtan; uv += 0.5; return uv; } void main() { // localPos   vec2 uv = SampleSphericalMap(normalize(localPos)); vec3 color = texture(equirectangularMap, uv).rgb; FragColor = vec4(color, 1.0); } 

Jika Anda benar-benar menggambar kubus dengan shader ini dan peta lingkungan HDR terkait, Anda mendapatkan sesuatu seperti ini:


Yaitu dapat dilihat bahwa sebenarnya kami memproyeksikan tekstur persegi panjang ke sebuah kubus. Hebat, tapi bagaimana ini akan membantu kami dalam membuat peta kubik nyata? Untuk mengakhiri tugas ini, perlu merender kubus yang sama 6 kali dengan kamera melihat masing-masing wajah, sambil menulis output ke objek frame buffer terpisah:

 unsigned int captureFBO, captureRBO; glGenFramebuffers(1, &captureFBO); glGenRenderbuffers(1, &captureRBO); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO); 

Tentu saja, kami tidak akan lupa untuk mengatur memori untuk menyimpan masing-masing dari enam wajah peta kubik masa depan:

 unsigned int envCubemap; glGenTextures(1, &envCubemap); glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); for (unsigned int i = 0; i < 6; ++i) { //  ,     // 16     glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, nullptr); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 

Setelah persiapan ini, tetap hanya untuk secara langsung melakukan transfer bagian-bagian dari peta yang sama persegi panjang di ambang peta kubik.

Kami tidak akan membahas terlalu banyak detail, terutama karena kode berulang banyak terlihat dalam pelajaran pada frame buffer dan bayangan omnidirectional . Pada prinsipnya, semuanya bermuara pada persiapan enam matriks tampilan terpisah yang mengorientasikan kamera secara ketat ke masing-masing permukaan kubus, serta matriks proyeksi khusus dengan sudut pandang 90 ° untuk menangkap seluruh permukaan kubus. Kemudian, hanya enam kali, rendering dilakukan, dan hasilnya disimpan dalam framebuffer floating-point:

 glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); glm::mat4 captureViews[] = { glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) }; //  HDR        equirectangularToCubemapShader.use(); equirectangularToCubemapShader.setInt("equirectangularMap", 0); equirectangularToCubemapShader.setMat4("projection", captureProjection); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, hdrTexture); //         glViewport(0, 0, 512, 512); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); for (unsigned int i = 0; i < 6; ++i) { equirectangularToCubemapShader.setMat4("view", captureViews[i]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderCube(); //    } glBindFramebuffer(GL_FRAMEBUFFER, 0); 

Di sini kita menggunakan lampiran warna buffer bingkai, dan secara bergantian mengubah wajah yang terhubung dari peta kubik, yang mengarah ke output langsung dari render ke salah satu wajah dari peta lingkungan. Kode ini perlu dieksekusi hanya sekali, setelah itu kita masih akan memiliki peta lingkungan envCubemap yang lengkap yang berisi hasil konversi versi asli sama dengan persegi panjang dari peta lingkungan HDR.

Kami akan menguji peta kubik yang dihasilkan dengan membuat sketsa shader skybox paling sederhana:

 #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 projection; uniform mat4 view; out vec3 localPos; void main() { localPos = aPos; //         mat4 rotView = mat4(mat3(view)); vec4 clipPos = projection * rotView * vec4(localPos, 1.0); gl_Position = clipPos.xyww; } 

Perhatikan trik dengan komponen-komponen vektor clipPos : kita menggunakan xyww tetrad ketika merekam koordinat vertex yang diubah untuk memastikan bahwa semua fragmen skybox memiliki kedalaman maksimum 1,0 (pendekatan itu sudah digunakan dalam pelajaran yang sesuai ). Jangan lupa untuk mengubah fungsi perbandingan ke GL_LEQUAL :

 glDepthFunc(GL_LEQUAL); 

Shader fragmen hanya memilih dari peta kubik:

 #version 330 core out vec4 FragColor; in vec3 localPos; uniform samplerCube environmentMap; void main() { vec3 envColor = texture(environmentMap, localPos).rgb; envColor = envColor / (envColor + vec3(1.0)); envColor = pow(envColor, vec3(1.0/2.2)); FragColor = vec4(envColor, 1.0); } 

Pemilihan dari peta didasarkan pada koordinat lokal yang diinterpolasikan dari simpul-simpul kubus, yang merupakan arah yang benar dari pemilihan dalam kasus ini (sekali lagi, dibahas dalam pelajaran tentang skybox, sekitar Per. ). Karena komponen transportasi dalam matriks tampilan diabaikan, render skybox tidak akan bergantung pada posisi pengamat, menciptakan ilusi latar belakang yang sangat jauh. Karena di sini kita langsung mengeluarkan data dari kartu HDR ke framebuffer default, yang merupakan penerima LDR, perlu untuk memanggil kembali kompresi nada. Dan akhirnya, hampir semua kartu HDR disimpan dalam ruang linier, yang berarti bahwa koreksi gamma harus diterapkan sebagai chord pemrosesan akhir.

Jadi, ketika mengeluarkan skybox yang diperoleh, bersama dengan array bola yang sudah akrab, sesuatu yang serupa diperoleh:


Yah, banyak upaya yang dihabiskan, tetapi pada akhirnya kami berhasil membiasakan membaca peta lingkungan HDR, mengubahnya dari sama sisi menjadi peta kubik, dan mengeluarkan peta kubik HDR sebagai skybox di tempat kejadian. Selain itu, kode untuk mengkonversi ke peta kubik dengan merender ke enam wajah peta kubik berguna bagi kita lebih lanjut dalam tugas konvolusi peta lingkungan . Kode untuk seluruh proses konversi ada di sini .

Konvolusi kartu kubik


Seperti yang dikatakan di awal pelajaran, tujuan utama kami adalah untuk menyelesaikan integral untuk semua arah yang mungkin dari pencahayaan difus tidak langsung, dengan mempertimbangkan iradiasi yang diberikan dari pemandangan itu dalam bentuk peta kubik lingkungan. Diketahui bahwa kita bisa mendapatkan nilai kecerahan energi dari pemandangan L(p,wi) untuk arah yang sewenang-wenang wi dengan mengambil sampel dari HDR, sebuah peta kubik lingkungan ke arah itu. Untuk memecahkan integral, perlu untuk sampel kecerahan energi adegan dari semua arah yang mungkin di belahan bumi  Omega masing-masing fragmen yang ditinjau.
Jelas, tugas pengambilan sampel pencahayaan dari lingkungan dari semua kemungkinan arah di belahan bumi  Omega tidak praktis secara komputasi - ada banyak arah seperti itu. Namun, dimungkinkan untuk menerapkan perkiraan dengan mengambil sejumlah arah terbatas yang dipilih secara acak atau terletak secara seragam di dalam belahan bumi.Ini akan memungkinkan kami untuk mendapatkan perkiraan yang cukup baik untuk iradiasi sejati, yang pada dasarnya menyelesaikan integral yang menarik bagi kami dalam bentuk jumlah yang terbatas.

Tetapi untuk tugas waktu nyata, bahkan pendekatan seperti itu masih sangat dipaksakan, karena sampel diambil untuk setiap fragmen, dan jumlah sampel harus cukup tinggi untuk hasil yang dapat diterima. Dengan demikian, akan lebih baik untuk mempersiapkan terlebih dahulu data untuk langkah ini, di luar proses rendering. Karena orientasi belahan bumi menentukan dari wilayah mana kita menangkap iradiasi, maka dimungkinkan untuk menghitung terlebih dahulu iradiasi untuk setiap kemungkinan orientasi belahan bumi berdasarkan semua kemungkinan arah keluarw o :

L o ( p , ω o ) = k d cπΩLi(p,ωi)nωidωi



Akibatnya, untuk vektor yang diberikan sewenang-wenang w i , kita dapat sampel dari peta iradiasi yang dihitung untuk mendapatkan iradiasi difus dalam arah ini. Untuk menentukan besarnya radiasi difus tidak langsung pada titik fragmen saat ini, kami mengambil total iradiasi dari orientasi belahan bumi sepanjang normal ke permukaan fragmen. Dengan kata lain, mendapatkan iradiasi dari sebuah adegan datang ke seleksi sederhana:

  vec3 irradiance = texture(irradianceMap, N); 

Selanjutnya, untuk membuat peta iradiasi, perlu untuk melilitkan peta lingkungan, dikonversi menjadi peta kubik. Kita tahu bahwa untuk setiap fragmen belahannya dianggap berorientasi sepanjang normal ke permukaanN . Dalam hal ini, lilitan peta kubik dikurangi untuk menghitung jumlah rata-rata kecerahan energi dari semua arah w i dalam belahan bumiΩ berorientasi sepanjang normalN :


Untungnya, pekerjaan awal yang memakan waktu yang kami lakukan di awal pelajaran sekarang akan membuatnya cukup mudah untuk mengubah peta lingkungan menjadi peta kubik dalam shader fragmen khusus, output yang akan digunakan untuk membentuk peta kubik baru. Untuk ini, bagian kode yang digunakan untuk menerjemahkan peta lingkungan yang sama-persegi panjang menjadi peta kubik berguna.

Tetap hanya untuk mengambil shader pemrosesan lain:

 #version 330 core out vec4 FragColor; in vec3 localPos; uniform samplerCube environmentMap; const float PI = 3.14159265359; void main() { //       vec3 normal = normalize(localPos); vec3 irradiance = vec3(0.0); [...] //   FragColor = vec4(irradiance, 1.0); } 

Di sini, sampler environmentMap adalah peta lingkungan HDR kubik yang sebelumnya berasal dari yang sama sisi.

Ada banyak cara untuk berbelit-belit peta lingkungan.Dalam kasus ini, untuk setiap texel dari peta kubik, kita akan menghasilkan beberapa vektor sampel belahan bumiΩ , berorientasi sepanjang arah sampel, dan rata-rata hasilnya. Jumlah vektor sampel akan diperbaiki, dan vektor itu sendiri akan didistribusikan secara merata di dalam belahan bumi. Saya perhatikan bahwa integand adalah fungsi kontinu, dan perkiraan diskrit dari fungsi ini hanya akan menjadi perkiraan. Dan semakin banyak vektor pengambilan sampel yang kita ambil, semakin dekat dengan solusi analitis integral kita. Integrand ekspresi untuk reflektifitas tergantung pada sudut yang solid

d w - nilai yang dengannya sangat tidak nyaman untuk bekerja. Alih-alih mengintegrasikan lebih dari sudut yang solidd dari w kami mengubah ekspresi, yang mengarah ke integrasi dalam koordinat bolaθ dan ϕ :


Sudut Phi akan mewakili azimuth di bidang dasar belahan bumi, bervariasi dari 0 hingga 2 π . Sudut θ akan mewakili sudut elevasi, bervariasi dari 0 hingga12 π . Ekspresi yang dimodifikasi untuk reflektivitas dalam istilah-istilah tersebut adalah sebagai berikut:

L o ( p , ϕ o , θ o ) = k d cπ 2 π ϕ = 0 12 πθ=0Li(p,ϕi,θi)cos(θ)sin(θ)dϕdθ



Solusi integral seperti itu akan membutuhkan pengambilan sampel dalam jumlah terbatas di belahan bumi Ω dan rata-rata hasilnya. Mengetahui jumlah sampeln 1 dan n 2 untuk masing-masing koordinat bola, kita dapat menerjemahkan integral kejumlah Riemannian:

L o ( p , ϕ o , θ o ) = k d cπ 1n 1 n 2 n 1 ϕ=0 n 2 θ=0Li(p,ϕi,θi)cos(θ)sin(θ)dϕdθ


Karena kedua koordinat bola berubah secara terpisah, maka pada setiap saat, pengambilan sampel dilakukan dengan area rata-rata tertentu di belahan bumi, seperti yang dapat dilihat pada gambar di atas. Karena sifat permukaan bola, ukuran area pengambilan sampel diskrit pasti berkurang dengan meningkatnya sudut elevasiθ dan mendekati puncaknya. Untuk mengimbangi efek pengurangan area ini, kami menambahkan koefisien bobot pada ekspresis i n θ .

Akibatnya, penerapan pengambilan sampel diskrit di belahan bumi berdasarkan koordinat bola untuk setiap fragmen dalam bentuk kode adalah sebagai berikut:

 vec3 irradiance = vec3(0.0); vec3 up = vec3(0.0, 1.0, 0.0); vec3 right = cross(up, normal); up = cross(normal, right); float sampleDelta = 0.025; float nrSamples = 0.0; for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) { for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) { //   .   (  -) vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); //      vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); nrSamples++; } } irradiance = PI * irradiance * (1.0 / float(nrSamples)); 

SampleDelta variabel menentukan ukuran langkah diskrit di sepanjang permukaan belahan bumi. Dengan mengubah nilai ini, Anda dapat menambah atau mengurangi keakuratan hasil.

Di dalam kedua siklus, vektor sampel 3 dimensi biasa dibentuk dari koordinat bola, dipindahkan dari garis singgung ke ruang dunia, dan kemudian digunakan untuk sampel peta lingkungan kubik dari HDR. Hasil sampel diakumulasikan dalam variabel irradiansi , yang pada akhir pemrosesan akan dibagi dengan jumlah sampel yang dibuat untuk mendapatkan nilai rata-rata iradiasi. Perhatikan bahwa hasil pengambilan sampel dari tekstur dimodulasi oleh dua kuantitas: cos (theta) - untuk memperhitungkan redaman cahaya pada sudut besar, dan sin (theta)- untuk mengkompensasi pengurangan area sampel saat mendekati zenith.

Tetap hanya berurusan dengan kode yang merender dan menangkap hasil konvolusi peta lingkungan envCubemap . Pertama, buat peta kubik untuk menyimpan iradiasi (Anda harus melakukannya sekali, sebelum memasuki siklus render utama):

 unsigned int irradianceMap; glGenTextures(1, &irradianceMap); glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); for (unsigned int i = 0; i < 6; ++i) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 

Karena peta iradiasi diperoleh dengan rata-rata sampel yang terdistribusi secara merata dari kecerahan energi dari peta lingkungan, secara praktis tidak mengandung bagian dan elemen frekuensi tinggi - tekstur resolusi yang cukup kecil (32x32 di sini) dan penyaringan linier yang diaktifkan akan cukup untuk menyimpannya.

Selanjutnya, atur framebuffer pengambilan ke resolusi ini:

 glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32); 

Kode untuk menangkap hasil konvolusi mirip dengan kode untuk mentransfer peta lingkungan dari yang sama sisi ke yang kubik, hanya shader konvolusi yang digunakan:

 irradianceShader.use(); irradianceShader.setInt("environmentMap", 0); irradianceShader.setMat4("projection", captureProjection); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); //        glViewport(0, 0, 32, 32); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); for (unsigned int i = 0; i < 6; ++i) { irradianceShader.setMat4("view", captureViews[i]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderCube(); } glBindFramebuffer(GL_FRAMEBUFFER, 0); 

Setelah menyelesaikan tahap ini, kami akan memiliki peta iradiasi yang sudah dihitung sebelumnya di tangan kami yang dapat langsung digunakan untuk menghitung iluminasi difus tidak langsung. Untuk memeriksa bagaimana konvolusi berjalan, kami akan mencoba mengganti tekstur skybox dari peta lingkungan dengan peta iradiasi:


Jika, sebagai hasilnya, Anda melihat sesuatu yang tampak seperti peta lingkungan yang sangat buram, maka, kemungkinan besar, konvolusi berhasil.

PBR dan iluminasi tidak langsung


Peta iradiasi yang dihasilkan digunakan pada bagian difus dari ekspresi reflektifitas yang terbagi dan mewakili akumulasi kontribusi dari semua arah yang mungkin dari iluminasi tidak langsung. Karena dalam hal ini cahaya tidak berasal dari sumber tertentu, tetapi dari lingkungan secara keseluruhan, kami menganggap pencahayaan tidak langsung difus dan cermin sebagai latar belakang ( ambient ), menggantikan nilai konstan yang sebelumnya digunakan.

Untuk memulai, jangan lupa untuk menambahkan sampler baru dengan peta iradiasi:

 uniform samplerCube irradianceMap; 

Memiliki peta iradiasi yang menyimpan semua informasi tentang radiasi difusi tidak langsung dari pemandangan dan normal ke permukaan, memperoleh data tentang iradiasi fragmen tertentu semudah membuat satu sampel dari tekstur:

 // vec3 ambient = vec3(0.03); vec3 ambient = texture(irradianceMap, N).rgb; 

Namun, karena radiasi tidak langsung mengandung data untuk komponen difus dan cermin (seperti yang kita lihat dalam versi komponen dari ekspresi reflektifitas), kita perlu memodulasi komponen difus dengan cara khusus. Seperti dalam pelajaran sebelumnya, kami menggunakan ekspresi Fresnel untuk menentukan tingkat pantulan cahaya untuk permukaan tertentu, di mana kami memperoleh tingkat refraksi cahaya atau koefisien difus:

 vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0); vec3 kD = 1.0 - kS; vec3 irradiance = texture(irradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; vec3 ambient = (kD * diffuse) * ao; 

Karena pencahayaan latar belakang jatuh dari semua arah di belahan bumi berdasarkan yang normal ke permukaan N , tidak mungkin untuk menentukan satu-satunya median (setengah jalan)) vektor untuk menghitung koefisien Fresnel. Untuk mensimulasikan efek Fresnel dalam kondisi seperti itu, perlu untuk menghitung koefisien berdasarkan sudut antara normal dan vektor pengamatan. Namun, sebelumnya, sebagai parameter untuk menghitung koefisien Fresnel, kami menggunakan vektor median yang diperoleh atas dasar model microsurfaces dan tergantung pada kekasaran permukaan. Karena dalam kasus ini, kekasaran tidak termasuk dalam parameter perhitungan, tingkat pantulan cahaya oleh permukaan akan selalu ditaksir terlalu tinggi. Pencahayaan tidak langsung secara keseluruhan harus berperilaku sama dengan pencahayaan langsung, yaitu dari permukaan kasar kami mengharapkan tingkat pantulan yang lebih rendah di tepinya. Tapi karena kekasarannya tidak diperhitungkan,maka tingkat pantulan specular menurut Fresnel untuk penerangan tidak langsung terlihat tidak realistis pada permukaan non-logam yang kasar (pada gambar di bawah, efek yang dijelaskan berlebihan untuk kejelasan yang lebih besar):


Anda dapat mengatasi gangguan ini dengan memperkenalkan kekasaran ke dalam ekspresi Fremlin-Schlick, sebuah proses yang dijelaskan oleh Sébastien Lagarde :

 vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); } 

Mengingat kekasaran permukaan saat menghitung set Fresnel, kode untuk menghitung komponen latar belakang mengambil bentuk berikut:

 vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); vec3 kD = 1.0 - kS; vec3 irradiance = texture(irradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; vec3 ambient = (kD * diffuse) * ao; 

Ternyata, penggunaan pencahayaan berbasis gambar pada dasarnya bermuara pada satu sampel dari peta kubik. Semua kesulitan terutama terkait dengan persiapan awal dan transfer peta lingkungan ke peta iradiasi.

Mengambil pemandangan yang akrab dari pelajaran tentang sumber cahaya analitik yang mengandung berbagai bola dengan berbagai tingkat logam dan kekasaran, dan menambahkan pencahayaan latar belakang difus dari lingkungan, Anda mendapatkan sesuatu seperti ini:


Ini masih terlihat aneh, karena bahan dengan tingkat logam yang tinggi masih memerlukan refleksi agar benar-benar terlihat, hmm, logam (bagaimana pun juga, logam tidak memantulkan pencahayaan difus). Dan dalam hal ini, satu-satunya refleksi diperoleh dari sumber cahaya analitik titik. Namun, sekarang kita dapat mengatakan bahwa bola terlihat lebih terbenam di lingkungan (terutama terlihat ketika beralih peta lingkungan), karena permukaan sekarang merespons dengan benar pencahayaan latar belakang dari lingkungan pemandangan.

Kode sumber lengkap untuk pelajaran ada di sini.. Dalam pelajaran berikutnya, kita akhirnya akan membahas bagian kedua dari ekspresi reflektifitas, yang bertanggung jawab atas pencahayaan specular tidak langsung. Setelah langkah ini, Anda akan benar-benar merasakan kekuatan pendekatan PBR dalam pencahayaan.

Bahan tambahan


  • Coding Labs: Render berbasis fisik : pengantar model PBR bersama dengan penjelasan tentang bagaimana peta irradiance dibangun dan mengapa.
  • The Matematika Shading : Tinjauan singkat oleh ScratchAPixel pada beberapa teknik matematika yang digunakan dalam pelajaran ini, khususnya tentang koordinat kutub dan integral.

PS : Kami punya telegram conf untuk koordinasi transfer. Jika Anda memiliki keinginan serius untuk membantu penerjemahan, maka Anda dipersilakan!

Source: https://habr.com/ru/post/id426987/


All Articles