
Pelajaran
sebelumnya memberikan gambaran tentang dasar-dasar menerapkan model rendering yang masuk akal secara fisik. Kali ini kita akan beralih dari perhitungan teoritis ke implementasi render tertentu dengan partisipasi sumber cahaya langsung (analitik): titik, arah atau jenis lampu sorot.
Pertama, mari menyegarkan kembali ekspresi untuk menghitung reflektivitas dari pelajaran sebelumnya:
Sebagian besar, kita telah membahas komponen-komponen formula ini, tetapi pertanyaannya tetap bagaimana secara spesifik mewakili
irradiansi , yang merupakan kecerahan energi total (
cahaya ).
seluruh adegan. Kami sepakat bahwa kecerahan energi
(dalam hal terminologi grafik komputer) dianggap sebagai rasio dari fluks radiasi (
fluks radiasi )
(energi radiasi dari sumber cahaya) dengan nilai sudut padat
. Dalam kasus kami, sudut solid
kami menganggapnya sangat kecil, dan karenanya kecerahan energi memberikan gambaran tentang fluks radiasi untuk setiap sinar cahaya individu (arahnya).
Bagaimana menghubungkan penghitungan ini dengan model pencahayaan yang kita ketahui dari pelajaran sebelumnya? Pertama, bayangkan Anda diberi sumber titik cahaya tunggal (yang memancarkan seragam ke segala arah) dengan fluks radiasi yang didefinisikan sebagai triad RGB (23.47, 21.31, 20.79).
Intensitas radiasi sumber tersebut sama dengan fluks radiasi di semua arah. Namun, setelah mempertimbangkan masalah menentukan warna titik tertentu
di permukaan, Anda dapat melihat bahwa dari semua kemungkinan arah kejadian cahaya di belahan bumi
hanya vektor
jelas akan berasal dari sumber cahaya. Karena hanya satu sumber cahaya yang diwakili, diwakili oleh suatu titik dalam ruang, untuk semua kemungkinan arah kejadian cahaya lainnya menjadi suatu titik
kecerahan energi akan sama dengan nol:
Sekarang, jika kita tidak secara temporer memperhitungkan hukum pelemahan cahaya untuk sumber tertentu, ternyata kecerahan energi untuk berkas cahaya yang ditimbulkan oleh sumber ini tetap tidak berubah di mana pun kita menempatkan sumbernya (penskalaan luminositas berdasarkan kosinus sudut kejadian)
juga tidak masuk hitungan). Secara total, sumber titik menjaga gaya radiasi konstan terlepas dari sudut pandang, yang setara dengan mengambil gaya radiasi sama dengan fluks radiasi awal dalam bentuk konstanta triad (23.47, 21.31, 20.79).
Namun, perhitungan kecerahan energi juga didasarkan pada titik koordinat
, setidaknya setiap sumber cahaya yang andal secara fisik menunjukkan pelemahan gaya radiasi dengan meningkatnya jarak dari satu titik ke sumber lain. Anda juga harus memperhitungkan orientasi permukaan, seperti yang dapat dilihat dari ekspresi asli untuk luminositas: hasil perhitungan gaya radiasi harus dikalikan dengan nilai skalar dari vektor normal ke permukaan.
dan vektor insiden radiasi
.
Untuk menulis ulang di atas: untuk sumber cahaya titik langsung, fungsi radiasi
menentukan warna cahaya insiden, dengan mempertimbangkan redaman pada jarak tertentu dari titik
dan dengan mempertimbangkan penskalaan oleh suatu faktor
tetapi hanya untuk satu sinar cahaya
sampai ke titik
- pada dasarnya satu-satunya vektor yang menghubungkan sumber dan titik. Dalam bentuk kode sumber, ini ditafsirkan sebagai berikut:
vec3 lightColor = vec3(23.47, 21.31, 20.79); vec3 wi = normalize(lightPos - fragPos); float cosTheta = max(dot(N, Wi), 0.0); float attenuation = calculateAttenuation(fragPos, lightPos); vec3 radiance = lightColor * attenuation * cosTheta;
Jika Anda menutup mata terhadap terminologi yang sedikit dimodifikasi, kode ini akan mengingatkan Anda pada sesuatu. Ya, ya, ini semua kode yang sama untuk menghitung komponen difus dalam model pencahayaan yang kita kenal. Untuk penerangan langsung, kecerahan energi ditentukan oleh vektor tunggal untuk sumber cahaya, karena perhitungannya dilakukan dengan cara yang sangat mirip dengan yang masih kita ketahui.
Saya perhatikan bahwa pernyataan ini benar hanya dengan asumsi bahwa sumber titik cahaya sangat kecil dan diwakili oleh titik di ruang angkasa. Saat memodelkan sumber volume, luminositasnya akan berbeda dari nol di berbagai arah, dan tidak hanya pada satu balok.
Untuk sumber cahaya lain yang memancarkan radiasi dari satu titik, kecerahan energi dihitung dengan cara yang sama. Misalnya, sumber cahaya arah memiliki arah yang konstan
dan tidak menggunakan atenuasi, dan sumber proyeksi menunjukkan daya radiasi yang bervariasi, tergantung pada arah sumber.
Di sini kita kembali ke nilai integral
di permukaan belahan bumi
. Karena kita tahu sebelumnya posisi semua sumber cahaya yang berpartisipasi dalam naungan titik tertentu, kita tidak perlu mencoba untuk menyelesaikan integral. Kita dapat langsung menghitung total iradiasi yang disediakan oleh jumlah sumber cahaya ini, karena kecerahan energi permukaan dipengaruhi oleh satu arah untuk setiap sumber.
Akibatnya, perhitungan PBR untuk sumber cahaya langsung adalah hal yang cukup sederhana, karena semuanya berujung pada pencarian berurutan dari sumber yang terlibat dalam pencahayaan. Nantinya, komponen dari lingkungan akan muncul dalam model pencahayaan, yang akan kita bahas dalam tutorial tentang pencahayaan berbasis
gambar (
Image-Based Lighting ,
IBL ). Tidak ada jalan keluar dari estimasi integral, karena cahaya dalam model seperti itu jatuh dari banyak arah.
Model permukaan PBR
Mari kita mulai dengan shader fragmen yang mengimplementasikan model PBR yang dijelaskan di atas. Pertama, kami mengatur data input yang diperlukan untuk bayangan permukaan:
#version 330 core out vec4 FragColor; in vec2 TexCoords; in vec3 WorldPos; in vec3 Normal; uniform vec3 camPos; uniform vec3 albedo; uniform float metallic; uniform float roughness; uniform float ao;
Di sini Anda dapat melihat input biasa yang dihitung menggunakan vertex shader paling sederhana, serta seperangkat seragam yang menggambarkan karakteristik permukaan objek.
Selanjutnya, pada awal kode shader, kami melakukan perhitungan yang sangat akrab dari penerapan model pencahayaan Blinn-Fong:
void main() { vec3 N = normalize(Normal); vec3 V = normalize(camPos - WorldPos); [...] }
Pencahayaan langsung
Contoh untuk pelajaran ini hanya berisi empat titik sumber cahaya yang secara jelas menentukan iradiasi pemandangan. Untuk memenuhi ekspresi reflektifitas, kami secara iteratif menelusuri setiap sumber cahaya, menghitung kecerahan energi individu dan merangkum kontribusi ini, secara simultan memodulasi nilai BRDF dan sudut datangnya sinar. Anda bisa membayangkan iterasi ini sebagai solusi integral atas permukaan
Hanya untuk sumber cahaya analitis.
Jadi, pertama-tama kita menghitung nilai yang dihitung untuk setiap sumber:
vec3 Lo = vec3(0.0); for(int i = 0; i < 4; ++i) { vec3 L = normalize(lightPositions[i] - WorldPos); vec3 H = normalize(V + L); float distance = length(lightPositions[i] - WorldPos); float attenuation = 1.0 / (distance * distance); vec3 radiance = lightColors[i] * attenuation; [...]
Karena perhitungan dilakukan dalam ruang linear (
koreksi gamma dilakukan di ujung shader), hukum pelemahan yang lebih tepat secara fisik digunakan sesuai dengan kuadrat terbalik jarak:
Misalkan hukum kuadrat terbalik lebih benar secara fisik, untuk lebih mengontrol sifat redaman, sangat mungkin untuk menggunakan rumus yang sudah akrab yang mengandung istilah konstan, linier, dan kuadratik.
Selanjutnya, untuk setiap sumber, kami juga menghitung nilai mirror Cook-Torrance BRDF:
Langkah pertama adalah menghitung rasio antara refleksi specular dan difus, atau, dengan kata lain, rasio antara jumlah cahaya yang dipantulkan dan jumlah cahaya yang dibiaskan oleh permukaan. Dari
pelajaran sebelumnya, kita tahu seperti apa perhitungan koefisien Fresnel:
vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); }
Perkiraan Fresnel-Schlick mengharapkan parameter
F0 pada input, yang menunjukkan
tingkat pantulan permukaan pada sudut nol insiden cahaya , yaitu. tingkat refleksi, jika Anda melihat permukaan sepanjang normal dari atas ke bawah. Nilai
F0 bervariasi tergantung pada bahan dan memperoleh cor warna untuk logam, seperti yang dapat dilihat dengan melihat katalog bahan PBR. Untuk proses
alur kerja logam (proses pembuatan bahan PBR, membagi semua bahan menjadi kelas dielektrik dan konduktor), diasumsikan bahwa semua dielektrik terlihat cukup andal pada nilai konstan
F0 = 0,04 , sedangkan untuk permukaan logam
F0 diatur berdasarkan albedo permukaan. Dalam bentuk kode:
vec3 F0 = vec3(0.04); F0 = mix(F0, albedo, metallic); vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
Seperti yang Anda lihat, untuk permukaan non-logam,
F0 disetel sama dengan 0,04. Tetapi pada saat yang sama, itu dapat dengan lancar berubah dari nilai ini ke nilai albedo berdasarkan "keasaman" permukaan. Indikator ini biasanya disajikan sebagai tekstur terpisah (dari sini, pada kenyataannya, alur kerja
logam diambil,
kira-kira Trans. ).
Setelah diterima
kita perlu menghitung nilai fungsi distribusi normal
dan fungsi geometri
:
Kode fungsi untuk kasing dengan pencahayaan analitis:
float DistributionGGX(vec3 N, vec3 H, float roughness) { float a = roughness*roughness; float a2 = a*a; float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH*NdotH; float num = a2; float denom = (NdotH2 * (a2 - 1.0) + 1.0); denom = PI * denom * denom; return num / denom; } float GeometrySchlickGGX(float NdotV, float roughness) { float r = (roughness + 1.0); float k = (r*r) / 8.0; float num = NdotV; float denom = NdotV * (1.0 - k) + k; return num / denom; } float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx2 = GeometrySchlickGGX(NdotV, roughness); float ggx1 = GeometrySchlickGGX(NdotL, roughness); return ggx1 * ggx2; }
Perbedaan penting dari yang dijelaskan dalam bagian
teoretis : di sini kita langsung memberikan parameter roughness ke semua fungsi yang disebutkan. Hal ini dilakukan untuk memungkinkan setiap fungsi mengubah nilai kekasaran aslinya dengan caranya sendiri. Sebagai contoh, studi Disney, tercermin dalam mesin dari Epic Games, menunjukkan bahwa model pencahayaan memberikan hasil yang lebih benar secara visual jika kita menggunakan kuadrat dari kekasaran pada fungsi geometri dan fungsi distribusi normal.
Setelah mengatur semua fungsi, seseorang dapat langsung mendapatkan nilai NDF dan G:
float NDF = DistributionGGX(N, H, roughness); float G = GeometrySmith(N, V, L, roughness);
Total yang kami miliki semua nilai untuk menghitung seluruh Cook-Torrance BRDF:
vec3 numerator = NDF * G * F; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; vec3 specular = numerator / denominator;
Harap perhatikan bahwa kami membatasi penyebut ke nilai minimum 0,001 untuk mencegah pembagian dengan nol jika terjadi penurunan produk skalar.
Sekarang kita lanjutkan menghitung kontribusi setiap sumber terhadap persamaan reflektivitas. Karena koefisien Fresnel secara langsung adalah variabel
, maka kita dapat menggunakan nilai F untuk menunjukkan kontribusi sumber terhadap pantulan specular permukaan. Dari kuantitas
dapat diperoleh dan indeks bias
:
vec3 kS = F; vec3 kD = vec3(1.0) - kS; kD *= 1.0 - metallic;
Karena kami menganggap kuantitas
kS yang mewakili jumlah energi cahaya sebagai permukaan yang dipantulkan, mengurangkannya dari kesatuan, kami memperoleh energi residu
kD cahaya yang dibiaskan oleh permukaan. Selain itu, karena logam tidak memantulkan cahaya dan tidak memiliki komponen difus cahaya yang dipancarkan kembali, komponen
kD akan dimodulasi menjadi nol untuk semua bahan logam. Setelah perhitungan ini, kami akan memiliki semua data untuk menghitung pantulan yang disediakan oleh masing-masing sumber cahaya:
const float PI = 3.14159265359; float NdotL = max(dot(N, L), 0.0); Lo += (kD * albedo / PI + specular) * radiance * NdotL; }
Nilai akhir
Lo , atau kecerahan energi keluar, pada dasarnya adalah solusi untuk ekspresi reflektifitas, yaitu hasil integrasi permukaan
. Dalam hal ini, kita tidak perlu mencoba menyelesaikan integral dalam bentuk umum untuk semua arah yang mungkin, karena dalam contoh ini hanya ada empat sumber cahaya yang mempengaruhi fragmen yang sedang diproses. Itu sebabnya semua "integrasi" terbatas pada siklus sederhana sumber cahaya yang ada.
Tetap hanya menambahkan kesamaan komponen pencahayaan latar belakang dengan hasil penghitungan sumber cahaya langsung dan warna akhir dari fragmen siap:
vec3 ambient = vec3(0.03) * albedo * ao; vec3 color = ambient + Lo;
Render linier dan HDR
Sampai sekarang, kami mengasumsikan bahwa semua perhitungan dilakukan dalam ruang warna linier, dan oleh karena itu digunakan
koreksi gamma sebagai akord terakhir dalam shader kami. Melakukan perhitungan dalam ruang linear sangat penting untuk simulasi PBR yang benar, karena model ini membutuhkan linearitas semua data input. Usahakan untuk tidak memastikan linearitas dari salah satu parameter dan hasil bayangan akan salah. Selain itu, akan lebih baik untuk mengatur sumber cahaya dengan karakteristik yang dekat dengan sumber nyata: misalnya, warna radiasi mereka dan kecerahan energi dapat dengan bebas bervariasi pada rentang yang luas. Sebagai hasilnya,
Lo dapat dengan mudah menerima nilai besar, tetapi pasti jatuh di bawah batas dalam interval [0., 1.] karena rentang dinamis rendah (
LDR ) dari buffer bingkai default.
Untuk menghindari hilangnya nilai HDR, sebelum koreksi gamma, perlu untuk melakukan kompresi nada:
color = color / (color + vec3(1.0)); color = pow(color, vec3(1.0/2.2));
Operator Reinhardt yang akrab digunakan di sini, yang memungkinkan kami untuk mempertahankan rentang dinamis yang luas dalam kondisi iradiasi yang sangat berubah dari berbagai bagian gambar. Karena di sini kita tidak menggunakan shader terpisah untuk pasca-pemrosesan, operasi yang dijelaskan dapat ditambahkan hanya ke akhir kode shader.
Saya ulangi bahwa untuk pemodelan PBR yang benar, sangat penting untuk mengingat dan mempertimbangkan fitur bekerja dengan ruang warna linier dan rendering HDR. Mengabaikan aspek-aspek ini akan menyebabkan perhitungan yang salah dan hasil yang secara visual tidak estetis.
PBR shader untuk pencahayaan analitik
Jadi, bersama dengan sentuhan akhir dalam bentuk kompresi tonal dan koreksi gamma, tetap hanya untuk mentransfer warna akhir fragmen ke output shader fragmen dan kode shader PBR untuk pencahayaan langsung dapat dianggap selesai. Akhirnya, mari kita melihat-lihat seluruh kode fungsi
utama () dari shader ini:
#version 330 core out vec4 FragColor; in vec2 TexCoords; in vec3 WorldPos; in vec3 Normal;
Saya berharap bahwa setelah membaca bagian
teoretis dan dengan analisis hari ini tentang ekspresi kemampuan reflektif, daftar itu akan berhenti terlihat mengintimidasi.
Kami menggunakan shader ini dalam sebuah adegan yang berisi empat sumber cahaya titik, sejumlah bola yang karakteristik permukaannya akan mengubah tingkat kekasaran dan sifat logamnya masing-masing di sepanjang sumbu horizontal dan vertikal. Pada output kita mendapatkan gambar berikut:
Metalik berubah dari nol menjadi satu dari bawah ke atas, dan kekasarannya serupa, tetapi dari kiri ke kanan. Menjadi jelas bahwa hanya mengubah dua karakteristik permukaan ini saja sudah memungkinkan untuk mengatur berbagai material.
Kode sumber lengkap ada di
sini .
PBR dan texturing
Kami akan memperluas model permukaan kami dengan mentransmisikan karakteristik dalam bentuk tekstur. Dengan cara ini, kami dapat memberikan kontrol per-fragmen dari parameter material permukaan:
[...] uniform sampler2D albedoMap; uniform sampler2D normalMap; uniform sampler2D metallicMap; uniform sampler2D roughnessMap; uniform sampler2D aoMap; void main() { vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, 2.2); vec3 normal = getNormalFromNormalMap(); float metallic = texture(metallicMap, TexCoords).r; float roughness = texture(roughnessMap, TexCoords).r; float ao = texture(aoMap, TexCoords).r; [...] }
Perhatikan bahwa tekstur permukaan Albedo biasanya dibuat oleh seniman di ruang warna sRGB, jadi dalam kode di atas kita mengembalikan warna texel ke ruang linier sehingga dapat digunakan dalam perhitungan lebih lanjut. Bergantung pada bagaimana para seniman membuat tekstur yang mengandung data
peta oklusi ambien , mungkin juga harus dibawa ke ruang linear. Peta metallicity dan kekasaran hampir selalu dibuat di ruang linear.
Penggunaan tekstur bukan parameter permukaan tetap dalam kombinasi dengan algoritma PBR memberikan peningkatan yang signifikan dalam keandalan visual dibandingkan dengan algoritma pencahayaan yang digunakan sebelumnya:
Kode contoh texturing lengkap ada di
sini , dan tekstur yang digunakan ada di
sini (bersama dengan tekstur peneduh latar belakang). Saya menarik perhatian Anda pada kenyataan bahwa permukaan yang sangat metalik tampak gelap di bawah kondisi pencahayaan langsung, karena kontribusi refleksi difus kecil (dalam batas tidak ada sama sekali). Bayangan mereka menjadi lebih benar hanya ketika memperhitungkan pantulan cermin pencahayaan dari lingkungan, yang akan kita lakukan dalam pelajaran selanjutnya.
Saat ini, hasilnya mungkin tidak mengesankan seperti beberapa demonstrasi PBR - namun kami belum menerapkan sistem pencahayaan berbasis gambar (
IBL ). Namun demikian, render kami sekarang dianggap berdasarkan pada prinsip-prinsip fisik dan, bahkan tanpa IBL, itu menunjukkan gambar yang lebih dapat diandalkan daripada sebelumnya.