Apakah ada cara untuk melewati sejumlah lokasi cahaya (dan warna) yang sewenang-wenang untuk fragmen shader, dan memutarnya di shader?
Jika tidak, lalu bagaimana beberapa lampu seharusnya disimulasikan? Misalnya berkenaan dengan pencahayaan directional difus, Anda tidak bisa hanya melewatkan sejumlah bobot cahaya untuk shader.
Jawaban:
Biasanya ada dua metode untuk menangani ini. Saat ini, mereka disebut rendering maju dan rendering ditangguhkan. Ada satu variasi pada keduanya yang akan saya bahas di bawah ini.
Penerusan ke depan
Berikan setiap objek satu kali untuk setiap cahaya yang mempengaruhinya. Ini termasuk cahaya sekitar. Anda menggunakan mode campuran aditif (
glBlendFunc(GL_ONE, GL_ONE)
), sehingga kontribusi setiap lampu ditambahkan satu sama lain. Karena kontribusi lampu yang berbeda bersifat aditif, framebuffer akhirnya mendapatkan nilaiAnda bisa mendapatkan HDR dengan merender ke framebuffer floating-point. Anda kemudian mengambil lintasan terakhir adegan untuk menurunkan skala nilai pencahayaan HDR ke rentang yang terlihat; ini juga akan menjadi tempat Anda menerapkan bloom dan efek pasca lainnya.
Peningkatan kinerja umum untuk teknik ini (jika adegan memiliki banyak objek) adalah dengan menggunakan "pra-pass", di mana Anda membuat semua objek tanpa menggambar apa pun ke framebuffer warna (gunakan
glColorMask
untuk mematikan penulisan warna). Ini hanya mengisi buffer kedalaman. Dengan cara ini, jika Anda merender objek yang berada di belakang objek lain, GPU dapat dengan cepat melewati bagian-bagian itu. Itu masih harus menjalankan vertex shader, tetapi dapat melewati perhitungan fragmen shader yang biasanya lebih mahal.Ini lebih mudah untuk dikodekan dan lebih mudah divisualisasikan. Dan pada beberapa perangkat keras (terutama GPU seluler dan tertanam), ini bisa lebih efisien daripada alternatifnya. Tetapi pada perangkat keras kelas atas, alternatif umumnya menang untuk adegan dengan banyak lampu.
Render yang ditangguhkan
Render yang ditangguhkan sedikit lebih rumit.
Persamaan pencahayaan yang Anda gunakan untuk menghitung cahaya untuk suatu titik pada permukaan menggunakan parameter permukaan berikut:
Dalam rendering maju, parameter ini sampai ke fungsi pencahayaan fragmen shader baik dengan dilewatkan langsung dari vertex shader, ditarik dari tekstur (biasanya melalui koordinat tekstur yang dilewatkan dari vertex shader), atau dihasilkan dari seluruh kain dalam fragmen shader berdasarkan pada parameter lainnya. Warna difus dapat dihitung dengan menggabungkan warna per-simpul dengan tekstur, menggabungkan beberapa tekstur, apa pun.
Dalam rendering yang ditangguhkan, kami menjadikan ini semua eksplisit. Pada pass pertama, kami me-render semua objek. Tapi kami tidak membuat warna . Sebagai gantinya, kami membuat parameter permukaan . Jadi setiap piksel pada layar memiliki satu set parameter permukaan. Ini dilakukan melalui rendering ke tekstur di luar layar. Satu tekstur akan menyimpan warna difus sebagai RGB-nya, dan mungkin kilau specular sebagai alfa. Tekstur lain akan menyimpan warna specular. Yang ketiga akan menyimpan yang normal. Dan seterusnya.
Posisi ini biasanya tidak disimpan. Alih-alih disusun kembali dalam lulus kedua dengan matematika yang terlalu rumit untuk masuk ke sini. Cukuplah untuk mengatakan, kita menggunakan buffer kedalaman dan posisi fragmen ruang layar sebagai input untuk mengetahui posisi ruang kamera dari titik di permukaan.
Jadi, sekarang karena tekstur ini pada dasarnya menyimpan semua informasi permukaan untuk setiap piksel yang terlihat dalam adegan, kami mulai membuat paha depan layar penuh. Setiap lampu mendapat render layar penuh quad. Kami sampel dari tekstur parameter permukaan (dan menyusun kembali posisi), lalu gunakan saja untuk menghitung kontribusi cahaya itu. Ini ditambahkan (lagi
glBlendFunc(GL_ONE, GL_ONE)
) ke gambar. Kami terus melakukan ini sampai kami kehabisan lampu.HDR lagi adalah langkah pasca-proses.
Kelemahan terbesar dari rendering yang ditangguhkan adalah antialiasing. Dibutuhkan sedikit lebih banyak kerja untuk antialias dengan benar.
Keuntungan terbesarnya, jika GPU Anda memiliki banyak bandwidth memori, adalah kinerja. Kami hanya merender geometri aktual satu kali (atau 1 + 1 per cahaya yang memiliki bayangan, jika kami melakukan pemetaan bayangan). Kami tidak pernah menghabiskan waktu pada piksel atau geometri tersembunyi yang tidak terlihat setelah ini. Semua waktu berlalu pencahayaan dihabiskan untuk hal-hal yang benar-benar terlihat.
Jika GPU Anda tidak memiliki banyak bandwidth memori, maka pass cahaya benar-benar dapat mulai sakit. Menarik dari 3-5 tekstur per piksel layar tidak menyenangkan.
Pra-Lintasan Ringan
Ini adalah semacam variasi pada rendering yang ditangguhkan yang memiliki tradeoff yang menarik.
Seperti halnya dalam rendering yang ditangguhkan, Anda membuat parameter permukaan Anda ke sejumlah buffer. Namun, Anda telah menyingkat data permukaan; satu-satunya data permukaan yang Anda pedulikan saat ini adalah nilai buffer kedalaman (untuk merekonstruksi posisi), normal, dan kekakuan specular.
Kemudian untuk setiap cahaya, Anda hanya menghitung hasil pencahayaan. Tidak ada multiplikasi dengan warna permukaan, tidak ada. Hanya titik (N, L), dan istilah specular, sepenuhnya tanpa warna permukaan. Istilah specular dan difus harus disimpan dalam buffer terpisah. Istilah specular dan difus untuk setiap cahaya dirangkum dalam dua buffer.
Kemudian, Anda merender ulang geometri, menggunakan perhitungan pencahayaan specular total dan difus untuk melakukan kombinasi akhir dengan warna permukaan, sehingga menghasilkan keseluruhan pantulan.
Sisi baiknya di sini adalah Anda mendapatkan multisampling kembali (setidaknya, lebih mudah daripada dengan ditangguhkan). Anda melakukan rendering per objek lebih sedikit daripada rendering maju. Tetapi hal utama yang ditangguhkan adalah bahwa ini menyediakan waktu yang lebih mudah untuk memiliki persamaan pencahayaan yang berbeda untuk permukaan yang berbeda.
Dengan rendering yang ditangguhkan, Anda biasanya menggambar seluruh adegan dengan shader per-light yang sama. Jadi setiap objek harus menggunakan parameter material yang sama. Dengan pra-lintasan cahaya, Anda dapat memberi masing-masing objek shader yang berbeda, sehingga dapat melakukan langkah pencahayaan terakhir sendiri.
Ini tidak memberikan kebebasan sebanyak kasus rendering maju. Tetapi itu masih lebih cepat jika Anda memiliki bandwidth tekstur untuk cadangan.
sumber
invariant
kata kunci untuk menjaminnya untuk kasus lain).Anda perlu menggunakan pencahayaan yang ditangguhkan atau pencahayaan pra-lulus . Beberapa pipa fungsi tetap lama (baca: tidak ada shader) mendukung hingga 16 atau 24 lampu - tetapi hanya itu . Render yang ditangguhkan menghilangkan batas cahaya; tetapi dengan biaya sistem rendering yang jauh lebih rumit.
Rupanya WebGL mendukung MRT yang mutlak diperlukan untuk segala bentuk penangguhan render - sehingga bisa dilakukan; Saya hanya tidak yakin betapa masuk akalnya itu.
Atau Anda dapat menyelidiki Unity 5 - yang telah menunda rendering langsung dari kotak.
Cara sederhana lain untuk mengatasinya adalah dengan hanya memprioritaskan lampu (mungkin, berdasarkan jarak dari pemain dan apakah mereka ada di kamera frustum) dan hanya mengaktifkan 8 teratas. Banyak judul AAA berhasil melakukan ini tanpa banyak dampak pada kualitas output (misalnya, Far Cry 1).
Anda juga dapat melihat lightmap yang sudah dihitung sebelumnya . Game seperti Quake 1 mendapat banyak jarak tempuh dari ini - dan mereka bisa sangat kecil (penyaringan bilinear melembutkan lightmaps yang diregangkan dengan cukup baik). Sayangnya pra-perhitungan tidak termasuk gagasan lampu dinamis 100%, tetapi itu benar-benar terlihat hebat . Anda dapat menggabungkan ini dengan batas 8 lampu Anda, jadi misalnya, hanya roket atau yang akan memiliki cahaya nyata - tetapi lampu di dinding atau semacamnya akan menjadi lightmaps.
Catatan: Anda tidak dapat melilitkannya dalam shader? Ucapkan selamat tinggal pada kinerja Anda. GPU bukan CPU dan tidak dirancang untuk bekerja dengan cara yang sama, misalnya, JavaScript. Ingatlah bahwa setiap piksel yang Anda render (jika ditimpa) harus melakukan loop - jadi jika Anda menjalankannya pada 1920x1080 dan loop sederhana yang berjalan 16 kali Anda secara efektif menjalankan semua yang ada di dalam loop tersebut sebanyak 33177600 kali. Kartu grafis Anda akan menjalankan a banyak fragmen secara paralel, tetapi loop tersebut masih memakan perangkat keras yang lebih lama.
sumber
Anda dapat menggunakan pixel shader yang mendukung n lampu (di mana n adalah angka bertubuh kecil seperti 4 atau 8), dan menggambar ulang adegan beberapa kali, melewati kumpulan lampu baru setiap kali, dan menggunakan campuran aditif untuk menggabungkan semuanya.
Itu ide dasarnya. Tentu saja ada banyak optimasi yang diperlukan untuk membuat ini cukup cepat untuk adegan berukuran wajar. Jangan menggambar semua lampu, hanya yang terlihat (frustum dan oklusi pemusnahan); jangan benar-benar menggambar ulang seluruh adegan setiap pass, hanya objek dalam jangkauan lampu di pass itu; memiliki beberapa versi shader yang mendukung jumlah lampu yang berbeda (1, 2, 3, ...) sehingga Anda tidak membuang waktu untuk mengevaluasi lebih banyak lampu daripada yang Anda perlukan.
Render yang ditangguhkan seperti yang disebutkan dalam jawaban lain adalah pilihan yang baik ketika Anda memiliki banyak lampu kecil, tetapi itu bukan satu-satunya cara.
sumber