Pelajari OpenGL. Pelajaran 5.10 - Layar Ruang Ambient Occlusion

OGL3

SSAO


Topik tentang pencahayaan latar belakang diangkat oleh kami dalam pelajaran tentang dasar - dasar pencahayaan , tetapi hanya secara sepintas. Biarkan saya mengingatkan Anda: komponen latar belakang pencahayaan pada dasarnya adalah nilai konstan yang ditambahkan ke semua perhitungan pencahayaan pemandangan untuk mensimulasikan proses hamburan cahaya . Di dunia nyata, cahaya mengalami banyak pantulan dengan berbagai tingkat intensitas, yang menyebabkan pencahayaan yang sama tidak merata dari bagian-bagian pemandangan yang secara tidak langsung diterangi. Jelas, suar dengan intensitas konstan tidak terlalu masuk akal.

Salah satu jenis perhitungan perkiraan bayangan dari pencahayaan tidak langsung adalah algoritma ambient occlusion (AO ), yang mensimulasikan redaman pencahayaan tidak langsung di sekitar sudut, kerutan, dan penyimpangan permukaan lainnya. Elemen-elemen tersebut, secara umum, secara signifikan tumpang tindih oleh geometri yang berdekatan dan karenanya meninggalkan lebih sedikit sinar cahaya untuk keluar, mengaburkan area-area ini.

Di bawah ini adalah perbandingan rendering tanpa dan menggunakan algoritma AO. Perhatikan bagaimana intensitas pencahayaan latar belakang berkurang di sekitar sudut-sudut dinding dan pecah tajam lainnya di permukaan:


Meskipun efeknya tidak terlalu terlihat, kehadiran efek di seluruh adegan menambah realisme karena ilusi kedalaman tambahan yang diciptakan oleh detail kecil dari efek bayangan diri.


Perlu dicatat bahwa algoritma untuk menghitung AO cukup intensif sumber daya, karena mereka memerlukan analisis geometri sekitarnya. Dalam implementasi yang naif, dimungkinkan untuk memancarkan banyak sinar pada setiap titik di permukaan dan menentukan tingkat bayangannya, tetapi pendekatan ini dengan sangat cepat mencapai batas sumber daya intensif yang dapat diterima untuk aplikasi interaktif. Untungnya, pada tahun 2007, Crytek menerbitkan sebuah makalah yang menjelaskan pendekatannya sendiri untuk mengimplementasikan algoritma Screen-Space Ambient Occlusion (SSAO ) yang digunakan dalam versi rilis Crysis. Pendekatan ini menghitung tingkat bayangan di ruang layar, hanya menggunakan buffer kedalaman saat ini, bukan data nyata tentang geometri sekitarnya. Optimalisasi seperti itu secara radikal mempercepat algoritma dibandingkan dengan implementasi referensi dan pada saat yang sama memberikan sebagian besar hasil yang masuk akal, yang membuat pendekatan ini perkiraan perhitungan latar belakang shading industri de facto standar.

Prinsip yang menjadi dasar algoritma ini cukup sederhana: untuk setiap fragmen dari quad-screen penuh, faktor oklusi dihitung berdasarkan nilai kedalaman fragmen-fragmen di sekitarnya. Koefisien naungan yang dihitung kemudian digunakan untuk mengurangi intensitas pencahayaan latar belakang (hingga pengecualian total). Untuk mendapatkan koefisien, diperlukan pengumpulan data kedalaman dari sejumlah sampel dari wilayah bola yang mengelilingi fragmen yang dimaksud dan membandingkan nilai kedalaman ini dengan kedalaman fragmen yang dimaksud. Jumlah sampel yang memiliki kedalaman lebih besar daripada fragmen saat ini secara langsung menentukan koefisien naungan. Lihatlah diagram ini:


Di sini, setiap titik abu-abu terletak di dalam objek geometris tertentu, dan oleh karena itu memberikan kontribusi pada nilai koefisien naungan. Semakin banyak sampel di dalam geometri objek di sekitarnya, semakin sedikit intensitas residu dari latar belakang naungan di area ini.

Jelas, kualitas dan realisme efek secara langsung tergantung pada jumlah sampel yang diambil. Dengan sejumlah kecil sampel, keakuratan algoritme menurun dan mengarah pada munculnya artefak banding atau " banding " karena transisi mendadak antara kawasan dengan koefisien bayangan yang sangat berbeda. Sejumlah besar sampel hanya membunuh kinerja. Pengacakan inti sampel memungkinkan hasil yang agak mirip untuk sedikit mengurangi jumlah sampel yang diperlukan. Reorientasi dengan rotasi ke sudut acak dari sekumpulan vektor sampel tersirat. Namun, memperkenalkan keacakan segera membawa masalah baru dalam bentuk pola kebisingan yang nyata, yang membutuhkan penggunaan filter blur untuk memuluskan hasilnya. Di bawah ini adalah contoh algoritma (penulis - John Chapman ) dan masalah khasnya: pola garis dan derau.


Seperti yang dapat dilihat, pita yang terlihat karena jumlah sampel yang sedikit dihilangkan dengan memperkenalkan pengacakan orientasi sampel.

Implementasi SSAO spesifik Crytek memiliki gaya visual yang dapat dikenali. Karena spesialis Crytek menggunakan inti bulat dari sampel, ini bahkan mempengaruhi permukaan datar seperti dinding, menjadikannya teduh - karena setengah volume inti sampel terendam di bawah geometri. Di bawah ini adalah tangkapan layar adegan dari Crysis yang ditunjukkan dalam skala abu-abu berdasarkan nilai faktor peneduh. Di sini efek "kelabu" terlihat jelas:


Untuk menghindari efek ini, kami akan pindah dari inti bola sampel ke belahan bumi yang berorientasi normal ke permukaan:


Ketika mengambil sampel dari belahan otak yang berorientasi normal, kita tidak harus memperhitungkan fragmen yang berada di bawah permukaan permukaan yang berdekatan dalam perhitungan koefisien naungan. Pendekatan ini menghilangkan naungan yang tidak perlu, secara umum, memberikan hasil yang lebih realistis. Pelajaran ini akan menggunakan pendekatan belahan bumi dan kode yang sedikit lebih halus dari pelajaran SSAO yang brilian oleh John Chapman .

Buffer data mentah


Proses menghitung faktor peneduh pada setiap fragmen memerlukan ketersediaan data tentang geometri sekitarnya. Secara khusus, kami membutuhkan data berikut:

  • Vektor posisi untuk setiap fragmen;
  • Vektor normal untuk setiap fragmen;
  • Warna difus untuk setiap fragmen;
  • Inti dari sampel
  • Vektor rotasi acak untuk setiap fragmen yang digunakan dalam reorientasi inti sampel.

Dengan menggunakan data pada koordinat fragmen dalam ruang spesies, kita dapat mengarahkan belahan dari inti sampel sepanjang vektor normal yang ditentukan dalam ruang spesies untuk fragmen saat ini. Kemudian, inti yang dihasilkan digunakan untuk membuat sampel dengan berbagai offset dari tekstur yang menyimpan data pada koordinat fragmen. Kami membuat banyak sampel di setiap fragmen, dan untuk setiap sampel yang kami buat, kami membandingkan nilai kedalamannya dengan nilai kedalaman dari buffer koordinat fragmen untuk memperkirakan jumlah naungan. Nilai yang dihasilkan kemudian digunakan untuk membatasi kontribusi komponen latar belakang dalam perhitungan pencahayaan akhir. Menggunakan vektor rotasi acak yang terpecah-pecah, kita dapat secara signifikan mengurangi jumlah sampel yang diperlukan untuk mendapatkan hasil yang layak, dan kemudian ini akan ditunjukkan.


Karena SSAO adalah efek yang diwujudkan dalam ruang layar, dimungkinkan untuk melakukan perhitungan langsung dengan merender layar quad penuh. Tapi kemudian kita tidak akan memiliki data tentang geometri tempat kejadian. Untuk mengatasi batasan ini, kami akan memberikan semua informasi yang diperlukan dalam tekstur, yang nantinya akan digunakan dalam shader SSAO untuk mengakses informasi geometris dan lainnya tentang adegan tersebut. Jika Anda dengan hati-hati mengikuti pelajaran ini, maka Anda harus sudah tahu dalam pendekatan yang dijelaskan tentang penampilan algoritma peneduhan tertunda. Ini adalah sebagian besar mengapa efek SSAO sebagai asli muncul dalam render dengan peneduhan yang ditangguhkan - setelah semua, tekstur yang menyimpan koordinat dan normal sudah tersedia di buffer-G.

Dalam pelajaran ini, efeknya diterapkan di atas versi kode yang sedikit disederhanakan dari pelajaran tentang pencahayaan yang ditangguhkan . Jika Anda belum membiasakan diri dengan prinsip-prinsip pencahayaan yang tertunda, saya sangat menyarankan Anda beralih ke pelajaran ini.

Karena akses ke informasi fragmen tentang koordinat dan normals seharusnya sudah tersedia karena buffer-G, shader fragmen pada tahap pemrosesan geometri cukup sederhana:

#version 330 core layout (location = 0) out vec4 gPosition; layout (location = 1) out vec3 gNormal; layout (location = 2) out vec4 gAlbedoSpec; in vec2 TexCoords; in vec3 FragPos; in vec3 Normal; void main() { //        gPosition = FragPos; //       gNormal = normalize(Normal); //    -   gAlbedoSpec.rgb = vec3(0.95); } 

Karena algoritma SSAO berpengaruh pada ruang layar, dan faktor peneduh dihitung berdasarkan area yang terlihat dari pemandangan, masuk akal untuk melakukan perhitungan dalam ruang tampilan. Dalam hal ini, variabel FragPos yang diperoleh dari vertex shader menyimpan posisi tepat di viewport. Perlu memastikan bahwa koordinat dan normals disimpan dalam buffer-G di ruang tampilan, karena semua perhitungan lebih lanjut akan dilakukan di dalamnya.

Ada kemungkinan untuk mengembalikan vektor posisi hanya berdasarkan kedalaman fragmen yang diketahui dan sejumlah sihir matematika, yang dijelaskan, misalnya, di blog Matt Pettineo. Ini, tentu saja, membutuhkan biaya perhitungan yang besar, tetapi itu menghilangkan kebutuhan untuk menyimpan data posisi dalam G-buffer, yang memakan banyak memori video. Namun, demi kesederhanaan, kode contoh akan meninggalkan pendekatan ini untuk pembelajaran pribadi.

Tekstur buffer warna gPosition dikonfigurasi sebagai berikut:

 glGenTextures(1, &gPosition); glBindTexture(GL_TEXTURE_2D, gPosition); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 

Tekstur ini menyimpan koordinat fragmen dan dapat digunakan untuk mendapatkan data kedalaman untuk setiap titik dari inti sampel. Saya perhatikan bahwa tekstur menggunakan format data floating-point - ini akan memungkinkan koordinat fragmen tidak dikurangi ke interval [0., 1.]. Perhatikan juga mode pengulangan - GL_CLAMP_TO_EDGE diatur. Hal ini diperlukan untuk menghilangkan kemungkinan tidak melakukan oversampling di ruang layar secara sengaja. Melampaui interval utama koordinat tekstur akan memberi kita posisi dan kedalaman data yang salah.

Selanjutnya, kita akan terlibat dalam pembentukan inti hemispherical sampel dan penciptaan metode orientasi acak.

Menciptakan belahan otak yang berorientasi normal


Jadi, tugasnya adalah untuk membuat satu set titik sampel yang terletak di dalam belahan bumi yang berorientasi sepanjang normal ke permukaan. Karena pembuatan kernel sampel untuk semua kemungkinan arah normal secara komputasi tidak dapat dicapai, kami menggunakan transisi ke ruang singgung , di mana normal selalu direpresentasikan sebagai vektor ke arah semiaxis positif Z.


Dengan asumsi jari-jari belahan bumi menjadi satu proses tunggal, pembentukan inti sampel 64 poin terlihat seperti ini:

 //      0.0 - 1.0 std::uniform_real_distribution<float> randomFloats(0.0, 1.0); std::default_random_engine generator; std::vector<glm::vec3> ssaoKernel; for (unsigned int i = 0; i < 64; ++i) { glm::vec3 sample( randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) ); sample = glm::normalize(sample); sample *= randomFloats(generator); float scale = (float)i / 64.0; ssaoKernel.push_back(sample); } 

Di sini kita secara acak memilih koordinat x dan y dalam interval [-1., 1.], dan koordinat z dalam interval [0., 1.] (jika intervalnya sama dengan untuk x dan y , kita akan mendapatkan inti bola sampling). Vektor sampel yang dihasilkan akan terbatas pada hemisfer, karena inti sampel pada akhirnya akan berorientasi sepanjang normal ke permukaan.

Saat ini, semua titik sampel didistribusikan secara acak di dalam inti, tetapi demi kualitas efeknya, sampel yang berada lebih dekat ke asal kernel harus memberikan kontribusi yang lebih besar pada perhitungan koefisien shading. Ini dapat diwujudkan dengan mengubah distribusi titik sampel yang terbentuk dengan meningkatkan kepadatannya di dekat titik asal. Tugas ini mudah diselesaikan menggunakan fungsi interpolasi percepatan:

 scale = lerp(0.1f, 1.0f, scale * scale); sample *= scale; ssaoKernel.push_back(sample); } 

Fungsi lerp () didefinisikan sebagai:

 float lerp(float a, float b, float f) { return a + f * (b - a); } 

Trik semacam itu memberi kita distribusi yang dimodifikasi, di mana sebagian besar titik sampel terletak di dekat asal kernel.


Setiap vektor sampel yang diperoleh akan digunakan untuk menggeser koordinat fragmen dalam ruang spesies untuk mendapatkan data tentang geometri sekitarnya. Untuk mendapatkan hasil yang layak saat bekerja di viewport, Anda mungkin perlu jumlah sampel yang mengesankan, yang pasti akan menekan kinerja. Namun, pengenalan pseudo-random noise atau rotasi vektor sampel di setiap fragmen yang diproses akan secara signifikan mengurangi jumlah sampel yang diperlukan dengan kualitas yang sebanding.

Rotasi acak dari inti sampel


Jadi, memasukkan keacakan dalam distribusi titik-titik dalam inti sampel dapat secara signifikan mengurangi persyaratan jumlah titik-titik ini untuk mendapatkan efek kualitas yang layak. Dimungkinkan untuk membuat vektor rotasi acak untuk setiap fragmen adegan, tetapi terlalu mahal dari memori. Ini lebih efisien untuk membuat tekstur kecil yang berisi satu set vektor rotasi acak, dan kemudian hanya menggunakannya dengan set mode pengulangan GL_REPEAT .

Buat array 4x4 dan isi dengan vektor rotasi acak yang berorientasi sepanjang vektor normal dalam ruang singgung:

 std::vector<glm::vec3> ssaoNoise; for (unsigned int i = 0; i < 16; i++) { glm::vec3 noise( randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f); ssaoNoise.push_back(noise); } 

Karena inti disejajarkan di sepanjang semiaxis positif Z dalam ruang singgung, kita membiarkan komponen z sama dengan nol - ini akan memastikan rotasi hanya di sekitar sumbu Z.

Selanjutnya, buat tekstur 4x4 dan isi dengan array vektor rotasi kami. Pastikan untuk menggunakan mode replay GL_REPEAT untuk ubin tekstur:

 unsigned int noiseTexture; glGenTextures(1, &noiseTexture); glBindTexture(GL_TEXTURE_2D, noiseTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 

Nah, sekarang kita memiliki semua data yang diperlukan untuk implementasi langsung dari algoritma SSAO!

Shader SSAO


Efek shader akan dieksekusi untuk setiap fragmen quad layar penuh, menghitung koefisien bayangan di masing-masingnya. Karena hasilnya akan digunakan pada tahap rendering lain yang menciptakan pencahayaan akhir, kita perlu membuat objek framebuffer lain untuk menyimpan hasil shader:

 unsigned int ssaoFBO; glGenFramebuffers(1, &ssaoFBO); glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO); unsigned int ssaoColorBuffer; glGenTextures(1, &ssaoColorBuffer); glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBuffer, 0); 

Karena hasil dari algoritma adalah satu-satunya bilangan real dalam [0., 1.], untuk penyimpanan akan cukup untuk membuat tekstur dengan satu-satunya komponen yang tersedia. Itulah sebabnya GL_RED ditetapkan sebagai format internal untuk buffer warna.

Secara umum, proses rendering tahap SSAO terlihat seperti ini:

 //  :  G- glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); [...] glBindFramebuffer(GL_FRAMEBUFFER, 0); //  G-      SSAO glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gPosition); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gNormal); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, noiseTexture); shaderSSAO.use(); SendKernelSamplesToShader(); shaderSSAO.setMat4("projection", projection); RenderQuad(); glBindFramebuffer(GL_FRAMEBUFFER, 0); //  :    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shaderLightingPass.use(); [...] glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer); [...] RenderQuad(); 

ShaderSSAO shader menerima tekstur buffer-G yang dibutuhkan sebagai input, serta tekstur noise dan inti sampel:

 #version 330 core out float FragColor; in vec2 TexCoords; uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D texNoise; uniform vec3 samples[64]; uniform mat4 projection; //             //      1280x720 const vec2 noiseScale = vec2(1280.0/4.0, 720.0/4.0); void main() { [...] } 

Perhatikan variabel noiseScale . Tekstur kecil kami dengan noise harus dipasang di seluruh permukaan layar, tetapi karena koordinat tekstur TexCoords berada dalam [0., 1.] ini tidak akan terjadi tanpa campur tangan kami. Untuk keperluan ini, kami menghitung faktor untuk koordinat tekstur, yang ditemukan sebagai rasio ukuran layar dengan ukuran tekstur noise:

 vec3 fragPos = texture(gPosition, TexCoords).xyz; vec3 normal = texture(gNormal, TexCoords).rgb; vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz; 

Karena ketika membuat tekstur noise texNoise kami mengatur mode repeat ke GL_REPEAT , sekarang akan diulang berkali-kali pada permukaan layar. Dengan randomVec , fragPos, dan nilai normal , kami dapat membuat matriks transformasi TBN dari garis singgung ke ruang spesies:

 vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); vec3 bitangent = cross(normal, tangent); mat3 TBN = mat3(tangent, bitangent, normal); 

Menggunakan proses Gram-Schmidt, kami membuat basis ortogonal yang dimiringkan secara acak di setiap fragmen berdasarkan nilai acak randomVec . Poin penting: karena dalam kasus ini tidak masalah bagi kami bahwa matriks TBN berorientasi tepat di sepanjang permukaan segitiga (seperti dalam kasus dengan paralaks pemetaan, kira-kira Per.), Maka kita tidak perlu data tangen dan bi-tangen yang telah dihitung sebelumnya.

Selanjutnya, kita pergi melalui array dari inti sampel, menerjemahkan setiap vektor sampel dari ruang singgung ke ruang spesies dan mendapatkan jumlahnya dengan posisi fragmen saat ini. Kemudian kita membandingkan nilai kedalaman dari jumlah yang dihasilkan dengan nilai kedalaman yang diperoleh dengan mengambil sampel dari tekstur buffer-G yang sesuai.

Meskipun terdengar membingungkan, mari kita ikuti langkah-langkahnya:

 float occlusion = 0.0; for(int i = 0; i < kernelSize; ++i) { //     vec3 sample = TBN * samples[i]; //      - sample = fragPos + sample * radius; [...] } 

Di sini, kernelSize dan radius adalah variabel yang mengontrol karakteristik efek. Dalam hal ini, mereka masing-masing adalah 64 dan 0,5. Pada setiap iterasi, kami menerjemahkan vektor inti sampel ke dalam ruang spesies. Selanjutnya, kami menambah nilai yang diperoleh dari perpindahan sampel di ruang spesies nilai posisi fragmen di ruang spesies. Dalam hal ini, nilai offset dikalikan dengan variabel radius, yang mengontrol jari-jari inti sampel efek SSAO.

Setelah langkah-langkah ini, kita harus mengubah vektor sampel yang dihasilkan menjadi ruang layar, sehingga kita dapat memilih dari tekstur buffer-G yang menyimpan posisi dan kedalaman fragmen menggunakan nilai proyeksi yang diperoleh. Karena sampel ada di viewport, kita memerlukan matriks proyeksi proyeksi :

 vec4 offset = vec4(sample, 1.0); offset = projection * offset; //     offset.xyz /= offset.w; //   offset.xyz = offset.xyz * 0.5 + 0.5; //    [0., 1.] 

Setelah konversi ke ruang klip, kami secara manual melakukan pembagian perspektif dengan hanya membagi komponen xyz dengan komponen w . Vektor yang dihasilkan dalam koordinat perangkat dinormalisasi ( NDC ) diterjemahkan ke dalam interval nilai [0., 1.] sehingga dapat digunakan sebagai koordinat tekstur:

 float sampleDepth = texture(gPosition, offset.xy).z; 

Kami menggunakan komponen xy dari vektor sampel untuk memilih dari tekstur posisi buffer-G. Kami mendapatkan nilai kedalaman (komponen z ) yang sesuai dengan vektor sampel ketika dilihat dari posisi pengamat (ini adalah fragmen terlihat unshielded pertama). Jika pada saat yang sama kedalaman pengambilan sampel yang diperoleh lebih besar dari kedalaman yang disimpan, maka kami meningkatkan koefisien naungan:

 occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0); 

Perhatikan offset bias , yang ditambahkan ke kedalaman fragmen asli (atur dalam contoh ke 0,025). Offset ini tidak selalu diperlukan, tetapi keberadaan variabel memungkinkan Anda untuk mengontrol bagaimana efek SSAO terlihat, dan juga, dalam situasi tertentu, menghilangkan masalah dengan riak di area yang diarsir.

Tapi ini tidak semua, karena implementasi seperti itu mengarah pada artefak yang terlihat. Ini memanifestasikan dirinya dalam kasus-kasus ketika sebuah fragmen yang terletak di dekat tepi permukaan tertentu dipertimbangkan. Dalam situasi seperti itu, ketika membandingkan kedalaman, algoritma pasti akan menangkap kedalaman permukaan, yang dapat terletak sangat jauh di belakang yang dianggap. Di tempat-tempat ini, algoritma secara keliru akan sangat meningkatkan tingkat bayangan, yang akan menciptakan lingkaran cahaya gelap yang terlihat di tepi objek. Artefak diperlakukan dengan memperkenalkan pemeriksaan jarak tambahan (contoh oleh John Chapman ):


Pemeriksaan akan membatasi kontribusi pada koefisien naungan hanya untuk nilai kedalaman yang berada dalam radius sampel:

 float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth)); occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0) * rangeCheck; 

Kami juga menggunakan fungsi GLSL smoothstep () , yang mengimplementasikan interpolasi halus dari parameter ketiga antara yang pertama dan yang kedua. Pada saat yang sama, mengembalikan 0 jika parameter ketiga kurang dari atau sama dengan yang pertama, atau 1 jika parameter ketiga lebih besar atau sama dengan yang kedua. Jika perbedaan kedalaman berada dalam radius , maka nilainya akan dihaluskan dengan lancar dalam interval [0., 1.] sesuai dengan kurva ini:


Jika kita menggunakan batas yang jelas dalam kondisi memeriksa kedalaman, ini akan menambah artefak dalam bentuk batas tajam di tempat-tempat di mana nilai perbedaan kedalaman berada di luar batas radius .

Dengan sentuhan akhir, kami menormalkan nilai koefisien naungan menggunakan ukuran inti sampel dan mencatat hasilnya. Kami juga membalikkan nilai akhir dengan menguranginya dari kesatuan, sehingga Anda dapat menggunakan nilai akhir secara langsung untuk memodulasi komponen latar belakang pencahayaan tanpa langkah-langkah tambahan:

 } occlusion = 1.0 - (occlusion / kernelSize); FragColor = occlusion; 

Untuk adegan dengan nanosuit berbaring yang familier bagi kami, melakukan SSAO shader memberikan tekstur berikut:


Seperti yang Anda lihat, efek bayangan latar belakang menciptakan ilusi kedalaman yang bagus. Hanya gambar keluaran shader yang sudah memungkinkan Anda untuk membedakan detail kostum dan memastikan bahwa itu benar-benar terletak di lantai, dan tidak melayang agak jauh darinya.

Namun demikian, efeknya jauh dari ideal, karena pola kebisingan yang diperkenalkan oleh tekstur vektor rotasi acak mudah terlihat. Untuk memperlancar hasil perhitungan SSAO, kami menerapkan filter blur.

Mengaburkan bayangan latar belakang


Setelah membangun hasil SSAO dan sebelum pencampuran akhir pencahayaan, perlu untuk mengaburkan tekstur yang menyimpan data pada koefisien naungan. Untuk melakukan ini, kita akan memiliki framebuffer lain:

 unsigned int ssaoBlurFBO, ssaoColorBufferBlur; glGenFramebuffers(1, &ssaoBlurFBO); glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO); glGenTextures(1, &ssaoColorBufferBlur); glBindTexture(GL_TEXTURE_2D, ssaoColorBufferBlur); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBufferBlur, 0); 

Ubin tekstur noise di ruang layar memberikan karakteristik keacakan yang jelas yang dapat Anda gunakan untuk keuntungan Anda saat membuat filter blur:

 #version 330 core out float FragColor; in vec2 TexCoords; uniform sampler2D ssaoInput; void main() { vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0)); float result = 0.0; for (int x = -2; x < 2; ++x) { for (int y = -2; y < 2; ++y) { vec2 offset = vec2(float(x), float(y)) * texelSize; result += texture(ssaoInput, TexCoords + offset).r; } } FragColor = result / (4.0 * 4.0); } 

Shader hanya mentransisikan texels dari tekstur SSAO dengan offset dari -2 ke +2, yang sesuai dengan ukuran aktual dari noise noise. Offset sama dengan ukuran tepat satu texel: fungsi teksturSize () digunakan untuk perhitungan, yang mengembalikan vec2 dengan dimensi tekstur yang ditentukan. T.O. Shader hanya rata-rata hasil yang disimpan dalam tekstur, yang memberikan blur cepat dan cukup efektif:


Secara total, kami memiliki tekstur dengan data bayangan latar belakang untuk setiap fragmen di layar - semuanya siap untuk tahap pengurangan gambar akhir!

Terapkan Background Shading


Langkah penerapan koefisien naungan dalam perhitungan akhir pencahayaan sangat sederhana: untuk setiap fragmen, cukup dengan melipatgandakan nilai komponen latar belakang sumber cahaya dengan koefisien naungan dari tekstur yang disiapkan. Anda dapat mengambil shader yang sudah jadi dengan model Blinn-Fong dari pelajaran tentang naungan yang ditangguhkan dan sedikit memperbaikinya:

 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D gAlbedo; uniform sampler2D ssao; struct Light { vec3 Position; vec3 Color; float Linear; float Quadratic; float Radius; }; uniform Light light; void main() { //    G- vec3 FragPos = texture(gPosition, TexCoords).rgb; vec3 Normal = texture(gNormal, TexCoords).rgb; vec3 Diffuse = texture(gAlbedo, TexCoords).rgb; float AmbientOcclusion = texture(ssao, TexCoords).r; //   -    //   :   -  vec3 ambient = vec3(0.3 * Diffuse * AmbientOcclusion); vec3 lighting = ambient; //    (0, 0, 0)   - vec3 viewDir = normalize(-FragPos); //   vec3 lightDir = normalize(light.Position - FragPos); vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color; //   vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(Normal, halfwayDir), 0.0), 8.0); vec3 specular = light.Color * spec; //   float dist = length(light.Position - FragPos); float attenuation = 1.0 / (1.0 + light.Linear * dist + light.Quadratic * dist * dist); diffuse *= attenuation; specular *= attenuation; lighting += diffuse + specular; FragColor = vec4(lighting, 1.0); } 

Hanya ada dua perubahan besar: transisi ke perhitungan di viewport dan penggandaan komponen pencahayaan latar belakang dengan nilai AmbientOcclusion . Contoh pemandangan dengan cahaya titik biru tunggal:


Kode sumber lengkap ada di sini .

Manifestasi dari efek SSAO sangat tergantung pada parameter seperti kernelSize , radius dan bias , yang sering kali fine-tuning mereka adalah masalah bagi artis untuk mencari lokasi / adegan tertentu. Tidak ada kombinasi parameter "terbaik" dan universal: untuk beberapa adegan, jari-jari kecil inti sampel baik, sementara yang lain mendapat manfaat dari peningkatan jari-jari dan jumlah sampel. Contoh ini menggunakan 64 titik sampel, yang, terus terang, berlebihan, tetapi Anda selalu dapat mengedit kode dan melihat apa yang terjadi dengan jumlah sampel yang lebih sedikit.

Selain seragam yang terdaftar yang bertanggung jawab untuk mengatur efek, ada kemungkinan untuk secara eksplisit mengontrol tingkat keparahan efek bayangan latar belakang. Untuk melakukan ini, cukup menaikkan koefisien ke tingkat yang dikendalikan oleh seragam lain:

 occlusion = 1.0 - (occlusion / kernelSize); FragColor = pow(occlusion, power); 

Saya menyarankan Anda untuk meluangkan waktu pada permainan dengan pengaturan, karena ini akan memberikan pemahaman yang lebih baik tentang sifat perubahan dalam gambar akhir.

Untuk meringkas, perlu dikatakan bahwa meskipun efek visual dari penerapan SSAO agak halus, dalam adegan dengan pencahayaan yang ditempatkan dengan baik itu tidak dapat disangkal menambahkan sebagian kecil dari realisme. Memiliki alat seperti itu di gudang senjata Anda tentu berharga.

Sumber Daya Tambahan


  1. Tutorial SSAO : Artikel pelajaran yang sangat bagus dari John Chapman, yang menjadi dasar dibangunnya kode untuk pelajaran ini.
  2. Ketahui artefak SSAO Anda : Artikel yang sangat berharga dengan jelas menunjukkan tidak hanya masalah paling mendesak dengan kualitas SSAO, tetapi juga cara untuk menyelesaikannya. Bacaan yang disarankan.
  3. SSAO Dengan Rekonstruksi Kedalaman : Tambahan pada pelajaran SSAO utama oleh OGLDev mengenai teknik yang biasa digunakan untuk mengembalikan koordinat fragmen berdasarkan kedalaman. Pentingnya pendekatan ini adalah karena penghematan memori yang signifikan karena kurangnya kebutuhan untuk menyimpan posisi dalam buffer-G. Pendekatannya sangat universal, itu berlaku untuk SSAO sejauh.

PS : Kami punya telegram conf untuk koordinasi transfer. Jika Anda memiliki keinginan serius untuk membantu penerjemahan, silakan!

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


All Articles