Apakah ada cara untuk menggunakan jumlah lampu sembarang dalam shader fragmen?

19

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.

NotRoyal
sumber
Saya belum pernah bekerja dengan WebGL, tetapi di OpenGL, Anda memiliki maksimal 8 sumber cahaya. Menurut pendapat saya, jika Anda ingin lulus lebih dari itu, Anda harus menggunakan misalnya variabel seragam.
zacharmarz
Metode lama adalah untuk selalu melewati semua lampu, lampu yang tidak digunakan diatur ke 0 luminance dan karena itu tidak akan mempengaruhi pemandangan. Mungkin tidak banyak digunakan lagi ;-)
Patrick Hughes
7
Saat Anda menggunakan Google untuk hal-hal seperti ini, jangan gunakan istilah 'WebGL' - teknologinya terlalu muda untuk dimiliki orang walaupun tentang mendekati masalah ini. Ambil pencarian ini misalnya, 'Saya merasa beruntung' akan berhasil. Ingat bahwa masalah WebGL harus diterjemahkan dengan baik ke masalah OpenGL yang sama persis.
Jonathan Dickinson
Untuk lebih dari 8 lampu dalam rendering maju saya biasanya menggunakan shader multi-pass dan memberikan masing-masing lulus 8 lampu yang berbeda untuk diproses, menggunakan campuran aditif.
ChrisC

Jawaban:

29

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 nilai

Anda 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 glColorMaskuntuk 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:

  • Posisi permukaan
  • Permukaan normals
  • Permukaan warna menyebar
  • Permukaan warna specular
  • Permukaan specular shininess
  • Mungkin parameter permukaan lainnya (tergantung pada seberapa kompleks persamaan pencahayaan Anda)

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.

Nicol Bolas
sumber
-1: kegagalan menyebutkan LPP / PPL. -1 ditangguhkan: rendering adalah kemenangan instan pada perangkat keras DX9.0 apa pun (ya bahkan pada laptop 'bisnis' saya) - yang merupakan persyaratan dasar sekitar 2009. Kecuali Anda menargetkan DX8.0 (yang tidak dapat ditangguhkan / LPP) Ditangguhkan / LPP adalah standar . Akhirnya 'banyak bandwidth memori' adalah gila - kita biasanya bahkan tidak menjenuhkan PCI-X x4, lebih jauh, LPP menjatuhkan bandwidth memori secara substansial. Akhirnya, -1 untuk komentar Anda; loop seperti ini OK? Anda tahu loop itu terjadi 207.300 kali per frame, kan? Bahkan dengan parrelisme kartu grafis, itu buruk.
Jonathan Dickinson
1
@ JonathanDickinson Saya pikir maksudnya adalah bahwa bandwidth memori untuk pra-pass ditangguhkan / ringan biasanya beberapa kali lebih besar daripada untuk rendering maju. Ini tidak membatalkan pendekatan yang ditangguhkan; itu hanya sesuatu untuk dipertimbangkan ketika memilihnya. BTW: buffer yang ditangguhkan Anda harus dalam memori video, sehingga bandwidth PCI-X tidak relevan; bandwidth internal GPU yang penting. Shader pixel panjang, misalnya dengan loop terbuka, tidak perlu khawatir jika mereka melakukan pekerjaan yang bermanfaat. Dan tidak ada yang salah dengan trik prepass z-buffer; ini bekerja dengan baik.
Nathan Reed
3
@ JonathanDickinson: Ini berbicara tentang WebGL, jadi diskusi tentang "model shader" tidak relevan. Dan jenis rendering yang digunakan bukan "topik agama": itu hanya masalah perangkat keras apa yang Anda jalankan. GPU tertanam, di mana "memori video" hanyalah RAM CPU biasa, akan bekerja sangat buruk dengan rendering yang ditangguhkan. Pada penyaji berbasis ubin seluler, itu bahkan lebih buruk . Render yang ditangguhkan bukan merupakan "kemenangan instan" terlepas dari perangkat kerasnya; itu memiliki pengorbanan, sama seperti perangkat keras apa pun.
Nicol Bolas
2
@JonathanDickinson: "Juga, dengan trik pre-pass z-buffer Anda akan berjuang untuk menghilangkan z-fighting dengan benda-benda yang harus ditarik." Itu omong kosong. Anda merender objek yang sama dengan matriks transformasi yang sama dan shader vertex yang sama. Render multipass dilakukan dalam Voodoo 1 hari; ini adalah masalah yang terpecahkan . Akumulasi pencahayaan tidak mengubah apa pun.
Nicol Bolas
8
@ JonathanDickinson: Tapi kita tidak berbicara tentang membuat gambar rangka, kan? Kita berbicara tentang membuat segitiga yang sama seperti sebelumnya. OpenGL menjamin invarian untuk objek yang sama dirender (selama Anda menggunakan vertex shader yang sama, tentu saja, dan bahkan kemudian, ada invariantkata kunci untuk menjaminnya untuk kasus lain).
Nicol Bolas
4

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.

Jonathan Dickinson
sumber
-1: "Anda harus menggunakan rendering yang ditangguhkan" Ini tidak benar sama sekali. Render yang ditangguhkan tentu saja merupakan cara untuk melakukannya, tetapi itu bukan satu - satunya cara. Juga loop tidak terlalu buruk dalam hal kinerja, terutama jika mereka didasarkan pada nilai yang seragam (yaitu: setiap fragmen tidak memiliki panjang loop yang berbeda).
Nicol Bolas
1
Silakan baca paragraf ke-4.
Jonathan Dickinson
2

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.

Nathan Reed
sumber