opengl
Grundläggande belysning
Sök…
Phong Lighting Model
OBS: Det här exemplet är WIP, det kommer att uppdateras med diagram, bilder, fler exempel etc.
Vad är Phong?
Phong är en mycket grundläggande, men verklig snygg ljusmodell för ytor som har tre delar: omgivande, diffus och speciell belysning.
Ambient Lighting:
Omgivande belysning är den enklaste av de tre delarna att förstå och beräkna. Omgivande belysning är ljus som översvämmar scenen och tänder upp objektet jämnt i alla riktningar.
De två variablerna i omgivningsbelysningen är styrkan i omgivningen och färgen på omgivningen. I din fragmentskydd fungerar följande för omgivning:
in vec3 objColor;
out vec3 finalColor;
uniform vec3 lightColor;
void main() {
float ambientStrength = 0.3f;
vec3 ambient = lightColor * ambientStrength;
finalColor = ambient * objColor;
}
Diffus belysning:
Diffus belysning är något mer komplex än omgivande. Diffus belysning är riktningsljus, vilket innebär att ansikten som vetter mot ljuskällan blir bättre belysta och ansikten som pekar bort blir mörkare på grund av hur ljuset träffar dem.
Obs: diffus belysning kräver användning av normaler för varje ansikte som jag inte kommer att visa hur man beräknar här. Om du vill lära dig hur du gör detta kan du kolla in 3D-matematikssidan.
För att modellera reflektion av ljus i datorgrafik används en Bidirectional reflectance distribution function (BRDF). BRDF är en funktion som ger förhållandet mellan det reflekterade ljuset längs en utgående riktning och ljuset infaller från en inkommande riktning.
En perfekt diffus yta har en BRDF som har samma värde för alla infallande och utgående riktningar. Detta reducerar beräkningarna väsentligt och därför används det vanligtvis för att modellera diffusa ytor eftersom det är fysiskt plausibelt, även om det inte finns några rena diffusa material i den verkliga världen. Denna BRDF kallas lambertisk reflektion eftersom den följer Lamberts kosinuslag.
Lambertisk reflektion används ofta som modell för diffus reflektion. Denna teknik får alla stängda polygoner (till exempel en triangel i ett 3D-nät) att reflektera ljus lika i alla riktningar när de återges. Diffusionskoefficienten beräknas utifrån vinkeln mellan den normala vektorn och ljusvektorn.
f_Lambertian = max( 0.0, dot( N, L )
där N
är ytans normala vektor, och L
är vektorn mot ljuskällan.
Hur det fungerar
I allmänhet är prickprodukten för 2 vektorer lika med kosinus för vinkeln mellan de två vektorerna multiplicerad med storleken (längden) för båda vektorerna.
dot( A, B ) == length( A ) * length( B ) * cos( angle_A_B )
Detta följer att punktprodukten för 2 enhetsvektorer är lika med kosinus för vinkeln mellan de två vektorerna, eftersom längden på en enhetsvektor är 1.
uA = normalize( A )
uB = normalize( B )
cos( angle_A_B ) == dot( uA, uB )
Om vi tittar på cos (x) -funktionen mellan vinklarna -90 ° och 90 ° så kan vi se att den har maximalt 1 i en vinkel på 0 ° och den går ner till 0 vid vinklarna 90 ° och -90 °.
Detta beteende är exakt det vi vill ha för reflektionsmodellen. När ytans nromala vetor och ledningen till ljuskällan är i samma riktning (vinkeln mellan är 0 °), vill vi ha ett maximium av reflektion. Däremot, om vektorerna är orthonormaliserade (vinkeln däremellan är 90 °), vill vi ha ett minimum av reflektion och vi vill ha en smidig och kontinuerlig funktion som går mellan de två gränserna mellan 0 ° och 90 °.
Om ljuset beräknas per toppunkt beräknas reflektionen för varje hörn av det primitiva. Mellan primitiven interpoleras reflektionerna enligt dess barycentriska koordinater. Se de resulterande reflektionerna på en sfärisk yta:
Okej, så för att börja med vår fragment shader, behöver vi fyra ingångar.
- Vertex normaler (ska vara i buffert och specificeras av vertex attributpekare)
- Fragmentposition (ska matas ut från toppskärmskiva till fragskuggare)
- Ljuskällans position (enhetlig)
- Ljus färg (enhetlig)
in vec3 normal;
in vec3 fragPos;
out vec3 finalColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 objColor;
Insidan av main är där vi behöver göra lite matte. Hela konceptet med diffus belysning baseras på vinkeln mellan den normala och ljusriktningen. Ju större vinkel, desto mindre ljus finns det tills 90 ° där det inte finns något ljus alls.
Innan vi kan börja beräkna mängden ljus behöver vi ljusriktningsvektorn. Detta kan hämtas genom att helt enkelt subtrahera ljuspositionen från fragmentpositionen som returnerar en vektor från ljuspositionen som pekar till fragmentpositionen.
vec3 lightDir = lightPos-fragPos;
Gå också vidare och normalisera de normal
och lightDir
vektorerna så att de är lika långa att arbeta med.
normal = normalize(normal);
lightDir = normalize(lightDir);
Nu när vi har våra vektorer kan vi beräkna skillnaden mellan dem. För att göra detta kommer vi att använda punktproduktfunktionen. I princip tar detta två vektorer och returnerar cos () för den bildade vinkeln. Detta är perfekt eftersom det vid 90 grader ger 0 och vid 0 grader kommer det att ge 1. Som ett resultat, när ljuset pekar direkt mot objektet kommer det att vara helt upplyst och vice versa.
float diff = dot(normal, lightDir);
Det är en sak till som vi måste göra för det beräknade antalet, vi måste se till att det alltid är positivt. Om du tänker på det är ett negativt tal inte vettigt i sammanhanget eftersom det betyder att ljuset är bakom ansiktet. Vi kan använda en if-sats, eller så kan vi använda funktionen max()
som returnerar maximalt två ingångar.
diff = max(diff, 0.0);
Med det gjort är vi nu redo att beräkna den slutliga utgångsfärgen för fragmentet.
vec3 diffuse = diff * lightColor;
finalColor = diffuse * objColor;
Speciell belysning:
Arbeta i progres, kom tillbaka senare.
Kombinerad
Arbeta pågår, kom tillbaka senare.
Nedanstående kod och bild visar dessa tre belysningskoncept tillsammans.