Apa itu kaustik?
Caustics adalah pola cahaya yang terjadi ketika cahaya dibiaskan dan dipantulkan dari suatu permukaan, dalam kasus kami, di perbatasan air dan udara.
Karena refleksi dan refraksi terjadi pada gelombang air, air bertindak sebagai lensa dinamis di sini, menciptakan pola cahaya ini.

Dalam postingan kali ini, kami akan fokus pada caustics yang disebabkan oleh refraksi cahaya, yang biasanya terjadi di bawah air.
Untuk mencapai 60fps yang stabil, kami perlu menghitungnya pada kartu grafis (GPU), jadi kami hanya akan menghitung caustics dengan shader yang ditulis dalam GLSL.
Untuk menghitungnya, kita membutuhkan:
- hitung sinar yang dibiaskan di permukaan air (di GLSL ini mudah, karena ada fungsi built-in untuk ini )
- menghitung, menggunakan algoritma persimpangan, titik-titik di mana sinar-sinar ini bertabrakan dengan lingkungan
- hitung kecerahan kaustik dengan memeriksa titik konvergensi sinar

Demo air terkenal di WebGL
Saya selalu kagum dengan demo Evan Wallace yang menampilkan kaustik air yang secara visual realistis di WebGL: madebyevan.com/webgl-water

Saya sarankan untuk membaca artikel Medium- nya , yang menjelaskan cara menghitung caustics secara real time menggunakan fungsi light front mesh dan GLSL PD . Penerapannya sangat cepat dan terlihat sangat bagus, tetapi memiliki beberapa kekurangan: ini hanya berfungsi dengan kolam kubus dan bola biliar bulat . Jika Anda menempatkan hiu di bawah air, demo tidak akan berfungsi: di shader ada hardcode yang menyatakan bahwa ada bola bulat di bawah air.
Dia menempatkan bola di bawah air karena menghitung perpotongan antara sinar bias cahaya dan bola adalah tugas mudah yang menggunakan matematika yang sangat sederhana.
Ini semua bagus untuk demo, tetapi saya ingin membuat solusi yang lebih umum. untuk menghitung caustics sehingga setiap mata jaring yang tidak terstruktur seperti hiu dapat berada di kolam.

Sekarang mari beralih ke teknik saya. Untuk artikel ini, saya akan berasumsi bahwa Anda sudah mengetahui dasar-dasar rendering 3D dengan rasterisasi, dan Anda sudah familiar dengan cara vertex shader dan fragmen shader bekerja sama untuk merender primitif (segitiga) ke layar.
Bekerja dengan batasan GLSL
Dalam shader yang ditulis dalam GLSL (OpenGL Shading Language), kami hanya dapat mengakses informasi dalam jumlah terbatas tentang pemandangan, misalnya:
- Atribut simpul yang saat ini digambar (posisi: vektor 3D, normal: vektor 3D, dll.). Kita dapat meneruskan atribut GPU kita, tetapi atribut tersebut harus dari tipe GLSL bawaan.
- Seragam , yaitu konstanta untuk seluruh mesh yang saat ini dirender dalam bingkai saat ini. Ini bisa berupa tekstur, matriks proyeksi kamera, arah pencahayaan, dll. Mereka harus memiliki tipe bawaan: int, float, sampler2D untuk tekstur, vec2, vec3, vec4, mat3, mat4.
Namun, tidak ada cara untuk mengakses jerat yang ada di tempat kejadian.
Inilah mengapa demo webgl-water hanya dapat dilakukan dengan pemandangan 3D sederhana. Lebih mudah untuk menghitung perpotongan sinar yang dibiaskan dan bentuk yang sangat sederhana yang dapat direpresentasikan menggunakan seragam. Dalam kasus sebuah bola, ia dapat ditentukan oleh posisi (vektor 3D) dan radius (float), sehingga informasi ini dapat diteruskan ke shader menggunakan seragam , dan menghitung persimpangan membutuhkan matematika yang sangat sederhana, dengan mudah dan cepat dilakukan di shader.
Beberapa teknik penelusuran sinar yang dilakukan di shader merender mesh dalam tekstur, tetapi pada tahun 2020 solusi ini tidak berlaku untuk rendering waktu nyata di WebGL. Harus diingat bahwa untuk mendapatkan hasil yang layak, kita harus menghitung 60 gambar per detik dengan banyak sinar. Jika kita menghitung kaustik menggunakan sinar 256x256 = 65536, maka setiap detik kita harus melakukan perhitungan persimpangan dalam jumlah yang signifikan (yang juga tergantung pada jumlah mata jaring di tempat kejadian).
Kita perlu menemukan cara untuk merepresentasikan lingkungan bawah air secara seragam dan menghitung persimpangan dengan tetap mempertahankan kecepatan yang memadai.
Membuat peta lingkungan
Saat menghitung bayangan dinamis diperlukan, pemetaan bayangan adalah teknik yang terkenal . Ini sering digunakan dalam video game, terlihat bagus, dan cepat dieksekusi.
Pemetaan bayangan adalah teknik dua langkah:
- Pertama, pemandangan 3D ditampilkan dalam kaitannya dengan sumber cahaya. Tekstur ini tidak mengandung warna fragmen, tetapi kedalaman fragmen (jarak antara sumber cahaya dan fragmen). Tekstur ini disebut peta bayangan.
- Peta bayangan kemudian digunakan saat merender pemandangan 3D. Saat menggambar fragmen di layar, kita tahu apakah ada fragmen lain antara sumber cahaya dan fragmen saat ini. Jika demikian, maka kita tahu bahwa fragmen saat ini berada dalam bayangan, dan kita perlu menggambarnya sedikit lebih gelap.
Anda dapat membaca lebih lanjut tentang pemetaan bayangan dalam tutorial OpenGL yang luar biasa ini: www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping .
Anda juga dapat melihat contoh interaktif di ThreeJS (tekan T untuk menampilkan peta bayangan di sudut kiri bawah): threejs.org/examples/?q=shadowm#webgl_shadowmap .
Dalam kebanyakan kasus, teknik ini bekerja dengan baik. Ini dapat bekerja dengan mesh yang tidak terstruktur di tempat kejadian.
Awalnya saya pikir saya bisa menggunakan pendekatan serupa untuk caustics air, yaitu, mula-mula mengubah lingkungan bawah air menjadi tekstur, lalu menggunakan tekstur itu untuk menghitung perpotongan antara sinar dan lingkungan.... Alih-alih hanya menampilkan kedalaman fragmen, saya juga membuat posisi fragmen di peta lingkungan.
Berikut hasil pembuatan peta lingkungan:

Peta Env: posisi XYZ disimpan di saluran RGB, kedalaman di saluran alfa
Bagaimana menghitung persimpangan sinar dan lingkungan
Sekarang saya memiliki peta lingkungan bawah laut, saya perlu menghitung persimpangan antara sinar yang dibiaskan dan lingkungan.
Algoritme bekerja sebagai berikut:
- Tahap 1: mulai dari titik persimpangan antara sinar cahaya dan permukaan air
- Tahap 2: menghitung refraksi menggunakan fungsi refraksi
- Tahap 3: pergi dari posisi saat ini ke arah sinar yang dibiaskan, satu piksel dalam tekstur peta lingkungan.
- Tahap 4: Bandingkan kedalaman suasana yang terdaftar (disimpan dalam piksel tekstur suasana saat ini) dengan kedalaman saat ini. Jika kedalaman lingkungan lebih besar dari kedalaman saat ini, maka kita perlu melanjutkan, jadi kami menerapkan langkah 3 lagi . Jika kedalaman lingkungan kurang dari kedalaman saat ini, berarti sinar bertabrakan dengan lingkungan pada posisi terbaca dari tekstur lingkungan dan ditemukan perpotongan dengan lingkungan.

Kedalaman saat ini kurang dari kedalaman lingkungan: Anda harus melanjutkan

Kedalaman saat ini lebih besar dari kedalaman sekitarnya: kami menemukan persimpangan
Tekstur kaustik
Setelah menemukan persimpangan, kita dapat menghitung luminansi kaustik (dan tekstur luminansi kaustik) menggunakan teknik yang dijelaskan oleh Evan Wallace dalam artikelnya . Tekstur yang dihasilkan terlihat seperti ini:

Tekstur luminansi kaustik (perhatikan bahwa efek kaustik kurang penting pada hiu karena lebih dekat ke permukaan air, yang mengurangi konvergensi sinar cahaya)
Tekstur ini berisi informasi tentang intensitas cahaya untuk setiap titik dalam ruang 3D. Saat merender pemandangan akhir, kita dapat membaca intensitas cahaya ini dari tekstur kaustik dan mendapatkan hasil sebagai berikut:


Penerapan teknik ini dapat ditemukan di repositori Github: github.com/martinRenou/threejs-caustics . Beri dia bintang jika Anda menyukainya!
Jika Anda ingin melihat hasil perhitungan caustics, Anda dapat menjalankan demo: martinrenou.github.io/threejs-caustics .
Tentang algoritma persimpangan ini
Keputusan ini sangat bergantung pada resolusi tekstur lingkungan . Semakin besar teksturnya, semakin baik keakuratan algoritme, tetapi semakin lama waktu yang dibutuhkan untuk menemukan solusi (sebelum menemukannya, Anda perlu menghitung dan membandingkan lebih banyak piksel).
Selain itu, membaca tekstur di shader dapat diterima selama Anda tidak melakukannya terlalu sering; di sini kami membuat loop yang terus membaca piksel baru dari tekstur, yang tidak disarankan.
Selain itu, loop sementara tidak diizinkan di WebGL.(dan untuk alasan yang bagus), jadi kita perlu mengimplementasikan algoritme dalam loop for yang dapat diperluas oleh compiler. Ini berarti bahwa kita membutuhkan kondisi penghentian loop yang diketahui pada waktu kompilasi, biasanya nilai "iterasi maksimum", yang memaksa kita untuk berhenti mencari solusi jika kita belum menemukannya dalam jumlah percobaan maksimum. Batasan ini menyebabkan hasil kaustik yang salah jika refraksi terlalu penting.
Teknik kami tidak secepat pendekatan sederhana yang disarankan oleh Evan Wallace, tetapi jauh lebih fleksibel daripada pendekatan penelusuran sinar lengkap dan juga dapat digunakan untuk rendering waktu nyata. Namun, kecepatannya masih bergantung pada beberapa kondisi - arah cahaya, kecerahan refraksi, dan resolusi tekstur lingkungan.
Menutup ulasan demo
Dalam artikel ini, kami melihat penghitungan kaustik air, tetapi teknik lain digunakan dalam demo.
Saat membuat permukaan air, kami menggunakan tekstur skybox dan peta kubus untuk mendapatkan pantulan. Kami juga menerapkan pembiasan ke permukaan air menggunakan pembiasan sederhana di ruang layar (lihat artikel tentang pantulan dan pembiasan di ruang layar), teknik ini secara fisik salah, tetapi secara visual meyakinkan dan cepat. Kami juga menambahkan chromatic aberration untuk lebih realisme.
Kami memiliki lebih banyak ide untuk lebih meningkatkan metodologi, termasuk:
- Penyimpangan kromatik pada kaustik: Kami sekarang menerapkan penyimpangan kromatik ke permukaan air, tetapi efek ini juga harus terlihat pada kaustik bawah air.
- Hamburan cahaya dalam volume air.
- Seperti yang disarankan oleh Martin Gerard dan Alan Wolfe di Twitter , kami dapat meningkatkan kinerja dengan peta lingkungan hierarki (yang akan digunakan sebagai pohon quad untuk menemukan persimpangan). Mereka juga menyarankan untuk membuat peta lingkungan dalam kaitannya dengan sinar yang dibiaskan (dengan asumsi bahwa mereka datar sempurna), yang akan membuat kinerja tidak bergantung pada sudut datangnya pencahayaan.
Ucapan Terima Kasih
Pekerjaan visualisasi air real-time yang realistis ini dilakukan di QuantStack dan didanai oleh ERDC .