Menerapkan awan volumetrik yang benar secara fisik seperti di Horizon Zero Dawn

Sebelumnya, cloud dalam gim digambar dengan sprite 2D biasa, yang selalu diputar ke arah kamera, tetapi dalam beberapa tahun terakhir, model kartu video baru memungkinkan Anda untuk menggambar awan yang benar secara fisik tanpa kehilangan kinerja yang nyata. Diyakini bahwa awan tebal dalam game membawa studio Game Gerilya bersama dengan game Horizon Zero Dawn. Tentu saja, awan-awan seperti itu dapat dirender sebelumnya, tetapi studio membentuk sesuatu seperti standar industri untuk sumber daya sumber dan algoritma yang digunakan, dan sekarang setiap implementasi awan volumetrik entah bagaimana sesuai dengan standar ini.



Seluruh proses rendering cloud dibagi dengan sangat baik menjadi beberapa tahap dan penting untuk dicatat bahwa implementasi yang tidak akurat bahkan pada salah satu dari mereka dapat menyebabkan konsekuensi sedemikian rupa sehingga tidak akan jelas di mana kesalahan itu dan bagaimana cara memperbaikinya, oleh karena itu disarankan untuk membuat kesimpulan kontrol dari hasil setiap kali.

Pemetaan nada, sRGB


Sebelum mulai bekerja dengan pencahayaan, penting untuk melakukan dua hal:

  1. Sebelum menampilkan gambar akhir di layar, terapkan setidaknya pemetaan nada paling sederhana:

    tunedColor=color/(1+color) 

    Ini diperlukan karena nilai warna yang dihitung akan jauh lebih besar daripada satu.
  2. Pastikan bahwa framebuffer terakhir yang Anda gambar dan tampilkan di layar dalam format sRGB. Jika aktivasi mode sRGB adalah masalah, konversi dapat dilakukan secara manual di shader:

     finalColor=pow(color, vec3(1.0/2.2)) 

    Formula ini cocok untuk kebanyakan kasus, tetapi tidak 100% tergantung pada monitor. Penting bahwa konversi sRGB selalu dilakukan terakhir.

Model pencahayaan


Pertimbangkan ruang yang diisi dengan materi transparan sebagian dengan kepadatan berbeda. Ketika sinar cahaya melewati zat semacam itu, ia terkena empat efek: penyerapan, hamburan, hamburan memperkuat dan radiasi diri. Yang terakhir terjadi dalam kasus proses kimia dalam suatu zat, dan tidak terpengaruh di sini.

Misalkan kita memiliki sinar cahaya yang melewati materi dari titik A ke titik B:


Penyerapan

Cahaya yang melewati suatu zat mengalami penyerapan oleh zat ini. Fraksi cahaya yang tidak diserap dapat ditemukan dengan rumus:


dimana - cahaya yang tersisa di titik setelah penyerapan . - arahkan pada segmen AB di kejauhan dari A.

Hamburan

Bagian dari cahaya di bawah pengaruh partikel-partikel materi mengubah arahnya. Fraksi cahaya yang tidak berubah arahnya dapat ditemukan dengan rumus:


dimana - pecahan cahaya yang tidak berubah arah setelah hamburan pada suatu titik .

Penyerapan dan dispersi harus digabungkan:


Fungsi disebut pelemahan atau kepunahan. Suatu fungsi - fungsi transfer. Ini menunjukkan berapa banyak cahaya yang tersisa ketika melewati dari titik A ke titik B.

Salam dan : , di mana C adalah konstanta tertentu, yang mungkin memiliki nilai berbeda untuk setiap saluran dalam RGB, Adalah kepadatan medium pada titik tersebut .

Sekarang mari kita rumit tugasnya. Cahaya bergerak dari titik A ke titik B, padam saat gerakan. Pada titik X, bagian dari cahaya tersebar di arah yang berbeda, salah satu arah sesuai dengan pengamat di titik O. Selanjutnya, sebagian dari cahaya yang tersebar bergerak dari titik X ke titik O dan meredam lagi. Jalur cahaya AXO menarik bagi kami.


Hilangnya cahaya saat pindah dari A ke X kita tahu: , seperti yang kita ketahui hilangnya cahaya dari X ke O - ini . Tetapi bagaimana dengan sebagian kecil cahaya yang akan tersebar ke arah pengamat?

Dispersi amplifikasi

Jika dalam kasus hamburan biasa, intensitas cahaya berkurang, maka dalam kasus hamburan penguatan, itu meningkat karena hamburan cahaya yang telah terjadi di daerah tetangga. Jumlah total cahaya yang berasal dari daerah tetangga dapat ditemukan dengan rumus:


dimana berarti mengambil integral dari bola, - fungsi fase - Cahaya datang dari arah .

Sangat sulit untuk menghitung cahaya dari semua arah, namun, kita tahu bahwa bagian asli cahaya dibawa oleh balok AB asli kita. Formula dapat sangat disederhanakan:


dimana - sudut antara berkas cahaya dan berkas pengamat (mis., sudut AXO), - nilai awal dari intensitas cahaya. Meringkas semua hal di atas, kami memperoleh rumus:


dimana - cahaya yang masuk - cahaya mencapai pengamat.

Kami sedikit merumitkan tugas. Katakanlah cahaya dipancarkan oleh cahaya directional, mis. matahari:


Semuanya terjadi sama seperti pada kasus sebelumnya, tetapi berkali-kali. Cahaya dari titik A1 tersebar di titik X1 menuju pengamat di titik O, cahaya dari titik A2 tersebar di titik X2 ke arah pengamat di titik O, dll. Kita melihat bahwa cahaya yang mencapai pengamat sama dengan jumlah:


Atau ungkapan integral yang lebih akurat:


Penting untuk memahami hal itu di sini , yaitu segmen dibagi menjadi jumlah tak terbatas dari bagian dengan panjang nol.

Langit


Dengan sedikit penyederhanaan, sinar matahari yang melewati atmosfer hanya mengalami hamburan, yaitu .


Dan bahkan tidak satu jenis hamburan, tetapi dua: hamburan Rayleigh dan hamburan Mi. Yang pertama disebabkan oleh molekul udara, dan yang kedua disebabkan oleh aerosol air.

Total kepadatan udara (atau aerosol) yang dilalui oleh sinar cahaya, bergerak dari titik A ke titik B:
dimana - tinggi scaling, h - tinggi saat ini.

Solusi integral sederhana adalah:

di mana dh adalah ukuran langkah dengan mana sampel tinggi diambil.

Sekarang lihat gambar dan gunakan rumus yang diperoleh di bagian sebelumnya dari "model pencahayaan":


Pengamat melihat dari O ke O '. Kami ingin mengumpulkan semua cahaya yang mencapai titik X1, X2, ..., Xn, tersebar di dalamnya, dan kemudian mencapai pengamat:


dimana intensitas cahaya yang dipancarkan oleh matahari, - tinggi pada titik ; dalam kasus langit, konstanta C, yang berfungsi dilambangkan sebagai .

Solusi integral dapat sebagai berikut:

Formula ini berlaku untuk hamburan Rayleigh dan hamburan Mie. Sebagai hasilnya, nilai-nilai cahaya untuk masing-masing sebaran cukup dijumlahkan:


Rayleigh Dispersion



(berisi nilai untuk setiap saluran RGB)



Hasil:


Pencar saya



(nilai untuk semua saluran RGB adalah sama)



Hasil:


Jumlah sampel per segmen dan di segmennya Anda dapat mengambil 32 ke atas. Radius bumi adalah 6371000 m, atmosfer adalah 100000 m.

Apa yang harus dilakukan dengan semua ini:

  1. Di setiap piksel layar, kami menghitung arah pengamat V
  2. Kami mengambil posisi pengamat O sama dengan {0, 6371000, 0}
  3. Kami menemukan sebagai hasil dari persimpangan sinar yang berasal dari titik O, dan arah V dan bola berpusat di titik {0,0,0} dan jari-jari 6471000
  4. Segmen garis bagi menjadi 32 bagian dengan panjang yang sama
  5. Untuk setiap bagian, kami menghitung hamburan Rayleigh dan hamburan Mie, dan menambahkan semuanya. Apalagi untuk menghitung kita juga perlu membagi segmen 32 plot sama dalam setiap kasus. dapat dibaca melalui variabel, nilai yang meningkat pada setiap langkah dalam siklus.

Hasil akhir:


Model cloud


Kami membutuhkan beberapa jenis noise dalam 3D. Yang pertama adalah suara gerakan Brownian fraktal Perlin yang bersembunyi:

Hasil untuk irisan 2D:


Yang kedua adalah bunyi fBm cloaking Voronoi.

Hasil untuk irisan 2D:


Untuk mendapatkan noise fBm cloaking Vorley, Anda perlu membalikkan fBm cloaking Voronoj. Namun, saya sedikit mengubah rentang nilai menurut kebijaksanaan saya:

 float fbmTiledWorley3(...) { return clamp((1.0-fbmTiledVoronoi3(...))*1.5-0.25, 0.0, 1.0); } 

Hasilnya segera menyerupai struktur cloud:


Untuk cloud, Anda perlu mendapatkan dua tekstur khusus. Yang pertama memiliki ukuran 128x128x128 dan bertanggung jawab untuk kebisingan frekuensi rendah, yang kedua memiliki ukuran 32x32x32 dan bertanggung jawab untuk kebisingan frekuensi tinggi. Setiap tekstur hanya menggunakan satu saluran dalam format R8. Dalam beberapa contoh, 4 saluran R8G8B8A8 digunakan untuk tekstur pertama dan tiga saluran R8G8B8 untuk yang kedua, dan kemudian saluran dicampur dalam shader. Saya tidak mengerti intinya, karena pencampuran dapat dilakukan sebelumnya sehingga mendapatkan hit yang lebih besar dalam koherensi cache.

Untuk pencampuran, dan juga di beberapa tempat, fungsi remap () akan digunakan, yang menskalakan nilai dari satu rentang ke yang lain:

 float remap(float value, float minValue, float maxValue, float newMinValue, float newMaxValue) { return newMinValue+(value-minValue)/(maxValue-minValue)*(newMaxValue-newMinValue); } 

Mari kita mulai menyiapkan tekstur dengan noise frekuensi rendah:
R-channel - fBm noise perlin
G-channel - ubin fBm Vorley noise
B-channel - fBm noise Worley yang lebih kecil dengan skala yang lebih kecil
A-channel - kebisingan fBm taylable Varley dengan skala yang lebih kecil


Pencampuran dilakukan dengan cara ini:

 finalValue=remap(noise.x, (noise.y * 0.625 + noise.z*0.25 + noise.w * 0.125)-1, 1, 0, 1) 

Hasil untuk irisan 2D:


Sekarang siapkan tekstur dengan noise frekuensi tinggi:
R-channel - ubin fBm Vorley noise
G-channel - fBm Vorley noise berskala lebih kecil
B-channel - Varley taylivaya fBm noise dengan skala yang lebih kecil


 finalValue=noise.x * 0.625 + noise.y*0.25 + noise.z * 0.125; 

Hasil untuk irisan 2D:


Kita juga membutuhkan peta cuaca-tekstur 2D yang akan menentukan keberadaan, kepadatan, dan bentuk awan, tergantung pada koordinat ruang. Itu dilukis oleh seniman untuk menyempurnakan tutupan awan. Interpretasi saluran warna dari peta cuaca mungkin berbeda, dalam versi yang saya pinjamkan, adalah sebagai berikut:


R-channel - tutupan awan ketinggian rendah
G-channel - tutupan awan ketinggian tinggi
B-channel - ketinggian awan maksimum
A-channel - kerapatan awan

Sekarang kita siap membuat fungsi yang akan mengembalikan kerapatan awan tergantung pada koordinat ruang 3D.

Di pintu masuk, titik di ruang angkasa dengan koordinat dalam km

 vec3 position 

Segera tambahkan offset ke angin

 position.xz+=vec2(0.2f)*ufmParams.time; 

Dapatkan nilai peta cuaca

 vec4 weather=textureLod(ufmWeatherMap, position.xz/4096.0f, 0); 
Kami mendapatkan persentase tinggi (dari 0 hingga 1)

 float height=cloudGetHeight(position); 

Tambahkan pembulatan kecil awan di bawah ini:
 float SRb=clamp(remap(height, 0, 0.07, 0, 1), 0, 1); 
Kami membuat penurunan kerapatan linear menjadi 0 dengan meningkatnya ketinggian menurut B-channel dari peta cuaca:

 float SRt=clamp(remap(height, weather.b*0.2, weather.b, 1, 0), 0, 1); 
Gabungkan hasilnya:

 float SA=SRb*SRt; 

Sekali lagi tambahkan pembulatan awan di bawah ini:

 float DRb=height*clamp(remap(height, 0, 0.15, 0, 1), 0, 1); 

Tambahkan juga pembulatan awan di atas:

 float DRt=height*clamp(remap(height, 0.9, 1, 1, 0), 0, 1); 
Kami menggabungkan hasilnya, di sini kami menambahkan pengaruh kepadatan dari peta cuaca dan pengaruh kepadatan, yang ditetapkan melalui gui:

 float DA=DRb*DRt*weather.a*2*ufmProperties.density; 

Gabungkan noise frekuensi rendah dan frekuensi tinggi dari tekstur kami:

 float SNsample=textureLod(ufmLowFreqNoiseTexture, position/48.0f, 0).x*0.85f+textureLod(ufmHighFreqNoiseTexture, position/4.8f, 0).x*0.15f; 

Di semua dokumen yang saya baca, penggabungan terjadi dengan cara yang berbeda, tetapi saya menyukai opsi ini.

Kami menentukan jumlah cakupan (% dari langit yang ditempati awan), yang ditetapkan melalui gui, saluran R dan G pada peta cuaca juga digunakan:

 float WMc=max(weather.r, clamp(ufmProperties.coverage-0.5, 0, 1)*weather.g*2); 

Hitung kerapatan akhir:

 float d=clamp(remap(SNsample*SA, 1-ufmProperties.coverage*WMc, 1, 0, 1), 0, 1)*DA; 

Seluruh fungsi:

 float cloudSampleDensity(vec3 position) { position.xz+=vec2(0.2f)*ufmParams.time; vec4 weather=textureLod(ufmWeatherMap, position.xz/4096.0f+vec2(0.2, 0.1), 0); float height=cloudGetHeight(position); float SRb=clamp(remap(height, 0, 0.07, 0, 1), 0, 1); float SRt=clamp(remap(height, weather.b*0.2, weather.b, 1, 0), 0, 1); float SA=SRb*SRt; float DRb=height*clamp(remap(height, 0, 0.15, 0, 1), 0, 1); float DRt=height*clamp(remap(height, 0.9, 1, 1, 0), 0, 1); float DA=DRb*DRt*weather.a*2*ufmProperties.density; float SNsample=textureLod(ufmLowFreqNoiseTexture, position/48.0f, 0).x*0.85f+textureLod(ufmHighFreqNoiseTexture, position/4.8f, 0).x*0.15f; float WMc=max(weather.r, clamp(ufmProperties.coverage-0.5, 0, 1)*weather.g*2); float d=clamp(remap(SNsample*SA, 1-ufmProperties.coverage*WMc, 1, 0, 1), 0, 1)*DA; return d; } 

Apa sebenarnya fungsi ini seharusnya adalah pertanyaan terbuka, karena mengabaikan hukum yang dipatuhi awan saat mengatur parameter, Anda bisa mendapatkan hasil yang sangat tidak biasa dan indah. Itu semua tergantung aplikasi.


Integrasi


Atmosfer Bumi dibagi menjadi dua lapisan: internal dan eksternal, di antaranya awan dapat ditemukan. Lapisan-lapisan ini dapat diwakili oleh bola, tetapi juga oleh pesawat. Saya memilih bola. Untuk lapisan pertama, saya mengambil jari-jari bola 6415 km, untuk lapisan kedua, jari-jari 6435 km. Jari-jari bumi membulat hingga 6.400 km. Beberapa parameter akan tergantung pada ketebalan bersyarat dari bagian "mendung" atmosfer (20 km).



Tidak seperti langit, awan bersifat buram, dan integrasi tidak hanya membutuhkan warna, tetapi juga mendapatkan nilai untuk saluran alpha. Pertama, Anda membutuhkan fungsi yang mengembalikan kerapatan total awan tempat sinar cahaya dari matahari akan lewat.


Tidak ada yang menarik perhatian pada hal ini, tetapi latihan telah menunjukkan bahwa sama sekali tidak perlu memperhitungkan seluruh jalur balok, hanya dibutuhkan celah yang paling ekstrem. Kami berasumsi bahwa awan di atas segmen terpotong tidak ada sama sekali.


Selain itu, kami sangat terbatas dalam jumlah sampel kepadatan yang dapat dilakukan tanpa membunuh kinerja. Gerilya Game melakukan 6. Selain itu, dalam salah satu presentasi, pengembang mengatakan bahwa mereka menyebarkan sampel ini di dalam kerucut, dan sampel terakhir dibuat sangat jauh dari yang lain untuk menutupi ruang sebanyak mungkin. Ketidakakuratan dan kebisingan yang dihasilkan masih akan diperhalus dengan latar belakang sampel tetangga, dan ini, sebaliknya, akan berubah menjadi peningkatan akurasi.


Pada akhirnya, saya memilih 4 sampel yang terletak pada garis yang sama, tetapi yang terakhir diambil dengan langkah meningkat 6 kali. Ukuran langkah adalah 20 km * 0,01, yaitu 200 m.

Fungsinya cukup sederhana:

 float cloudSampleDirectDensity(vec3 position, vec3 sunDir) { //   float avrStep=(6435.0-6415.0)*0.01; float sumDensity=0.0; for(int i=0;i<4;i++) { float step=avrStep; //      6 if(i==3) step=step*6.0; //  position+=sunDir*step; //  ,  ,   //  float density=cloudSampleDensity(position)*step; sumDensity+=density; } return sumDensity; } 

Sekarang Anda dapat beralih ke bagian yang lebih sulit. Kami menentukan pengamat di permukaan bumi pada titik {0, 6400,0} dan menemukan persimpangan berkas pengamatan dengan bola jari-jari 6415 km dan pusat {0,0,0} - kami mendapatkan titik awal S.


Di bawah ini adalah versi dasar dari fungsi:

 vec4 mainMarching(vec3 viewDir, vec3 sunDir) { vec3 position; crossRaySphereOutFar(vec3(0.0, 6400.0, 0.0), viewDir, vec3(0.0), 6415.0, position); float avrStep=(6435.0-6415.0)/64.0; for(int i=0;i<128;i++) { position+=viewDir*step; if(length(position)>6435.0) break; } return vec4(0.0); } 

Ukuran langkah didefinisikan sebagai 20 km / 64. yaitu dalam kasus arah vertikal ketat dari berkas pengamat, kami akan membuat 64 sampel. Namun, ketika arah ini lebih horizontal, sampel akan sedikit lebih besar, sehingga tidak ada 64 langkah dalam siklus, tetapi 128 dengan margin.

Pada awalnya, kami menganggap bahwa warna akhir adalah hitam, dan transparansi adalah kesatuan. Dengan setiap langkah, kami akan meningkatkan nilai warna dan mengurangi nilai transparansi. Jika transparansi mendekati 0, maka Anda dapat keluar dari loop sebelumnya:

 vec3 color=vec3(0.0); float transmittance=1.0; … //    //      float density=cloudSampleDensity(position)*avrStep; //   ,   //   float sunDensity=cloudSampleDirectDensity(position, sunDir); //      float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*m2*m3; //       color+=sunColor*light*transmittance; transmittance*=exp(-ufmProperties.attenuation*density); … return vec4(color, 1.0-transmittance); 

ufmProperties.attenuation - Tidak ada yang lain selain C in dan ufmProperties.attenuation2 adalah C dalam . ufmProperties.sunIntensity - intensitas radiasi matahari. sunColor - warna matahari.

Hasil:


Sebuah cacat segera terlihat - naungan yang parah. Tapi sekarang kita akan memperbaiki kurangnya pencahayaan yang diperkuat di dekat matahari. Itu terjadi karena kami tidak menambahkan fungsi fase. Untuk menghitung hamburan cahaya yang melewati awan, fase-fungsi Hengy-Greenstein digunakan, yang membukanya pada tahun 1941 untuk perhitungan serupa dalam kluster gas di ruang angkasa:


Penyimpangan harus dilakukan di sini. Menurut model pencahayaan kanonik, fungsi fase harus satu. Namun, pada kenyataannya, hasil yang diperoleh tidak sesuai dengan siapa pun dan semua orang menggunakan fungsi dua fase, dan bahkan menggabungkan nilai-nilai mereka dengan cara khusus. Saya juga fokus pada fungsi dua fase, tapi saya hanya menambahkan nilainya. Fungsi fase pertama memiliki g mendekati 1 dan memungkinkan Anda membuat pencahayaan terang di dekat matahari. Fungsi fase kedua memiliki g mendekati 0,5 dan memungkinkan Anda untuk membuat penurunan bertahap dalam pencahayaan di seluruh bidang langit.

Kode yang diperbarui:

 // cos(theta) float mu=max(0, dot(viewDir, sunDir)); float m11=ufmProperties.phaseInfluence*cloudPhaseFunction(mu, ufmProperties.eccentrisy); float m12=ufmProperties.phaseInfluence2*cloudPhaseFunction(mu, ufmProperties.eccentrisy2); float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*(m11+m12)*m2*m3; 

ufmProperties.eccentrisy, ufmProperties.eccentrisy2 adalah nilai g

Hasil:


Sekarang Anda bisa memulai pertarungan dengan terlalu banyak naungan. Itu hadir karena kita tidak memperhitungkan cahaya dari awan di sekitarnya dan langit, yang ada di kehidupan nyata.

Saya memecahkan masalah ini seperti ini:

 return vec4(color+ambientColor*ufmProperties.ambient, 1.0-transmittance); 

Di mana ambientColor adalah warna langit ke arah sinar pengamatan, ufmProperties.ambient adalah parameter tuning.

Hasil:


Masih untuk menyelesaikan masalah terakhir. Dalam kehidupan nyata, semakin horizontal pandangan dipegang, semakin kita melihat kabut atau kabut tertentu yang tidak memungkinkan kita untuk melihat objek yang sangat jauh. Ini juga perlu tercermin dalam kode. Saya mengambil cosine biasa dari sudut pandang dan fungsi eksponensial. Berdasarkan ini, koefisien campuran tertentu dihitung, yang memungkinkan interpolasi linier antara warna yang dihasilkan dan warna latar belakang.

 float blending=1.0-exp(-max(0.0, dot(viewDir, vec3(0.0,1.0,0.0)))*ufmProperties.fog); blending=blending*blending*blending; return vec4(mix(ambientColor, color+ambientColor*ufmProperties.ambient, blending), 1.0-transmittance); 

ufmProperties.fog - untuk konfigurasi manual.


Fungsi ringkasan:

 vec4 mainMarching(vec3 viewDir, vec3 sunDir, vec3 sunColor, vec3 ambientColor) { vec3 position; crossRaySphereOutFar(vec3(0.0, 6400.0, 0.0), viewDir, vec3(0.0), 6415.0, position); float avrStep=(6435.0-6415.0)/64.0; vec3 color=vec3(0.0); float transmittance=1.0; for(int i=0;i<128;i++) { float density=cloudSampleDensity(position)*avrStep; if(density>0.0) { float sunDensity=cloudSampleDirectDensity(position, sunDir); float mu=max(0.0, dot(viewDir, sunDir)); float m11=ufmProperties.phaseInfluence*cloudPhaseFunction(mu, ufmProperties.eccentrisy); float m12=ufmProperties.phaseInfluence2*cloudPhaseFunction(mu, ufmProperties.eccentrisy2); float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*(m11+m12)*m2*m3; color+=sunColor*light*transmittance; transmittance*=exp(-ufmProperties.attenuation*density); } position+=viewDir*avrStep; if(transmittance<0.05 || length(position)>6435.0) break; } float blending=1.0-exp(-max(0.0, dot(viewDir, vec3(0.0,1.0,0.0)))*ufmProperties.fog); blending=blending*blending*blending; return vec4(mix(ambientColor, color+ambientColor*ufmProperties.ambient, blending), 1.0-transmittance); } 

Video demo:


Optimalisasi dan kemungkinan peningkatan


Setelah menerapkan algoritma rendering dasar, masalah berikutnya adalah ia bekerja terlalu lambat. Versi saya menghasilkan 25 fps dalam full hd pada radeon rx 480. Dua pendekatan berikut untuk memecahkan masalah disarankan oleh Guerrilla Games sendiri.

Kami menggambar apa yang benar-benar terlihat

Layar dibagi menjadi ubin berukuran 16x16 piksel. Pertama, lingkungan 3D biasa digambar. Ternyata sebagian besar langit ditutupi oleh gunung atau benda besar. Oleh karena itu, Anda perlu melakukan perhitungan hanya pada ubin-ubin di mana awan tidak terhalang oleh apa pun.

Proyeksi ulang

Ketika kamera diam, ternyata awan pada umumnya tidak bisa diperbarui. Namun, jika kamera telah bergerak, ini tidak berarti bahwa kami perlu memperbarui seluruh layar. Semuanya sudah digambar, Anda hanya perlu membangun kembali gambar sesuai dengan koordinat baru. Menemukan koordinat lama pada yang baru, melalui proyeksi dan melihat matriks dari frame saat ini dan sebelumnya, disebut proyeksi. Jadi, dalam kasus pergeseran kamera, kami cukup mentransfer warna sesuai dengan koordinat baru. Dalam kasus di mana koordinat ini menunjukkan di luar layar, awan harus digambar ulang dengan jujur.

Pembaruan sebagian

Saya tidak menyukai ide proyeksi ulang karena dengan putaran tajam pada kamera, mungkin awan akan berubah menjadi sepertiga layar, yang dapat menyebabkan jeda. Saya tidak tahu bagaimana Game Gerilya menangani ini, tetapi setidaknya di Horizon Zero Dawn, ketika mengendalikan joystick, kamera bergerak dengan lancar dan tidak ada masalah dengan lompatan yang tajam. Karena itu, sebagai percobaan, saya datang dengan pendekatan saya sendiri. Awan digambar dalam peta kubik, dalam 5 wajah, karena bagian bawah tidak menarik bagi kita.Sisi peta kubik memiliki resolusi berkurang sama dengan ⅔ dari ketinggian layar. Setiap muka peta kubik dibagi menjadi 8x8 ubin. Setiap bingkai di setiap wajah diperbarui dengan hanya satu dari 64 piksel di setiap ubin. Ini memberikan artefak yang terlihat selama perubahan tiba-tiba, tetapi karena Awannya cukup statis, maka trik seperti itu tidak terlihat. Hasilnya, radeon rx 480 menghasilkan 500 fps dalam resolusi penuh untuk gunung berapi dan 330 fps untuk pembukaan. Radeon hd 5700 series menghasilkan 109 fps dalam full hd di bawah OpenGL (vulkan tidak mendukung).

Menggunakan level mip

Saat mengakses tekstur dengan noise, Anda dapat mengambil data dari level nol mip hanya pada sampel pertama, dan kemudian semakin jauh sampel yang kami buat, semakin besar level mip yang dapat diambil.

Awan tinggi

Untuk mensimulasikan keberadaan awan cirrus-altitude dan cirrocumulus di Game Gerilya selama integrasi, sampel terbaru dibuat bukan dari tekstur 3D yang saya bicarakan, tetapi dari tekstur 2D khusus.


Curl noise

Beberapa tekstur tambahan dalam curl noise digunakan untuk menciptakan efek awan angin bertiup. Tekstur ini diperlukan untuk menggeser koordinat asli.


Sinar ilahi


Sinar seperti itu, menangkap pada drama, diwujudkan dalam postprocessing. Pertama, iluminasi terang digambar di sekitar matahari, di mana ia tidak terhalang oleh awan. Maka lampu latar ini harus diimbangi secara radial dari matahari.


Sekarang Anda perlu menerapkan perataan radial.


Faktanya, ada lebih banyak peningkatan dan kehalusan, tapi saya tidak memeriksa semuanya, jadi saya tidak bisa mengatakan dengan percaya diri tentang mereka. Namun, Anda bisa membiasakan diri dengan mereka sendiri. Saya pikir yang terkuat adalah dokumentasi cloud dari mesin Frostbite.

Tautan yang bermanfaat


Guerrilla Games
d1z4o56rleaq4j.cloudfront.net/downloads/assets/Nubis-Authoring-Realtime-Volumetric-Cloudscapes-with-the-Decima-Engine-Final.pdf?mtime=20170807141817
killzone.dl.playstation.net/killzone/horizonzerodawn/presentations/Siggraph15_Schneider_Real-Time_Volumetric_Cloudscapes_of_Horizon_Zero_Dawn.pdf
www.youtube.com/watch?v=-d8qT5-1LOI

GPU Pro 7
vk.com/doc179245989_437393482?hash=a9af5f665eda4edf58&dl=806d4dbdac0f7a761c


www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/simulating-sky/simulating-colors-of-the-sky

Frostbite
media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf
www.shadertoy.com/view/XlBSRz

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


All Articles