Ecranul ocluzie spațiu ambiental, luând în considerare normal și calcularea reflexia luminii
Într-un fel am vrut să lucrez cu grafica, și am decis să fac SSAO de la sol în sus, în baza experienței mele cu raze de urmărire și dobândite anterior cunoștințe despre modul în care în general ar trebui să funcționeze. În general, am stabilit sarcina de a scrie la demo-ul motorului său, folosind tot felul de tehnologii diferite. De asemenea, sa decis poizuchat umbrire amânat și ecran spațiu reflecții locale, dar mai multe despre asta altă dată. Acest articol se va concentra pe SSAO.
Pentru nerăbdător, aici este rezultatul:
Ceea ce spune Wikipedia despre ocluzie ambientală:
model de umbrire utilizate în grafică tridimensională și permite să adăugați o imagine realistă prin calcularea intensității luminii ajunge la suprafața punctului. Spre deosebire de metodele locale, cum ar fi Phong umbrire, metoda ocluzie ambientală este globală, adică valoarea luminozității fiecărui punct al obiectului depinde de alte obiecte din scenă. În principiu, este destul de vag amintește de iluminare la nivel mondial.
Se pare că avem nevoie pentru a calcula cât de mult lumina ajunge la punctul specific al emisferei, orientat de-a lungul normal în acest moment. Eu chiar pot desena o imagine în Photoshop:
Ceea ce vedem aici:
Pe partea de sus este un aparat de fotografiat care se uită la scena noastră.
Diferite culori arată punctul de pe obiect, normal și emisfera pe care o vom colecta umbrire.
Punctul fioleovym a indicat nici o umbră.
Punctul marcat cu galben - umbrită de destul de un pic.
Punctul indicat de albastru - umbrită de aproape jumătate.
Dar punctul obiectului în fundal, marcată în portocaliu, în teorie, din punctul de vedere al camerei este parțial blocată de obiectul din față, dar este relativ departe de obiect, atunci obiectul este de fapt un front care nu ascunde punctul. Cu aceasta va trebui să lupte singur, pentru a evita artefacte neplăcute sub formă de siluete întunecate ale obiectelor.
Astfel, avem nevoie pentru a calcula umbrire pentru fiecare punct, ținând cont de distanța până la obiect, pe care îl acoperă. Acest lucru va fi ocluzia noastră ambiantă.
Am decis, în contrast cu „tradiționale» SSAO (cum ar fi cel care, dacă îmi amintesc corect, primul utilizat în Crysis) numărului nu este în spațiu pe ecran, și în spațiul de vedere. Dezavantajul acestei abordări este mai complexitate de calcul (deși prea, trebuie să se uite, verifica și compara), plus - o AO mai precisă.
Deci, pentru a calcula ocluzie ambientală, avem nevoie de două texturi: adâncimea și normalelor.
Mai degrabă, prima funcție de aici nu au nevoie de măcar pentru că este folosit în înregistrarea normală.
Uite textura cu normalele va fi ca acest lucru:
Adâncimea texturii, vom organiza un „standard de adâncime» OpenGL.
adâncimea de valorile date în intervalul [-1..1] și construit în gradul 64th, eu arata ca ceva de genul:
Dar, după cum vom folosi spațiul de vedere, avem nevoie pentru a restabili poziția punctului de vedere adâncimea spațiului. Pentru informații cu privire la modul de a face acest lucru, a se vedea ultima secțiune a „nishtyak“. Pentru moment, să fie patru funcții în shader:
De asemenea, pentru a da varietate calculul nostru din AO, avem nevoie de o textură cu zgomot. Textura cea mai comună cu zgomot, chiar nu o voi arăta aici. În plus față de această textură mai ales pentru noi va fi coordonatele de textură necesare. O astfel de textură pentru a desena pe ecran un pixel texel. În general, acest lucru nu este obligatorie, dar este foarte de dorit ca proba a fost „mai casual“.
Total, la intrarea shader fragment avem trei texturi, și două seturi de coordonate textura.
Trebuie remarcat faptul că, în motorul meu pentru a sprijini diferite versiuni ale shader, următoarele lucruri sunt:
O variabilă membru în shader fragment - etFragmentIn. În shader mai vechi înlocuite cu varierea. nou înăuntru.
Rezultatul fragment shader scris în etFragmentOut variabilă (gl_FragColor în shadere vechi și "out vec4." + GlBindFragDataLocation noi versiuni).
bucata shader total avem deja:
Acum puteți trece direct la calcularea umbrire noastre.
Ideea generală este după cum urmează: în acest moment pentru a obține poziția și normală, apoi a genera câteva direcții aleatoare pe emisfera, un loc de muncă normală, și să le verifice în umbră. Rezultatul este colectat și împărțit la numărul de probe. Astfel, dorim să controleze cel puțin trei parametra:
1) numărul de eșantioane;
2) Distanța minimă la care vom verifica umbra (este necesar pentru noi pentru a scăpa de unele artefacte neplăcute);
3) distanța maximă la care vom verifica umbra;
Aici sunt cateva poze pentru compararea parametrilor: numărul de probe - cu atât mai mult, umbrirea mai buna si frumos, obținem:
Distanța maximă - atât mai mare, cu atât „mai mare“ și mai moale am umbrire:
Pentru a testa scena (Crytek Sponza) Am folosit următorii parametri:
Din păcate, nu am venit în minte, cum să scape de acești parametri și pentru a le calcula pe baza a ceea ce avem de pe ecran. Aș fi bucuros dacă cineva vă va spune unde să meargă în această direcție.
Așa că toți trebuie să calculeze umbrirea fiecare punct de pe ecran. Mai întâi trebuie să găsim normal în acest moment și poziția sa (nu uitați că suntem de lucru în spațiul de vedere). Acest lucru se face pur și simplu prin citirea de textura normală și restabilirea situației în profunzime:
Funcția etTexture2D - este, de asemenea, magia motorului meu. Pentru versiunile mai vechi ale GLSL se transformă în texture2D, nou în textură.
Acum, nu în oraș, tot în corpul principal funcției (), capul unei funcții speciale, care calculează umbrirea la un anumit punct. Am sunat-o performRaytracingInViewSpace patetic:
Ei bine, de fapt, nu la chin, restul shaderului:
Ca rezultat, vom fi shadowing acest punct. Dacă aveți nevoie de acoperire, scădem pur și simplu umbrirea unității:
Asta este, aici suntem doar palmat off la această funcție parametrii inițiali pentru punctul curent, și o valoare pseudo-aleatoare, pe care apoi actualizat (citit zgomot textura pe noile coordonate).
Astfel, secretul în calculul nostru funcției de umbrire. Să ne uităm la ea mai aproape.
Aici avem nevoie pentru a genera o direcție aleatoare pe emisfera, un loc de muncă în punctul normal. Eu o fac foarte simplu: normalizând valoarea texturii de zgomot, și dacă este în cealaltă jumătate de planul de noi dorit la normal, este înmulțită cu -1. Se pare ca acest lucru:
Din moment ce avem nevoie de toată emisfera, și avem câteva direcții preferate, această funcție este foarte potrivit. Dacă doriți să faceți selecții într-un anumit con - în motor este o funcție pentru aceasta (I pot arăta, dacă te afli).
Acum avem o direcție aleatoare în care vom face o selecție. Noi schimbare punct în această direcție, la o valoare aleatoare între MIN_SAMPLE_SIZE și SAMPLE_SIZE și a proiecta în spațiu pe ecran. Apoi obținem niște noi coordonate de textură și o adâncime în intervalul [0..1].
În continuare, ne uităm la ce adâncime suntem pe noile coordonate de textură, face o nouă selecție din adâncimea texturii. Apoi, verificați dacă noua adâncimea a fost mai mult decât ceea ce am primit după proiecția, atunci noul punct se află pe cont propriu și nu se pot suprapune, nu există nici o suprapunere - zero, întoarcerea din funcția de:
Și apoi, când am stabilit că noul punct este mai aproape de aparatul de fotografiat decât proiectat nostru, magia începe shadowing. Ceea ce avem la intrare:
- adâncimea punctului nostru proiectat (care este garantată de mai mult decât o nouă adâncime);
- adâncimea pe care am primit-o după ce proba (care este garantat să fie mai mică decât adâncimea punctului proiectat).
Ceea ce avem nevoie pentru a calcula:
- cât de mult obiectul (până la punctul pe care am dat peste) se suprapune peste punctul nostru de plecare.
Ceea ce trebuie să ia în considerare:
- mai aproape de punctul nou proiectat, cu atât mai mare suprapunere;
- în cazul în care un nou punct de puternic „departe“ de proiectat, cu atât mai puțin se suprapun.
Total: avem nevoie pentru a compara două adâncime non-liniară, care este probabil să fie mai aproape de unitate. Puteți restaura din nou adâncimea liniară, dar puteți face un pic hack și a obține o valoare care caracterizează adâncimea. După unele experimente, am ajuns la concluzia că funcția de forma
cele mai potrivite pentru o astfel de evaluare.
Total, avem două valori de evaluare de profunzime, ia diferența între ele, care va fi caracterizat prin distanța de un punct dintr-un alt:
Din moment ce vrem să controleze gradul de umbrire, putem introduce un koeffetsient pentru această diferență. In varianta finală se obține după cum urmează:
Pentru a testa modelul am folosit valoare DEPTH_DIFFERENCE_SCALE egală cu 3.33333. Totul depinde de măsura în care ne bazăm pe ceea ce vrem să calculeze umbrirea.
Avem acum o distanță între două puncte, să calculeze gradul de umbrire. Din nou, după multe experimente, am ajuns la concluzia că descrie cel mai bine funcția de umbrire de forma:
Pentru a face mai moale și plăcut, puteți lua în continuare în considerare distanța pe care am făcut-o probă (redus la intervalul [0..1]). Formula rezultată arată astfel:
Iată cum să afecteze scara distanțelor (cel care DEPTH_DIFFERENCE_SCALE)
Odată cu scăderea distanței (la DEPTH_DIFFERENCE_SCALE <1.0) за объектами появляются темные ореолы. При увеличении масштаба затенение становится практически незаметным.
Ei bine, de fapt, și toți pentru fiecare punct ne-am simțit umbrire.
Acum, că avem această caracteristică se răcească pentru calcularea umbrire să se gândească dacă se poate extinde într-un fel? Da, avem aproape fără încordare poate calcula prima culoare reflexie (după cum am înțeles, un algoritm cum ar fi spațiu pe ecran direcție ocluzie - căutați-l în Google). Tot ce avem nevoie - este de a face o selecție din textura cu culoarea noilor coordonate și ia în considerare impactul culorii asupra punctului curent. Presupunem că efectul culorii este direct proporțională cu cât de mult punct nou suprascrie originalul nostru. Prin urmare, acum noastră caracteristică de moda performRaytracingInViewSpace nu plutească se întoarce, iar culoarea (vec4), iar componenta alfa va păstra umbrire. De asemenea, avem nevoie de textura de culoare. Din moment ce am folosit în demo amânată umbrirea, este am deja, va trebui să se fixeze mai multe ținte face sau de a face mai multe treceri. În general, la intrare există o altă textură:
O funcție acum arata ca acest lucru:
Ei bine, în funcția principală, acum noi nu pluti, și vec4:
Ca rezultat, vom obține ceva de genul această imagine (canal alfa nu este afișat, iar luminozitatea este crescută cu un factor de 10 pentru claritate):
În această textură, după cum se poate observa, culoarea este stocată, care este reflectată de obiectele din jur. Noi folosim:
La prima vedere, diferentele un pic, dar ar trebui să arate la imaginea din dreapta: lumina este mult mai moale, iar pe pereți sunt reflecții mici pe „perdele“. În general, sa dovedit a folosi o tehnică interesantă la costuri minime.
Total, ce avem:
1) pentru a calcula un fel de acoperire globală, nu perediraya codul Shader inițial;
2) se calculează prima lumină de reflecție cu un efort minim și schimbări în algoritmul;
3) În cele din urmă, pentru a înțelege cum funcționează.
Așa cum a promis, în ultima parte, am posta fragmente de cod:
Înregistrarea și recuperarea normală (luată de aici):
Dispoziții de regenerare în spațiu vedere din partea de jos și proiectându-spate:
clipPlanes - este distanța față de planul de aproape și de departe de tăiere (ceea ce ne hrănim la stabilirea proiecției perspectivei funcției).
texCoordScales - componente inverse matrice de proiecție luate cu semnul minus.
Acestea sunt necesare pentru poziția corectă de recuperare în funcție de unghiul de vizualizare și viewport proporțiile.
În general, în demo, există o mulțime de lucruri, în plus față de umbrire și de reflexie a luminii:
Așa cum am spus la început, această implementare este foarte simplă și directă. Aici sunt link-uri către alte tehnici care sunt utilizate în jocurile: