Cienie są ważnym elementem oświetlenie. Dodają realizmu do sceny i dzięki nim łatwiej jest graczowi zorientować się w przestrzennym rozłożeniu obiektów. Na powyższym obrazku możesz zobaczyć, że dużo łatwiej jest okreśłić położenie kostek, gdy rzucają one cienie. Podstawową techniką generowania cieni jest shadow mapping i wiele bardziej zaawansowanych technik na niej bazuje.
Składa się on z dwóch kroków. W pierwszym obliczamy mapy głębokości z perspektywy źródła światła i zapisaniu do tekstury (tą teksturę nazywamy shadowmap), w drugim przy rysowaniu fragmentu porównujemy jego odległość do źródła światła z odległością zapisaną w teksturze.
Celem tych zajęć będzie dodanie cieni do początkowej sceny. W obecnym projekcie są 3 źródła światła: światło słoneczne, reflektor samolociku i lampa planetarna. W trakcie zajęć skupimy się na świetle słonecznym.
Framebuffer to obiekt, do którego rednerowana jest scena w postaci tekstury. Do tej pory korzystaliśmy z domyślnego Famebuffora, który był wyświetlany na ekranie. Teraz potrzebujemy dodatkowy, który będzie przechwytywał mapę głębokości. Tworzymy go w następujący sposób
1, &depthMapFBO); glGenFramebuffers(
Zmienna depthMapFBO
jest jak to typu unsignet int
i powinna być dostępna globalnie. Kolejnym krokiem jest stworzenie tekstury głębokości
1, &depthMap);
glGenTextures(
glBindTexture(GL_TEXTURE_2D, depthMap);0, GL_DEPTH_COMPONENT,
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
SHADOW_WIDTH, SHADOW_HEIGHT,
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);
Również zmienna depthMap
jest jak to typu unsignet int
i powinna być dostępna globalnie. Tworzymy teksturę, zaznaczamy, że jest to tekstura głębokości nadając jej format GL_DEPTH_COMPONENT
. Parametry SHADOW_WIDTH
, SHADOW_HEIGHT
są ustalone globalnie i oba wynoszą 1024.
I w końcu podpinamy teksturę pod FBO.
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap,
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);0); glBindFramebuffer(GL_FRAMEBUFFER,
Utwórz funkcję initDepthMap
, w której zainicjalizujesz mapę głębokości. Wywołaj ją w funkcji init
.
W tej części będziemy uzupełniać funkcję renderShadowapSun
. Funkcja ma za zadanie zapisać w FBO mapę głębokości z perspektywy słońca. Pierwsze co musimy mieć, to parę shaderów, która będzie renderować mapę. Ponieważ jedyne co potrzebujemy tylko rozmieścić obiekty w odpowiednich miejscach. Shader wierzchołków ustawia tylko pozycję w oparciu o przesyłanie macierze.
#version 430 core
0) in vec3 vertexPosition;
layout(location = 1) in vec3 vertexNormal;
layout(location = 2) in vec2 vertexTexCoord;
layout(location =
uniform mat4 viewProjectionMatrix;
uniform mat4 modelMatrix;
void main()
{1.0);
gl_Position = viewProjectionMatrix * modelMatrix * vec4(vertexPosition, }
Natomiast shader fragmentów jest pusty, ponieważ nic nie wysyłamy a głębokość zapisywana jest automatycznie.
#version 430 core
void main()
{ }
Dodaj utwórz parę shaderów jak powyżej, załaduj je do zmiennej globalnej o nazwie programDepth
i aktywuj go w funkcji renderShadowapSun
. Utwórz funkcję drawObjectDepth
. która będzie przyjmować referencję do RenderContext
, macierz viewProjection
i macierz modelu oraz przesyłać macierze do GPU i rysować RenderContext
Musimy zdefiniować macierz widoku i rzutowania, które mamy przesłać. Implementujemy cienie dla oświetlenia kierunkowego, gdzie dla każdego punktu kierunek światła jest taki sam. W takim wypadku skorzystamy z rzutowania prostopadłego.
Do stworzenia macierzy rzutowania perspektywicznego wykorzystamy funkcję:
glm::mat4 lightProjection = glm::ortho(float left,
float right,
float bottom,
float top,
float zNear,
float zFar
)
Tworzy ona macierz rzutowania prostopadłego dla zadanych wymiarów. Musimy tak je dobrać, żeby rzutowanie zawierało całą interesującą scenę. Za małe wartości spowodują artefakty a za duże pogorszą jakość. Przykładowo możesz wziąć glm::ortho(-10.f, 10.f, -10.f, 10.f, 1.0f, 30.0f)
. Do stworzenia macierzy kamery wykorzystamy funkcję glm::lookAt
z poniższymi argumentami argumentami
0, 1, 0)) glm::lookAt(sunPos, sunPos - sunDir, glm::vec3(
Uzupełnij funkcję renderShadowapSun
. Wywołaj w niej instrukcje
//ustawianie przestrzeni rysowania
0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glViewport(//bindowanie FBO
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);//czyszczenie mapy głębokości
glClear(GL_DEPTH_BUFFER_BIT);//ustawianie programu
glUseProgram(programDepth);
Stwórz macierz viewProjection
10.f, 10.f, -10.f, 10.f, 1.0f, 30.0f) * glm::lookAt(sunPos, sunPos - sunDir, glm::vec3(0, 1, 0)); glm::mat4 lightVP = glm::ortho(-
następnie wywołaj drawObjectDepth
dla każdego obiektu, który rysujemy w naszej scenie. wykorzystaj macierz viewProjection
zdefiniowaną wyżej, użyj tej samej macierzy modelu co przy właściwym rysowaniu.
Zakończ funkcję linią glBindFramebuffer(GL_FRAMEBUFFER, 0);
, która przywraca domyślny FBO.
odkomentuj linie:
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glUseProgram(programTest);
//glActiveTexture(GL_TEXTURE0);
//glBindTexture(GL_TEXTURE_2D, depthMap);
//Core::DrawContext(models::testContext);
znajdujące się w renderScene
. Rysują one prostokąt z mapą głębokości jako teksturą. Jeżeli wszystko zostało wykonane poprawnie, to powinien on zawierać rzutowanie naszego pokoju.
W tej chwili rzutowanie jest nieoptymalne. popraw je na lepsze. Zmodyfikuj wartości w glm::ortho(-10.f, 10.f, -10.f, 10.f, 1.0f, 30.0f)
do takich, żeby pokój wypełniał jak największą część tekstury.
Na tym etapie powinniśmy mieć poprawnie stworzoną mapę głębokości. Pozostaje wykorzystać ją w oświetleniu. ### Zadanie #### Przesłanie danych W funkcji drawObjectPBR
prześlij teksturę za pomocą instrukcji:
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, depthMap);
oraz macierz LightVP
, która musi być taka sama jak w drawObjectDepth
.
Następnie w shaderze wierzchołków odbierz LightVP
.
Oblicz pozycję wierzchołka z perspektywy słońca sunSpacePos=LightVP*modelMatrix*vec4(vertexPosition,1)
, wynik prześlij do shadera fragmentów #### Shader fragmentów odbierz sunSpacePos
i napisz funkcję calculateShadow
, która sprawdza czy obiekt jest zacieniony. Aby to zrobić kolejno w funkcji: * ujednorodnij zmienną lightSpacePos
dzieląc ją przez współrzędną w, * przeskaluj ją, ma wartości od -1 do 1 a potrzebujemy wartości od 0 do 1 (pomnóż przez 0.5 i dodaj 0.5) wynik zapisz do zmiennej lightSpacePosNormalized
, * pobierz głębokość z depthMap
próbkuj za pomocą współrzędnych x i y. Pobierz tylko kanał r
, zapisz go do zmiennej closestDepth
, * porównaj closestDepth
ze współrzędną z lightSpacePosNormalized
. jeżeli closestDepth
jest większa zwróć 1.0, w przeciwnym wypadku zwróć 0.0. * wynik funkcji przemnóż z sunColor
w trakcie oblicznia.
ilumination=ilumination+PBRLight(sunDir,sunColor,normal,viewDir);
Powinniśmy dostać cienie , jednak w niezacienionych strefach pojawiły się paski, które znane są jako shadow acne wynikają one z błędu przybliżenia liczb zmiennoprzecinkowych. Można się go pozbyć na dwa sposoby 1. dodać bias. zamiast sprawdzać closestDepth<lightSpacePosNormalized
można wziąć closestDepth+bias>lightSpacePosNormalized
, gdzie bias
to mała wartość (np 0.01). 2. innym rozwiązaniem jest, żeby przy renderowaniu cieni włączyć front face culling. dzięki temu rysowane będą część modelu, które są dalej niż te, które odpytujemy.
Dodaj rysowane cieni również dla latarki doczepionej do statku. pamiętaj, że musisz wykorzystać tutaj macierz rzutowania perspektywicznego