opengl
Podstawowe oświetlenie
Szukaj…
Model oświetlenia Phong
UWAGA: Ten przykład to WIP, zostanie zaktualizowany o diagramy, obrazy, więcej przykładów itp.
Co to jest Phong?
Phong jest bardzo prostym, ale prawdziwie wyglądającym modelem świetlnym dla powierzchni składających się z trzech części: oświetlenia otoczenia, rozproszonego i lustrzanego.
Otaczające światło:
Oświetlenie otoczenia jest najprostszą z trzech części do zrozumienia i obliczenia. Oświetlenie otoczenia to światło, które zalewa scenę i równomiernie oświetla obiekt we wszystkich kierunkach.
Dwie zmienne w oświetleniu otoczenia to siła otoczenia i kolor otoczenia. W module cieniującym fragmenty następujące opcje będą działać w przypadku ambient:
in vec3 objColor;
out vec3 finalColor;
uniform vec3 lightColor;
void main() {
float ambientStrength = 0.3f;
vec3 ambient = lightColor * ambientStrength;
finalColor = ambient * objColor;
}
Oświetlenie rozproszone:
Oświetlenie rozproszone jest nieco bardziej złożone niż otoczenie. Oświetlenie rozproszone jest światłem kierunkowym, co oznacza, że twarze zwrócone w stronę źródła światła będą lepiej oświetlone, a twarze skierowane w stronę przeciwną będą ciemniejsze ze względu na sposób, w jaki światło je uderza.
Uwaga: rozproszone oświetlenie będzie wymagało użycia normalnych dla każdej twarzy, której nie pokażę tutaj, jak obliczyć. Jeśli chcesz dowiedzieć się, jak to zrobić, sprawdź stronę matematyczną 3D.
Do modelowania odbicia światła w grafice komputerowej wykorzystuje się dwukierunkową funkcję dystrybucji odbicia (BRDF). BRDF to funkcja, która podaje zależność między światłem odbijanym wzdłuż kierunku wychodzącego a światłem padającym z kierunku przychodzącego.
Idealna powierzchnia rozproszona ma BRDF, który ma tę samą wartość dla wszystkich kierunków zdarzeń i wyjazdów. To znacznie zmniejsza obliczenia i dlatego jest powszechnie stosowane do modelowania powierzchni rozproszonych, ponieważ jest to fizycznie możliwe, nawet jeśli w prawdziwym świecie nie ma czystych materiałów rozproszonych. Ten BRDF nazywa się refleksją Lambertowską, ponieważ jest zgodny z prawem cosinusowym Lamberta.
Odbicie lambertowskie jest często stosowane jako model odbicia rozproszonego. Ta technika powoduje, że wszystkie zamknięte wielokąty (takie jak trójkąt w siatce 3D) odbijają światło równomiernie we wszystkich kierunkach podczas renderowania Współczynnik dyfuzji jest obliczany na podstawie kąta między wektorem normalnym a wektorem światła.
f_Lambertian = max( 0.0, dot( N, L )
gdzie N
jest normalnym wektorem powierzchni, a L
jest wektorem w kierunku źródła światła.
Jak to działa
Na ogół iloczyn skalarny 2 wektorów jest równa cosinusa kąta między 2 wektorów pomnożony przez wielkość (długość) obu wektorów.
dot( A, B ) == length( A ) * length( B ) * cos( angle_A_B )
Wynika z tego, że iloczyn punktowy 2 wektorów jednostkowych jest równy cosinus kąta między 2 wektorami, ponieważ długość wektora jednostkowego wynosi 1.
uA = normalize( A )
uB = normalize( B )
cos( angle_A_B ) == dot( uA, uB )
Jeśli spojrzymy na funkcję cos (x) między kątami -90 ° i 90 °, wówczas możemy zauważyć, że ma ona maksymalnie 1 pod kątem 0 ° i spada do 0 pod kątem 90 ° i -90 °.
Takie zachowanie jest dokładnie tym, czego chcemy dla modelu odbicia. Kiedy nromalny vetor powierzchni i kierunek do źródła światła są w tym samym kierunku (kąt między 0 °), to chcemy maksymalnego odbicia. Natomiast jeśli wektory są ortonormalizowane (kąt pomiędzy 90 °), to chcemy minimalnego odbicia i chcemy płynnego i ciągłego działania między dwiema granicami 0 ° i 90 °.
Jeśli światło jest obliczane dla wierzchołka, odbicie jest obliczane dla każdego rogu prymitywu. Pomiędzy prymitywami odbicia są interpolowane zgodnie ze współrzędnymi barycentrycznymi. Zobacz powstałe odbicia na kulistej powierzchni:
Ok, więc aby zacząć od naszego shadera fragmentów, potrzebujemy czterech danych wejściowych.
- Normalne wierzchołki (powinny być w buforze i określone przez wskaźniki atrybutów wierzchołków)
- Pozycja fragmentu (powinna być wyprowadzana z shadera wierzchołków do shadera fragów)
- Pozycja źródła światła (jednolita)
- Barwa światła (jednolita)
in vec3 normal;
in vec3 fragPos;
out vec3 finalColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 objColor;
Wewnątrz głównego jest miejsce, w którym musimy zrobić matematykę. Cała koncepcja rozproszonego oświetlenia opiera się na kącie między normalnym a kierunkiem światła. Im większy kąt, tym mniej światła dochodzi do 90 °, gdzie nie ma światła.
Zanim zaczniemy obliczać ilość światła, potrzebujemy wektora kierunku światła. Można to odzyskać, po prostu odejmując pozycję światła od pozycji fragmentu, która zwraca wektor z pozycji światła wskazującej pozycję fragmentu.
vec3 lightDir = lightPos-fragPos;
Idź dalej i znormalizuj wektory normal
i lightDir
aby miały taką samą długość do pracy.
normal = normalize(normal);
lightDir = normalize(lightDir);
Teraz, gdy mamy już nasze wektory, możemy obliczyć różnicę między nimi. W tym celu wykorzystamy funkcję iloczynu punktowego. Zasadniczo bierze to 2 wektory i zwraca cos () utworzonego kąta. Jest to idealne, ponieważ przy 90 stopniach da 0, a przy 0 stopniach da 1. W rezultacie, gdy światło skierowane jest bezpośrednio na obiekt, będzie w pełni oświetlone i odwrotnie.
float diff = dot(normal, lightDir);
Jest jeszcze jedna rzecz, którą musimy zrobić z obliczoną liczbą, musimy upewnić się, że jest ona zawsze dodatnia. Jeśli się nad tym zastanowić, liczba ujemna nie ma sensu w kontekście, ponieważ oznacza to, że światło znajduje się za twarzą. Możemy użyć instrukcji if lub możemy użyć funkcji max()
która zwraca maksymalnie dwa dane wejściowe.
diff = max(diff, 0.0);
Po wykonaniu tej czynności jesteśmy teraz gotowi obliczyć ostateczny kolor wyjściowy dla fragmentu.
vec3 diffuse = diff * lightColor;
finalColor = diffuse * objColor;
Oświetlenie lustrzane:
Pracuj w toku, sprawdź później.
Łączny
Prace w toku, sprawdź później.
Poniższy kod i zdjęcie pokazują te trzy koncepcje oświetlenia łącznie.