Unity GPU Path Tracing - Bagian 2

gambar

"Tidak ada yang lebih buruk daripada gambar yang jelas dari konsep yang buram." - fotografer Ansel Adams

Pada bagian pertama artikel, kami menciptakan pelacak sinar putih, yang mampu melacak pantulan sempurna dan bayangan yang tajam. Tetapi kita tidak memiliki efek ketidakjelasan: refleksi difus, refleksi mengkilap dan bayangan lembut.

Berdasarkan kode yang sudah kita miliki , kita akan menyelesaikan persamaan rendering yang dirumuskan oleh James Cajia pada tahun 1986 dan mengubah renderer kita menjadi pelacak jalur yang mampu mentransmisikan efek di atas. Kami akan kembali menggunakan C # untuk skrip dan HLSL untuk shader. Kode diunggah ke Bitbucket .

Artikel ini jauh lebih matematis daripada yang sebelumnya, tetapi jangan khawatir. Saya akan mencoba menjelaskan setiap formula sejelas mungkin. Rumus diperlukan di sini untuk melihat apa yang terjadi dan mengapa penyaji kami berfungsi, jadi saya sarankan mencoba memahaminya dan jika ada sesuatu yang tidak jelas, ajukan pertanyaan di komentar ke artikel asli.

Gambar di bawah ini dibuat menggunakan peta Graffiti Shelter dari situs HDRI Haven. Gambar lain dalam artikel ini telah dibuat menggunakan kartu Kiara 9 Dusk .

gambar

Persamaan rendering


Dari sudut pandang formal, tugas renderer fotorealistik adalah untuk menyelesaikan persamaan rendering, yang ditulis sebagai berikut:

L(x, vec omegao)=Le(x, vec omegao)+ int Omegafr(x, vec omegai, vec omegao)( vec omegai cdot vecn)L(x, vec omegai)d vec omegai


Mari kita analisa. Tujuan utama kami adalah untuk menentukan kecerahan piksel layar. Persamaan rendering memberi kita jumlah iluminasi L(x, vec omegao) datang dari suatu titik x (titik kejadian balok) dalam arah  vec omegao (arah di mana balok jatuh). Permukaan itu sendiri mungkin merupakan sumber cahaya yang memancarkan cahaya Le(x, vec omegao) ke arah kita. Sebagian besar permukaan tidak, jadi mereka hanya memantulkan cahaya dari luar. Itu sebabnya integral digunakan. Ini mengumpulkan pencahayaan yang datang dari setiap arah yang mungkin dari belahan bumi.  Omega sekitar normal (karena itu, sementara kami memperhitungkan pencahayaan yang jatuh pada permukaan dari atas , dan bukan dari dalam , yang mungkin diperlukan untuk bahan tembus cahaya).

Bagian pertama adalah fr disebut fungsi distribusi reflektansi dua arah (BRDF). Fungsi ini secara visual menggambarkan jenis bahan yang kita hadapi: logam atau dielektrik, gelap atau cerah, mengkilap atau matte. BRDF menentukan proporsi pencahayaan yang berasal  vec omegai yang tercermin dalam arah  vec omegao . Dalam praktiknya, ini diimplementasikan menggunakan vektor tiga komponen dengan nilai merah, hijau dan biru dalam interval [0,1] .

Bagian kedua - ( vec omegai cdot vecn) Apakah setara dengan 1 cos theta dimana  theta - sudut antara cahaya datang dan permukaan normal  vecn . Bayangkan sebuah kolom sinar paralel jatuh di permukaan secara tegak lurus. Sekarang bayangkan sinar yang sama jatuh ke permukaan pada sudut yang datar. Cahaya akan didistribusikan ke area yang lebih besar, tetapi itu juga berarti bahwa setiap titik area ini akan terlihat lebih gelap. Cosine diperlukan untuk memperhitungkan ini.

Akhirnya, pencahayaan itu sendiri didapat dari  vec omegai ditentukan secara rekursif menggunakan persamaan yang sama. Artinya, pencahayaan di titik x Tergantung pada cahaya insiden dari semua arah yang mungkin di belahan bumi atas. Di setiap arah ini dari satu titik x ada hal lain x prime , kecerahan yang sekali lagi tergantung pada cahaya yang jatuh dari semua arah yang mungkin dari belahan atas titik ini. Semua perhitungan diulang.

Inilah yang terjadi di sini, ini adalah persamaan integral rekursif yang tak terbatas dengan jumlah tak terbatas wilayah integrasi belahan otak. Kami tidak dapat menyelesaikan persamaan ini secara langsung, tetapi ada solusi yang cukup sederhana.



1 Jangan lupakan itu! Kita akan sering berbicara tentang kosinus, dan kita akan selalu mengingat produk skalar. Sejak  veca cdot vecb= | veca |  | vecb | cos( theta) , dan kita berhadapan dengan arah (vektor satuan), maka produk skalar adalah kosinus dalam sebagian besar tugas grafik komputer.

Monte Carlo datang untuk menyelamatkan


Integrasi Monte Carlo adalah teknik integrasi numerik yang memungkinkan kita untuk menghitung kira-kira setiap integral menggunakan jumlah sampel acak yang terbatas. Selain itu, Monte Carlo menjamin konvergensi ke keputusan yang tepat - semakin banyak sampel yang kami ambil, semakin baik. Berikut ini bentuk umum:

FN approx frac1N sumNn=0 fracf(xn)p(xn)


Oleh karena itu, fungsi integral f(xn) dapat kira-kira dihitung dengan rata-rata sampel acak dalam domain integrasi. Setiap sampel dibagi dengan probabilitas pemilihannya. p(xn) . Karena itu, sampel yang lebih sering dipilih akan memiliki lebih banyak berat daripada yang lebih jarang dipilih.

Dalam kasus sampel seragam di belahan bumi (setiap arah memiliki probabilitas yang sama untuk dipilih), probabilitas sampel adalah konstan: p( omega)= frac12 pi (karena 2 pi Apakah luas permukaan satu belahan bumi). Jika kita menggabungkan semua ini, kita mendapatkan yang berikut:

L(x, vec omegao) approxLe(x, vec omegao)+ frac1N sumNn=0 colorGreen2 pifr(x, vec omegai, vec omegao)( vec omegai cdot vecn)L(x, vec omegai)


Radiasi Le(x, vec omegao) Apakah hanya nilai yang dikembalikan oleh fungsi Shade kami.  frac1N sudah berjalan di fungsi AddShader kami. Perkalian dengan L(x, vec omegai) terjadi ketika kita memantulkan sinar dan melacaknya lebih jauh. Tugas kita adalah memberi kehidupan pada bagian hijau dari persamaan.

Prasyarat


Sebelum memulai perjalanan, mari kita selesaikan beberapa aspek: mengumpulkan sampel, adegan deterministik, dan keacakan shader.

Akumulasi


Untuk beberapa alasan, Unity tidak memberikan saya tekstur HDR sebagai destination di OnRenderImage . Format R8G8B8A8_Typeless bekerja untuk saya, sehingga akurasi dengan cepat menjadi terlalu rendah untuk mengakumulasikan sejumlah besar sampel. Untuk menangani ini, mari kita tambahkan private RenderTexture _converged ke private RenderTexture _converged C # private RenderTexture _converged . Ini akan menjadi buffer kami, terakumulasi dengan akurasi tinggi hasil sebelum menampilkannya di layar. Kami menginisialisasi / melepaskan tekstur dengan cara yang sama seperti _target dalam fungsi InitRenderTexture . Dalam fungsi Render , gandakan blitting:

 Graphics.Blit(_target, _converged, _addMaterial); Graphics.Blit(_converged, destination); 

Adegan Deterministik


Saat membuat perubahan pada rendering untuk mengevaluasi efeknya, ada baiknya membandingkan dengan hasil sebelumnya. Sejauh ini, dengan setiap restart mode Play atau kompilasi ulang skrip, kita akan mendapatkan adegan acak baru. Untuk menghindari ini, tambahkan public int SphereSeed ke public int SphereSeed C # dan baris berikut di awal SetUpScene :

 Random.InitState(SphereSeed); 

Sekarang kita dapat secara manual mengatur adegan seed. Masukkan nomor apa saja dan RayTracingMaster / aktifkan RayTracingMaster lagi sampai Anda mendapatkan adegan yang tepat.

Parameter berikut digunakan untuk gambar sampel: Sphere Seed 1223832719, Sphere Radius [5, 30], Spheres Max 10000, Sphere Placement Radius 100.

Keacakan shader


Sebelum memulai pengambilan sampel stokastik, kita perlu menambahkan keacakan ke shader. Saya akan menggunakan string kanonik yang saya temukan di jaringan, dimodifikasi untuk kenyamanan:

 float2 _Pixel; float _Seed; float rand() { float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f); _Seed += 1.0f; return result; } 

Inisialisasi _Pixel langsung di CSMain sebagai _Pixel = id.xy sehingga setiap piksel dapat menggunakan nilai acak yang berbeda. _Seed diinisialisasi dari C # dalam fungsi SetShaderParameters .

 RayTracingShader.SetFloat("_Seed", Random.value); 

Kualitas angka acak yang dihasilkan di sini tidak stabil. Di masa depan, ada baiknya mengeksplorasi dan menguji fungsi ini dengan menganalisis pengaruh parameter dan membandingkannya dengan pendekatan lain. Tapi untuk saat ini, kami hanya akan menggunakannya dan berharap yang terbaik.

Pengambilan sampel belahan otak


Mari kita mulai lagi: kita perlu arah acak yang terdistribusi secara seragam di belahan bumi. Tugas non-sepele ini untuk lingkup penuh dijelaskan secara rinci dalam artikel ini oleh Corey Simon. Sangat mudah untuk beradaptasi dengan belahan bumi. Seperti apa bentuk kode shader:

 float3 SampleHemisphere(float3 normal) { //     float cosTheta = rand(); float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta)); float phi = 2 * PI * rand(); float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); //      return mul(tangentSpaceDir, GetTangentSpace(normal)); } 

Arah dihasilkan untuk belahan yang berpusat pada sumbu Z positif, jadi kita perlu mengubahnya agar terpusat pada normal yang diinginkan. Kami menghasilkan tangen dan binormal (dua vektor ortogonal ke normal dan ortogonal satu sama lain). Pertama, kami memilih vektor bantu untuk menghasilkan garis singgung. Untuk melakukan ini, kita mengambil sumbu X positif, dan kembali ke positif Z hanya jika biasanya (kira-kira) sejajar dengan sumbu X. Kemudian kita dapat menggunakan produk vektor untuk menghasilkan garis singgung, dan kemudian binormal.

 float3x3 GetTangentSpace(float3 normal) { //       float3 helper = float3(1, 0, 0); if (abs(normal.x) > 0.99f) helper = float3(0, 0, 1); //   float3 tangent = normalize(cross(normal, helper)); float3 binormal = normalize(cross(normal, tangent)); return float3x3(tangent, binormal, normal); } 

Lambert berhamburan


Sekarang kami memiliki arahan acak yang seragam, kami dapat melanjutkan dengan implementasi BRDF pertama. Untuk refleksi difus, yang paling umum digunakan adalah Lambert BRDF, yang secara mengejutkan sederhana: fr(x, vec omegai, vec omegao)= frackd pi dimana kd - Ini adalah permukaan Albedo. Mari kita masukkan ke dalam persamaan rendering Monte Carlo kami (saya belum akan memperhitungkan emisivitas) dan lihat apa yang terjadi:

L(x, vec omegao) approx frac1N sumNn=0 colorBlueViolet2kd( vec omegai cdot vecn)L(x, vec omegai)


Mari kita masukkan persamaan ini ke shader segera. Dalam fungsi Shade , ganti kode di dalam if (hit.distance < 1.#INF) dengan baris berikut:

 //   ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(hit.normal); ray.energy *= 2 * hit.albedo * sdot(hit.normal, ray.direction); return 0.0f; 

Arah baru dari berkas pantulan ditentukan dengan menggunakan fungsi sampel hemisfer homogen kami. Energi balok dikalikan dengan bagian yang sesuai dari persamaan yang ditunjukkan di atas. Karena permukaan tidak memancarkan pencahayaan apa pun (hanya memantulkan cahaya yang diterima secara langsung atau tidak langsung dari langit), kami mengembalikan 0. Di sini, jangan lupa bahwa AddShader rata-rata sampel, jadi kami tidak perlu khawatir tentang  frac1N jumlah . CSMain sudah mengandung multiplikasi oleh L(x, vec omegai) (balok pantulan berikutnya), jadi kami tidak punya banyak pekerjaan tersisa.

sdot adalah fungsi pembantu yang saya buat sendiri. Ini hanya mengembalikan hasil produk skalar dengan koefisien tambahan, dan kemudian membatasi ke interval [0,1] :

 float sdot(float3 x, float3 y, float f = 1.0f) { return saturate(dot(x, y) * f); } 

Mari kita simpulkan apa yang kode kita lakukan sejauh ini. CSMain menghasilkan sinar utama kamera dan memanggil Shade . Ketika melintasi permukaan, fungsi ini pada gilirannya menghasilkan balok baru (acak seragam di belahan bumi di sekitar normal) dan memperhitungkan BRDF dari material dan kosinus dalam energi balok. Di persimpangan sinar dengan langit, kami mencicipi HDRI (satu-satunya sumber penerangan kami) dan mengembalikan iluminasi, yang dikalikan dengan energi sinar (mis., Hasil dari semua persimpangan sebelumnya, mulai dari kamera). Ini adalah sampel sederhana yang bercampur dengan hasil yang konvergen. Akibatnya, dampaknya diperhitungkan dalam setiap sampel.  frac1N .

Sudah waktunya untuk memeriksa semuanya dalam pekerjaan. Karena logam tidak memiliki pantulan yang difus, mari kita matikan untuk saat ini dalam fungsi SetUpScene dari skrip C # (tapi masih panggil Random.value sini untuk mempertahankan determinisme adegan):

 bool metal = Random.value < 0.0f; 

Luncurkan mode Putar dan lihat bagaimana gambar yang awalnya bising dibersihkan dan konvergen menjadi rendering yang indah:

Gambar Cermin Phong


Tidak buruk hanya untuk beberapa baris kode (dan sebagian kecil dari matematika). Mari kita perbaiki gambar dengan menambahkan refleksi cermin menggunakan BRDF Phong. Formulasi asli Fong memiliki masalah (kurangnya hubungan dan konservasi energi), tetapi untungnya orang lain menghilangkannya . BRDF yang disempurnakan ditunjukkan di bawah ini.  vec omegar Apakah arah cahaya yang dipantulkan dengan sempurna, dan  alpha Merupakan indikator Phong yang mengontrol kekasaran:

fr(x, vec omegai, vec omegao)=ks frac alpha+22 pi( vec omegar cdot vec omegao) alpha


Grafik dua dimensi interaktif menunjukkan seperti apa tampilan BRDF untuk Phong kapan  alpha=15 untuk insiden balok pada sudut 45 °. Coba ubah nilainya.  alpha .

Rekatkan ini ke dalam persamaan rendering Monte Carlo kami:

L(x, vec omegao) approx frac1N sumNn=0 colorbrownks( alpha+2)( vec omegar cdot vec omegao) alpha( vec omegai cdot vecn)L(x, vec omegai)


Dan akhirnya, mari kita tambahkan ini ke Lambert BRDF yang ada:

L(x, vec omegao) approx frac1N sumNn=0[ colorBlueViolet2kd+ warnabrownks( alpha+2)( vec omegar cdot vec omegao) alpha]( vec omegai cdot vecn)L(x, vec omegai)


Dan ini adalah bagaimana mereka terlihat dalam kode bersama dengan penyebaran Lambert:

 //    ray.origin = hit.position + hit.normal * 0.001f; float3 reflected = reflect(ray.direction, hit.normal); ray.direction = SampleHemisphere(hit.normal); float3 diffuse = 2 * min(1.0f - hit.specular, hit.albedo); float alpha = 15.0f; float3 specular = hit.specular * (alpha + 2) * pow(sdot(ray.direction, reflected), alpha); ray.energy *= (diffuse + specular) * sdot(hit.normal, ray.direction); return 0.0f; 

Perhatikan bahwa kami mengganti produk skalar dengan yang sedikit berbeda, tetapi setara (tercermin  omegao bukannya  omegai ) Sekarang SetUpScene material logam kembali ke fungsi SetUpScene dan periksa cara kerjanya.

Bereksperimen dengan nilai yang berbeda  alpha , Anda mungkin melihat masalah: bahkan kinerja rendah memerlukan banyak waktu untuk konvergensi, dan pada kebisingan kinerja tinggi sangat mencolok. Bahkan setelah beberapa menit menunggu, hasilnya jauh dari ideal, yang tidak dapat diterima untuk adegan yang begitu sederhana.  alpha=15 dan  alpha=300 dengan 8192 sampel terlihat seperti ini:



Mengapa ini terjadi? Bagaimanapun, sebelum kita memiliki refleksi ideal yang begitu indah (  alpha= infty )! .. Masalahnya adalah kita menghasilkan sampel yang homogen dan menetapkan bobotnya sesuai dengan BRDF. Dengan nilai Phong yang tinggi, BRDF kecil untuk semua orang, tetapi arah ini sangat dekat dengan refleksi sempurna, dan sangat tidak mungkin bahwa kami akan memilihnya secara acak menggunakan sampel homogen kami. Di sisi lain, jika kita benar-benar melewati salah satu arah ini, maka BRDF akan sangat besar untuk mengimbangi semua sampel kecil lainnya. Hasilnya adalah dispersi yang sangat besar. Jalur dengan beberapa pantulan specular bahkan lebih buruk dan menghasilkan noise yang terlihat dalam gambar.

Pengambilan Sampel yang Disempurnakan


Untuk membuat jalur pelacak kita praktis, kita perlu mengubah paradigma. Alih-alih membuang-buang sampel berharga pada area di mana mereka akhirnya menjadi tidak penting (karena mereka mendapatkan nilai BRDF dan / atau nilai kosinus yang sangat rendah), mari kita hasilkan sampel penting .

Sebagai langkah pertama, kami akan mengembalikan refleksi ideal kami, dan kemudian melihat bagaimana ide ini dapat digeneralisasi. Untuk melakukan ini, kami membagi logika bayangan menjadi refleksi difus dan specular. Untuk setiap sampel, kami akan secara acak memilih satu atau yang lain (tergantung pada rasio kd dan ks ) Dalam kasus refleksi difus, kami akan mematuhi sampel homogen, tetapi untuk specular, kami akan secara eksplisit mencerminkan balok di satu-satunya arah penting. Karena lebih sedikit sampel sekarang akan dihabiskan untuk setiap jenis refleksi, kita perlu meningkatkan pengaruhnya sesuai, untuk mendapatkan nilai total yang sama:

 //       hit.albedo = min(1.0f - hit.specular, hit.albedo); float specChance = energy(hit.specular); float diffChance = energy(hit.albedo); float sum = specChance + diffChance; specChance /= sum; diffChance /= sum; //     float roulette = rand(); if (roulette < specChance) { //   ray.origin = hit.position + hit.normal * 0.001f; ray.direction = reflect(ray.direction, hit.normal); ray.energy *= (1.0f / specChance) * hit.specular * sdot(hit.normal, ray.direction); } else { //   ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(hit.normal); ray.energy *= (1.0f / diffChance) * 2 * hit.albedo * sdot(hit.normal, ray.direction); } return 0.0f; 

energy adalah fungsi pembantu kecil yang rata-rata saluran warna:

 float energy(float3 color) { return dot(color, 1.0f / 3.0f); } 

Jadi kami membuat pelacak sinar putih dari bagian sebelumnya, tetapi sekarang dengan bayangan difus nyata (yang menyiratkan bayangan lembut, oklusi ambien, pencahayaan global difus):

gambar

Pentingnya Sampel


Mari kita lihat lagi rumus dasar Monte Carlo:

FN approx frac1N sumNn=0 fracf(xn)p(xn)


Seperti yang Anda lihat, kami membagi pengaruh masing-masing sampel (sampel) pada probabilitas memilih sampel khusus ini. Sejauh ini kami telah menggunakan sampel hemisfer homogen, jadi kami memiliki konstanta p( omega)= frac12 pi . Seperti yang kita lihat di atas, ini jauh dari optimal, misalnya, dalam kasus Phong BRDF, yang besar dalam sejumlah kecil arah.

Bayangkan kita dapat menemukan distribusi probabilitas yang cocok dengan fungsi yang dapat diintegrasikan: p(x)=f(x) . Maka yang berikut akan terjadi:

FN approx frac1N sumNn=01


Sekarang kami tidak memiliki sampel yang memberikan kontribusi sangat sedikit. Sampel-sampel ini kemungkinan kecil akan dipilih. Ini akan secara signifikan mengurangi varians dari hasil dan mempercepat konvergensi rendering.

Dalam praktiknya, tidak mungkin untuk menemukan distribusi ideal seperti itu, karena beberapa bagian dari fungsi yang dapat diintegrasikan (dalam kasus kami BRDF × cosine × cahaya insiden) tidak diketahui (ini paling jelas untuk cahaya insiden), tetapi distribusi sampel menurut BRDF × cosine atau bahkan hanya menurut BRDF akan membantu kita Prinsip ini disebut pengambilan sampel oleh kepentingan.

Sampel Kosinus


Dalam langkah-langkah berikut, kita perlu mengganti distribusi sampel yang homogen dengan distribusi sesuai dengan aturan cosinus. Jangan lupa, alih-alih mengalikan sampel homogen dengan cosinus, mengurangi pengaruhnya, kami ingin menghasilkan jumlah sampel yang lebih kecil secara proporsional.

Artikel ini oleh Thomas Poole menjelaskan cara melakukan ini. Kami akan menambahkan parameter alpha ke fungsi SampleHemisphere kami. Fungsi menentukan indeks pemilihan cosinus: 0 untuk sampel seragam, 1 untuk pemilihan cosinus, atau lebih tinggi untuk nilai Phong yang lebih tinggi. Dalam kode, tampilannya seperti ini:

 float3 SampleHemisphere(float3 normal, float alpha) { //  ,      float cosTheta = pow(rand(), 1.0f / (alpha + 1.0f)); float sinTheta = sqrt(1.0f - cosTheta * cosTheta); float phi = 2 * PI * rand(); float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); //      return mul(tangentSpaceDir, GetTangentSpace(normal)); } 

Sekarang probabilitas setiap sampel sama p( omega)= frac alpha+12 pi( vec omega cdot vecn) alpha . Keindahan persamaan ini mungkin tidak langsung tampak jelas, tetapi sedikit kemudian Anda akan memahaminya.

Sampel Lambert dengan Pentingnya


Sebagai permulaan, kami akan memperbaiki rendering refleksi difus. Dalam distribusi homogen kami, konstanta Lambert BRDF sudah digunakan, tetapi kami dapat memperbaikinya dengan menambahkan cosinus. Distribusi probabilitas sampel oleh cosinus (di mana  alpha=1 ) sama  frac( vec omegai cdot vecn) pi , yang menyederhanakan rumus Monte Carlo kami untuk refleksi difus:

L(x, vec omegao) approx frac1N sumNn=0 colorBlueVioletkdL(x, vec omegai)


 //   ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(hit.normal, 1.0f); ray.energy *= (1.0f / diffChance) * hit.albedo; 

Ini akan mempercepat naungan difus kami sedikit. Sekarang mari kita masuk ke masalah sebenarnya.

Pengambilan sampel Fongov penting


Untuk Phong BRDF, prosedurnya serupa. Kali ini kami memiliki produk dari dua cosinus: cosinus standar dari persamaan rendering (seperti dalam kasus dengan refleksi difus), dikalikan dengan cosinus BRDF yang tepat. Kami hanya akan berurusan dengan yang terakhir.

Mari kita masukkan distribusi probabilitas dari contoh di atas ke dalam persamaan Phong. Kesimpulan terperinci dapat ditemukan dalam Lafortune dan Willem: Menggunakan Model Reflektansi Phong yang Dimodifikasi untuk Rendering Berbasis Fisik (1994) :

L(x, vec omegao) approx frac1N sumNn=0 colorbrownks frac alpha+2 alpha+1( V e c o m e g a i c d o t v e c n )    L ( x , v e c o m e g a i ) 


 //   float alpha = 15.0f; ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(reflect(ray.direction, hit.normal), alpha); float f = (alpha + 2) / (alpha + 1); ray.energy *= (1.0f / specChance) * hit.specular * sdot(hit.normal, ray.direction, f); 

Perubahan ini cukup untuk menghilangkan masalah dengan kinerja tinggi di Phong dan membuat rendering kami bertemu dalam waktu yang jauh lebih masuk akal.

Material


Akhirnya, mari kita kembangkan generasi pemandangan kita untuk menciptakan nilai-nilai yang berubah demi kelancaran dan emisivitas bola-bola! Dalam struct Sphere dari skrip C #, tambahkan public float smoothness public Vector3 emission dan public Vector3 emission . Karena kita mengubah ukuran struct, kita perlu mengubah langkah ketika membuat Compute Buffer (4 × jumlah angka float, ingat?). Buat nilai SetUpScene fungsi SetUpScene untuk kelancaran dan SetUpScene .

Di shader, tambahkan kedua variabel ke struct Sphere dan struct RayHit , dan kemudian inisialisasi mereka di CreateRayHit . Dan akhirnya, tetapkan kedua nilai di IntersectGroundPlane (hardcoded, rekatkan nilai apa saja) dan IntersectSphere (mendapatkan nilai dari Sphere ).

Saya ingin menggunakan nilai-nilai kelancaran dengan cara yang sama seperti pada shader Unity standar, yang berbeda dari eksponen Fong yang agak sewenang-wenang. Berikut adalah konversi yang baik yang dapat digunakan dalam fungsi Shade :

 float SmoothnessToPhongAlpha(float s) { return pow(1000.0f, s * s); } 

 float alpha = SmoothnessToPhongAlpha(hit.smoothness); 



Menggunakan emisivitas dilakukan dengan mengembalikan nilai dalam Shade :

 return hit.emission; 

Hasil


Ambil napas dalam-dalam. santai dan tunggu sampai gambar berubah menjadi gambar yang begitu indah:

gambar

Selamat! Anda berhasil melewati rumpun ekspresi matematika. Kami menerapkan pelacak jalur yang melakukan penebaran difus dan cermin, mempelajari tentang pengambilan sampel berdasarkan kepentingan, segera menerapkan konsep ini sehingga rendering bertemu dalam hitungan menit, bukan jam atau hari.

Dibandingkan dengan yang sebelumnya, artikel ini merupakan langkah besar dalam hal kompleksitas, tetapi juga secara signifikan meningkatkan kualitas hasilnya. Bekerja dengan perhitungan matematis membutuhkan waktu, tetapi itu membenarkan dirinya sendiri karena dapat secara signifikan memperdalam pemahaman Anda tentang apa yang terjadi dan akan memungkinkan Anda untuk memperluas algoritme tanpa merusak keandalan fisik.

Terima kasih sudah membaca! Pada bagian ketiga, kita (untuk sementara waktu) akan meninggalkan hutan pengambilan sampel dan naungan, dan kembali ke peradaban untuk bertemu tuan-tuan Moller dan Trumbor. Kita perlu berbicara dengan mereka tentang segitiga.

Tentang Pengarang: David Curie adalah pengembang Three Eyed Games, programmer Virtual Engineering Lab Volkswagen, peneliti grafis komputer, dan musisi heavy metal.

gambar

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


All Articles