
Pada artikel sebelumnya, kami menggunakan pencahayaan langsung (penerusan maju atau penerusan ke depan) . Ini adalah pendekatan sederhana di mana kita menggambar objek dengan memperhitungkan semua sumber cahaya, lalu menggambar objek berikutnya bersama-sama dengan semua pencahayaan di atasnya, dan seterusnya untuk setiap objek. Ini cukup sederhana untuk dipahami dan diimplementasikan, tetapi pada saat yang sama ternyata agak lambat dari sudut pandang kinerja: untuk setiap objek Anda harus memilah-milah semua sumber cahaya. Selain itu, pencahayaan langsung bekerja secara tidak efisien pada adegan dengan sejumlah besar objek saling tumpang tindih, karena sebagian besar penghitungan piksel shader tidak berguna dan akan ditimpa dengan nilai untuk objek yang lebih dekat.
Pencahayaan yang ditangguhkan atau bayangan yang ditangguhkan atau rendering yang ditangguhkan memintas masalah ini dan secara dramatis mengubah cara kita menggambar objek. Ini memberi peluang baru untuk mengoptimalkan adegan secara signifikan dengan sejumlah besar sumber cahaya, memungkinkan Anda untuk menggambar ratusan bahkan ribuan sumber cahaya dengan kecepatan yang dapat diterima. Di bawah ini adalah adegan dengan 1847 titik sumber cahaya ditarik menggunakan pencahayaan yang ditangguhkan (gambar milik Hannes Nevalainen). Sesuatu seperti ini tidak mungkin dilakukan dengan perhitungan pencahayaan langsung:

Ide pencahayaan yang ditangguhkan adalah bahwa kita menunda bagian yang paling kompleks secara komputasional (seperti pencahayaan) untuk nanti. Pencahayaan yang ditangguhkan terdiri dari dua lintasan: pada lintasan pertama, lintasan geometri (lintasan geometri) , seluruh adegan diambil dan berbagai informasi disimpan dalam satu set tekstur yang disebut buffer-G. Misalnya: posisi, warna, normals dan / atau mirroring permukaan untuk setiap piksel. Informasi grafis yang disimpan dalam buffer-G kemudian digunakan untuk menghitung pencahayaan. Berikut ini adalah isi buffer-G untuk satu frame:

Pada lintasan kedua, yang disebut lintasan pencahayaan, kami menggunakan tekstur dari buffer-G ketika kami menggambar persegi panjang layar penuh. Alih-alih menggunakan vertex dan shader fragmen secara terpisah untuk setiap objek, kami menggambar seluruh adegan piksel demi piksel. Perhitungan pencahayaan tetap persis sama dengan dengan lintasan langsung, tetapi kami mengambil data yang diperlukan hanya dari buffer-G dan shader variabel (seragam) , dan bukan dari vertex shader.
Gambar di bawah ini menunjukkan proses menggambar umum dengan baik.

Keuntungan utama adalah bahwa informasi yang disimpan dalam buffer-G milik fragmen terdekat yang tidak dikaburkan oleh apa pun: tes kedalaman hanya menyisakan mereka. Berkat ini, kami menghitung pencahayaan untuk setiap piksel hanya sekali, tanpa melakukan terlalu banyak pekerjaan. Selain itu, pencahayaan yang ditangguhkan memberi kita peluang untuk optimalisasi lebih lanjut, memungkinkan kita untuk menggunakan lebih banyak sumber cahaya daripada pencahayaan langsung.
Namun, ada beberapa kelemahan: G-buffer menyimpan sejumlah besar informasi tentang adegan. Selain itu, data tipe posisi harus disimpan dengan akurasi tinggi, akibatnya, buffer-G memakan cukup banyak ruang memori. Kelemahan lain adalah bahwa kita tidak akan dapat menggunakan objek yang tembus cahaya (karena buffer menyimpan informasi hanya untuk permukaan terdekat) dan anti-aliasing seperti MSAA tidak akan berfungsi baik. Ada beberapa solusi untuk mengatasi masalah ini, mereka dibahas di akhir artikel.
(Note lane. - Buffer-G memakan banyak ruang memori. Misalnya, untuk layar 1920 * 1080 dan menggunakan 128 bit per pixel, buffer akan membutuhkan 33mb. Ada persyaratan yang berkembang untuk bandwidth memori - ada lebih banyak data yang ditulis dan dibaca)
G-buffer
G-buffer mengacu pada tekstur yang digunakan untuk menyimpan informasi terkait pencahayaan yang digunakan dalam render pass terakhir. Mari kita lihat informasi apa yang kita butuhkan untuk menghitung pencahayaan untuk rendering langsung:
- Vektor posisi 3D: digunakan untuk mengetahui posisi fragmen relatif terhadap kamera dan sumber cahaya.
- Warna difus fragmen (reflektifitas untuk merah, hijau dan biru - secara umum, warna).
- Vektor normal 3d (untuk menentukan pada sudut apa cahaya jatuh di permukaan)
- mengapung untuk menyimpan komponen cermin
- Posisi sumber cahaya dan warnanya.
- Posisi kamera.
Dengan menggunakan variabel-variabel ini, kita dapat menghitung cakupan menggunakan model Blinn-Fong yang sudah kita ketahui. Warna dan posisi sumber cahaya, serta posisi kamera dapat menjadi variabel umum, tetapi nilai sisanya akan berbeda untuk setiap fragmen gambar. Jika kita mengirimkan data yang persis sama ke dalam lintasan akhir dari pencahayaan yang ditangguhkan, yang akan kita gunakan untuk lintasan langsung, kita akan mendapatkan hasil yang sama, meskipun kita akan menggambar fragmen pada persegi panjang 2D biasa.
OpenGL tidak memiliki batasan pada apa yang dapat kita simpan dalam suatu tekstur, jadi masuk akal untuk menyimpan semua informasi dalam satu atau lebih tekstur ukuran layar (disebut buffer-G) dan menggunakannya semua dalam lintasan penerangan. Karena ukuran tekstur dan layarnya sama, kami mendapatkan data input yang sama seperti pada pencahayaan langsung.
Dalam pseudo-code, gambaran umum terlihat seperti ini:
while(...) // render loop { // 1. : / g- glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gBufferShader.use(); for(Object obj : Objects) { ConfigureShaderTransformsAndUniforms(); obj.Draw(); } // 2. : g- glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT); lightingPassShader.use(); BindAllGBufferTextures(); SetLightingUniforms(); RenderQuad(); }
Informasi yang diperlukan untuk setiap piksel: vektor posisi , vektor normal , vektor warna , dan nilai untuk komponen cermin . Dalam lintasan geometris, kami menggambar semua objek dalam adegan dan menyimpan semua data ini dalam buffer-G. Kita dapat menggunakan beberapa target render untuk mengisi semua buffer dalam satu seri, pendekatan ini dibahas dalam artikel sebelumnya tentang implementasi cahaya: Bloom , terjemahan pada hub
Untuk lintasan geometris, buat framebuffer dengan nama yang jelas gBuffer, yang akan kami lampirkan beberapa buffer warna dan satu buffer kedalaman. Untuk menyimpan posisi dan normals, lebih disukai menggunakan tekstur dengan akurasi tinggi (nilai float 16 atau 32-bit untuk setiap komponen), kami akan menyimpan warna difus dan nilai specular dalam tekstur secara default (8 bit per akurasi komponen).
unsigned int gBuffer; glGenFramebuffers(1, &gBuffer); glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); unsigned int gPosition, gNormal, gColorSpec; // glGenTextures(1, &gPosition); glBindTexture(GL_TEXTURE_2D, gPosition); 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_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0); // glGenTextures(1, &gNormal); glBindTexture(GL_TEXTURE_2D, gNormal); 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_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0); // + glGenTextures(1, &gAlbedoSpec); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0); // OpenGL, unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments); // . [...]
Karena kami menggunakan beberapa tujuan render, kami harus secara eksplisit memberi tahu OpenGL yang mana buffer dari GBuffer terlampir yang akan kami gambar di glDrawBuffers()
. Perlu juga dicatat bahwa kami menyimpan posisi dan normalnya masing-masing memiliki 3 komponen, dan kami menyimpannya dalam tekstur RGB. Tetapi pada saat yang sama, kami segera memasukkan tekstur RGBA yang sama baik warna dan koefisien refleksi specular - berkat ini kami menggunakan satu buffer kurang. Jika implementasi rendering yang ditangguhkan Anda menjadi lebih kompleks dan menggunakan lebih banyak data, Anda dapat dengan mudah menemukan cara baru untuk menggabungkan data dan mengaturnya dalam tekstur.
Di masa depan, kita harus merender data ke buffer-G. Jika setiap objek memiliki koefisien refleksi warna, normal, dan specular, kita dapat menulis sesuatu seperti shader berikut:
#version 330 core layout (location = 0) out vec3 gPosition; layout (location = 1) out vec3 gNormal; layout (location = 2) out vec4 gAlbedoSpec; in vec2 TexCoords; in vec3 FragPos; in vec3 Normal; uniform sampler2D texture_diffuse1; uniform sampler2D texture_specular1; void main() { // G- gPosition = FragPos; // G- gNormal = normalize(Normal); // gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb; // gAlbedoSpec.a = texture(texture_specular1, TexCoords).r; }
Karena kami menggunakan beberapa tujuan render, dengan bantuan layout
menunjukkan apa dan dalam buffer apa dari framebuffer saat ini kami render. Harap dicatat bahwa kami tidak menyimpan koefisien cermin dalam buffer terpisah, karena kami dapat menyimpan nilai float di saluran alfa salah satu buffer.
Ingatlah bahwa saat menghitung pencahayaan, sangat penting untuk menyimpan semua variabel dalam ruang koordinat yang sama, dalam hal ini kami menyimpan (dan melakukan perhitungan) di ruang dunia.
Jika sekarang kita merender beberapa nanosuit menjadi buffer-G dan menggambar kontennya dengan memproyeksikan setiap buffer ke seperempat layar, kita akan melihat sesuatu seperti ini:

Coba visualisasikan posisi dan vektor normal dan pastikan semuanya benar. Misalnya, vektor normal yang menunjuk ke kanan akan berwarna merah. Begitu pula dengan benda-benda yang terletak di sebelah kanan tengah pemandangan. Setelah Anda puas dengan isi buffer-G, mari beralih ke bagian selanjutnya: bagian pencahayaan.
Bagian pencahayaan
Sekarang kami memiliki sejumlah besar informasi dalam buffer-G, kami dapat sepenuhnya menghitung warna pencahayaan dan akhir untuk setiap piksel buffer-G, menggunakan kontennya sebagai input untuk algoritma perhitungan pencahayaan. Karena nilai buffer G hanya mewakili fragmen yang terlihat, kami akan melakukan perhitungan pencahayaan kompleks tepat satu kali untuk setiap piksel. Karena hal ini, pencahayaan yang ditangguhkan cukup efektif, terutama dalam pemandangan yang kompleks, di mana, ketika merender secara langsung untuk setiap piksel, seringkali perlu untuk menghitung pencahayaan beberapa kali.
Untuk penerangan, kita akan membuat persegi panjang layar penuh (sedikit seperti efek post-processing) dan melakukan perhitungan pencahayaan yang lambat untuk setiap piksel.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gPosition); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gNormal); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); // shaderLightingPass.use(); SendAllLightUniformsToShader(shaderLightingPass); shaderLightingPass.setVec3("viewPos", camera.Position); RenderQuad();
Kami mengikat semua tekstur buffer-G yang diperlukan sebelum rendering dan, di samping itu, mengatur nilai variabel terkait pencahayaan di shader.
Bagian shader fragmen sangat mirip dengan yang kami gunakan dalam pelajaran pertemuan. Pada dasarnya baru adalah cara kita mendapatkan input untuk penerangan langsung dari buffer-G.
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D gAlbedoSpec; struct Light { vec3 Position; vec3 Color; }; const int NR_LIGHTS = 32; uniform Light lights[NR_LIGHTS]; uniform vec3 viewPos; void main() { // G- vec3 FragPos = texture(gPosition, TexCoords).rgb; vec3 Normal = texture(gNormal, TexCoords).rgb; vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb; float Specular = texture(gAlbedoSpec, TexCoords).a; // vec3 lighting = Albedo * 0.1; // vec3 viewDir = normalize(viewPos - FragPos); for(int i = 0; i < NR_LIGHTS; ++i) { // vec3 lightDir = normalize(lights[i].Position - FragPos); vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color; lighting += diffuse; } FragColor = vec4(lighting, 1.0); }
Shader pencahayaan menerima 3 tekstur yang berisi semua informasi yang direkam dalam lintasan geometris dan yang terdiri dari G-buffer. Jika kita mengambil input untuk penerangan dari tekstur, kita mendapatkan nilai yang persis sama dengan rendering langsung normal. Di awal fragmen shader, kami mendapatkan nilai yang terkait dengan variabel pencahayaan dengan hanya membaca dari tekstur. Perhatikan bahwa kita mendapatkan warna dan koefisien pantulan specular dari satu tekstur - gAlbedoSpec
.
Karena untuk setiap fragmen ada nilai (serta variabel shader seragam) yang diperlukan untuk menghitung pencahayaan sesuai dengan model Blinn-Fong, kita tidak perlu mengubah kode perhitungan pencahayaan. Satu-satunya hal yang telah diubah adalah cara untuk mendapatkan nilai input.
Memulai demo sederhana dengan 32 sumber cahaya kecil terlihat seperti ini:

Salah satu kelemahan dari pencahayaan yang ditangguhkan adalah ketidakmungkinan pencampuran, karena semua g-buffer untuk setiap piksel berisi informasi tentang hanya satu permukaan, sementara pencampuran menggunakan kombinasi beberapa fragmen. (Blending) , terjemahan . Kerugian lain dari pencahayaan yang ditangguhkan adalah ia memaksa Anda untuk menggunakan satu metode umum untuk menghitung pencahayaan untuk semua objek; meskipun keterbatasan ini entah bagaimana dapat dielakkan dengan menambahkan informasi material ke buffer-g.
Untuk mengatasi kekurangan ini (terutama kurangnya pencampuran), mereka sering membagi rendering menjadi dua bagian: rendering dengan pencahayaan yang ditangguhkan, dan bagian kedua dengan rendering langsung yang dimaksudkan untuk menerapkan sesuatu ke tempat kejadian atau menggunakan shader yang tidak kompatibel dengan pencahayaan yang ditangguhkan. (Catatan. Dari contoh-contoh: menambahkan asap tembus, api, kaca) Untuk menggambarkan pekerjaan, kita akan menggambar sumber cahaya sebagai kubus kecil menggunakan rendering langsung, karena kubus pencahayaan memerlukan shader khusus (mereka bersinar seragam dengan warna yang sama).
Gabungkan render yang ditangguhkan dengan langsung.
Misalkan kita ingin menggambar setiap sumber cahaya dalam bentuk kubus 3D dengan pusat bertepatan dengan posisi sumber cahaya dan memancarkan cahaya dengan warna sumber. Gagasan pertama yang muncul dalam pikiran adalah untuk secara langsung memberikan kubus untuk setiap sumber cahaya di atas hasil rendering yang ditangguhkan. Artinya, kami menggambar kubus seperti biasa, tetapi hanya setelah rendering ditangguhkan. Kode akan terlihat seperti ini:
// [...] RenderQuad(); // shaderLightBox.use(); shaderLightBox.setMat4("projection", projection); shaderLightBox.setMat4("view", view); for (unsigned int i = 0; i < lightPositions.size(); i++) { model = glm::mat4(); model = glm::translate(model, lightPositions[i]); model = glm::scale(model, glm::vec3(0.25f)); shaderLightBox.setMat4("model", model); shaderLightBox.setVec3("lightColor", lightColors[i]); RenderCube(); }
Kubus yang diberikan ini tidak memperhitungkan nilai kedalaman akun dari rendering yang ditangguhkan dan sebagai hasilnya selalu digambarkan di atas objek yang telah diberikan: ini bukan tujuan kami.

Pertama kita perlu menyalin informasi kedalaman dari bagian geometri ke buffer kedalaman, dan hanya setelah itu menggambar kubus bercahaya. Dengan demikian, potongan kubus bercahaya akan ditarik hanya jika mereka lebih dekat daripada benda yang sudah ditarik.
Kami dapat menyalin konten framebuffer ke framebuffer lain menggunakan fungsi glBlitFramebuffer
. Kami telah menggunakan fungsi ini dalam contoh anti-aliasing : ( anti-aliasing ), terjemahan . Fungsi glBlitFramebuffer
menyalin bagian framebuffer yang ditentukan pengguna ke bagian yang ditentukan dari framebuffer lain.
Untuk objek yang digambar dalam bagian pencahayaan yang ditangguhkan, kami menyimpan kedalaman di g-buffer objek framebuffer. Jika kita cukup menyalin isi buffer kedalaman g-buffer ke buffer kedalaman default, kubus bercahaya akan ditarik seolah-olah seluruh geometri adegan digambar menggunakan pass rendering langsung. Seperti yang dijelaskan secara singkat dalam contoh anti-aliasing, kita perlu mengatur pembingkai untuk membaca dan menulis:
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // - glBlitFramebuffer( 0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST ); glBindFramebuffer(GL_FRAMEBUFFER, 0); // [...]
Di sini kita menyalin seluruh isi buffer kedalaman framebuffer ke buffer kedalaman default (Jika perlu, Anda dapat menyalin buffer warna atau buffer stensil dengan cara yang sama). Jika kita sekarang membuat kubus yang bercahaya, mereka akan digambar seolah-olah geometri adegan itu nyata (meskipun digambarkan sebagai sederhana).

Kode sumber demo dapat ditemukan di sini .
Dengan pendekatan ini, kita dapat dengan mudah menggabungkan rendering yang ditangguhkan dengan rendering langsung. Ini luar biasa, karena kita dapat menerapkan objek pencampuran dan menggambar yang memerlukan shader khusus yang tidak berlaku untuk rendering yang ditangguhkan.
Lebih banyak sumber cahaya
Pencahayaan yang ditangguhkan sering dipuji karena mampu menggambar sejumlah besar sumber cahaya tanpa penurunan kinerja yang signifikan. Pencahayaan yang tertunda saja tidak memungkinkan kita untuk menggambar sejumlah besar sumber cahaya, karena kita masih harus menghitung kontribusi semua sumber cahaya untuk setiap piksel. Untuk menggambar sejumlah besar sumber cahaya, optimasi yang sangat indah digunakan, berlaku untuk rendering yang ditangguhkan - bidang aksi sumber cahaya. (volume ringan)
Biasanya, ketika kita menggambar fragmen dalam adegan yang sangat terang, kita memperhitungkan kontribusi setiap sumber cahaya pada adegan, terlepas dari jaraknya ke fragmen. Jika sebagian besar sumber cahaya tidak akan pernah memengaruhi fragmen, mengapa kita membuang waktu untuk menghitungnya?
Gagasan ruang lingkup sumber cahaya adalah menemukan jari-jari (atau volume) sumber cahaya - yaitu, area di mana cahaya dapat mencapai permukaan. Karena sebagian besar sumber cahaya menggunakan semacam pelemahan, kita dapat menemukan jarak maksimum (jari-jari) yang dapat dijangkau cahaya. Setelah itu, kami melakukan perhitungan pencahayaan kompleks hanya untuk sumber cahaya yang memengaruhi fragmen ini. Ini menyelamatkan kita dari sejumlah besar perhitungan, karena kita hanya menghitung pencahayaan di tempat yang dibutuhkan.
Dengan pendekatan ini, trik utamanya adalah menentukan ukuran area sumber cahaya.
Perhitungan ruang lingkup sumber cahaya (radius)
Untuk mendapatkan jari-jari sumber cahaya, kita harus menyelesaikan persamaan redaman untuk kecerahan, yang kita anggap gelap - bisa 0,0 atau lebih terang, tapi masih gelap: misalnya, 0,03. Untuk mendemonstrasikan cara menghitung jari-jari, kita akan menggunakan salah satu fungsi atenuasi paling kompleks dan paling umum dari contoh light caster
Kami ingin menyelesaikan persamaan ini untuk kasus kapan , yaitu, ketika sumber cahaya benar-benar gelap. Namun, persamaan ini tidak akan pernah mencapai nilai tepat 0,0, jadi tidak ada solusi. Namun, kita bisa menyelesaikan persamaan untuk kecerahan untuk nilai mendekati 0,0, yang dapat dianggap hampir gelap. Dalam contoh ini, kami menganggap dapat diterima nilai kecerahan di - dibagi dengan 256, karena framebuffer 8-bit dapat berisi 256 nilai kecerahan yang berbeda.
Fungsi atenuasi yang dipilih menjadi hampir gelap pada jarak jangkauan, jika kita membatasi kecerahannya lebih rendah dari 5/256, maka kisaran sumber cahaya akan menjadi terlalu besar - ini tidak begitu efektif. Idealnya, seseorang seharusnya tidak melihat garis tajam cahaya yang tiba-tiba dari sumber cahaya. Tentu saja, ini tergantung pada jenis pemandangan, nilai yang lebih besar dari kecerahan minimum memberikan area aksi sumber cahaya yang lebih kecil dan meningkatkan efisiensi perhitungan, tetapi dapat menyebabkan artefak yang terlihat dalam gambar: pencahayaan akan tiba-tiba putus di perbatasan area aksi sumber cahaya.
Persamaan atenuasi yang harus kita pecahkan menjadi:
Di sini - komponen cahaya paling terang (dari saluran r, g, b). Kami akan menggunakan komponen yang paling terang, karena komponen lainnya akan memberikan batasan yang lebih lemah pada ruang lingkup sumber cahaya.
Kami terus memecahkan persamaan:
Persamaan terakhir adalah persamaan kuadrat dalam bentuk dengan solusi berikut:
Kami mendapat persamaan umum yang memungkinkan kami untuk mengganti parameter (pelemahan konstan, koefisien linier dan kuadratik) untuk menemukan x - jari-jari sumber cahaya.
float constant = 1.0; float linear = 0.7; float quadratic = 1.8; float lightMax = std::fmaxf(std::fmaxf(lightColor.r, lightColor.g), lightColor.b); float radius = (-linear + std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * lightMax))) / (2 * quadratic);
Formula mengembalikan radius antara sekitar 1,0 dan 5,0 tergantung pada kecerahan maksimum sumber cahaya.
Kami menemukan jari-jari ini untuk setiap sumber cahaya di atas panggung dan menggunakannya untuk memperhitungkan hanya sumber-sumber cahaya di mana ia berada dalam lingkup masing-masing fragmen. Di bawah ini adalah bagian redone pencahayaan yang memperhitungkan area aksi sumber cahaya. Harap dicatat bahwa pendekatan ini hanya diterapkan untuk tujuan pendidikan dan tidak cocok untuk penggunaan praktis (kami akan segera membahas alasannya).
struct Light { [...] float Radius; }; void main() { [...] for(int i = 0; i < NR_LIGHTS; ++i) { // float distance = length(lights[i].Position - FragPos); if(distance < lights[i].Radius) { // [...] } } }
Hasilnya persis sama dengan sebelumnya, tetapi sekarang untuk setiap sumber cahaya efeknya diperhitungkan hanya dalam bidang aksinya.
Kode terakhir adalah demo. .
Aplikasi nyata dari ruang lingkup sumber cahaya.
Shader fragmen yang ditunjukkan di atas tidak akan berfungsi dalam praktiknya dan hanya berfungsi untuk menggambarkan bagaimana kita dapat menyingkirkan perhitungan pencahayaan yang tidak perlu. Pada kenyataannya, kartu video dan bahasa shader GLSL sangat mengoptimalkan loop dan cabang. Alasan untuk ini adalah bahwa pelaksanaan shader pada kartu video dilakukan secara paralel untuk piksel yang berbeda, dan banyak arsitektur memaksakan batasan bahwa dalam eksekusi paralel utas yang berbeda harus menghitung shader yang sama. Seringkali ini mengarah pada fakta bahwa shader yang berjalan selalu menghitung semua cabang sehingga semua shader bekerja pada waktu yang sama. (Catatan jalur. Ini tidak mempengaruhi hasil perhitungan, tetapi dapat mengurangi kinerja shader.) Karena ini, mungkin ternyata pemeriksaan jari-jari kami tidak berguna: kami masih akan menghitung pencahayaan untuk semua sumber!
Pendekatan yang cocok untuk menggunakan ruang lingkup cahaya adalah membuat bidang dengan jari-jari seperti sumber cahaya. Pusat bola bertepatan dengan posisi sumber cahaya, sehingga bola itu sendiri memiliki kisaran aksi sumber cahaya. Ada sedikit trik di sini - kami pada dasarnya menggunakan shader fragmen yang ditangguhkan yang sama untuk menggambar bola. Saat menggambar sebuah bola, shader fragmen dipanggil khusus untuk piksel yang dipengaruhi oleh sumber cahaya, kami hanya membuat piksel yang diperlukan dan melewatkan yang lainnya. :

, . , , . _*__
_ + __ , .
: ( ) , , , - ( ). stenil .
, , , . ( ) : c (deferred lighting) (tile-based deferred shading) . MSAA. .
vs
( ) - , , . , β , MSAA, .
( ), ( g- ..) . , .
: , , , . , , , . . parallax mapping, , . , .
PS - . , !