diff --git a/grk/cw 6/Zadania 8.html b/grk/cw 6/Zadania 8.html new file mode 100644 index 0000000..d2e6ccc --- /dev/null +++ b/grk/cw 6/Zadania 8.html @@ -0,0 +1,150 @@ + + + + + + + Zadania 8 + + + + + + +

Physically based rendering

+

Physically based rendering jest zbiorem technik renderowania, które bazują na teorii, która ma za zadanie odwzorowywać świat rzeczywisty.

+

Podstawową dla obliczenia jest rendering equation następującej postaci.

+

\[L_o(p,\omega_o)=\int_\Omega f_r(p,\omega_i,\omega_o)L_i(p,\omega_i)n\cdot \omega_i\ d\omega_i\] Całka przechodzi po wszystkich kierunkach na półkuli \(\Omega\) oblicza wyjściowe oświetlenie \(L_O\) bazując na wejściowym oświetleniu \(L_i\) i funkcji \(f_r\) znanej jako BRDF, czyli bidirectional reflective distribution function. W najbardziej ogólnym przypadku należałoby traktować \(L_i\) jako dystrybucję i jest nadmiernym formalizmem w naszym przypadku, gdy będziemy wykorzystywać wyłącznie światła punktowe i kierunkowe. Dlatego o powyższej całce możemy myśleć jak o sumie po źródłach światła.

+

By uzyskać realistyczny efekt, potrzebujemy realistycznej funkcji \(f_r\). Wybierzemy Cook-Torrance BRDF, który jest najczęściej wykorzystywaną funkcją przy liczeniu PBR w czasie rzeczywistym.

+

Cook-Torrance BRDF rozdzielamy na światło rozproszone (diffuse) i odbite kierunkowo (specular)

+

\[f_r=k_d f_{lambert}+k_s f_{cook−torrance},\] gdzie \(k_d\) i \(k_s\) to są współczynniki, rozdzielające ile światła zostało rozproszone a ile odbite, ich suma musi być równa 1. komponent \(f_{lambert}\) jest znany jako Lambertian diffuse i wynosi \[f_{lambert}=\frac{c}{\pi}\] przy czym \(c\) to jest jest kolorem powierzchni a \(\pi\) odpowiada za normalizację.

+

Część odbicia kierunkowego jest bardziej skomplikowana i wygląda następująco:

+

\[f_{cook−torrance}=\frac{DFG}{4(w_o\cdot n)(w_i\cdot n)}\] gdzie \(D\), \(F\) i \(G\) są funkcjami, które zależą od normalnej, wektora wejściowego i wyjściowego oraz parametrów chropowatości \(\alpha\) i odbicia \(F_0\). Te funkcje to odpowiednio: - \(D\) - Normal distribution function przybliża ilość powierzchni, która jest ustawiona prostopadle do wektora połówkowego korzystamy z funkcji Trowbridge-Reitz GGX \[D(n,h,\alpha) = \frac{\alpha^2}{π((n⋅h)^2(α^2−1)+1)^2},\] gdzie \(h\) to: \[h = \frac{\omega_o+\omega_i}{||\omega_o+\omega_i||}\] - G - Geometry function opisuje stopień samo-zacieninienia. Wykorzystujemy Schlick-GGX \[G(n,\omega_o,k)=\frac{n⋅\omega_o}{(n⋅\omega_o)(1−k)+k}\] przy czym \(k\) wynosi \[k=\frac{(\alpha+1)^2}{8}\] - F - Fresnel equation opisuje stopień odbicia w zależności od kąta padania. Wykorzystujemy aproksymację Fresnela-Schlicka \[F(h,v,F_0)=F_0+(1−F_0)(1−(h⋅v))^5\] Parametr \(F_0\) jest własnością materiału dla uproszczenia uznaje się, że niemetale mają wartość (0.04,0.4,0.4) a dla metali jest ona równa kolorowi obiektu. Wprowadza się parametr metaliczności, który jest współczynnikiem, jakim miksujemy między (0.04,0.4,0.4) a kolorem, żeby uzyskać \(F_0\).

+

Zadanie

+

W projekcie znajduje się jedna kula, punktowe oświetlenie i cieniowanie Phonga. Zaimplementuj PBR i rozmieść kule w macierzy 10 x 10 zmieniając stopniowo w nich parametr chropowatości \(\alpha\) i metaliczności, jak na poniższym obrazku.

+

Teksturowanie

+

Do uzyskania realistycznego efektu wykorzystuje się szereg tekstur:

+

Tekstury Albedo i Normalnych odpowiadają za kolor i normalne jak przy cieniowaniu Phonga. Metalic i Roughness przechowuje wartość metaliczności i chropowatości, które pojawiły się w BRDF. Natomiast AO (ambient occlision) odpowiada za stopień samo-zacienienia, który modyfikuje oświetlenie ambientowe w danym punkcie, uwzględnienie jej podkreśla detale obiektów.

+

Karty graficzne posiadają ograniczenia, jeżeli chodzi o ilość tekstur, które można przesłać za jednym razem (współcześnie są to 32 tekstury). Może być to znaczące ograniczenie, dlatego AO Roughness i Metalic mogą być przesłane w jednej teksturze kolejno jako kanał r, g i b. Taką teksturę określa się jako arm od pierwszych liter nazw.

+

Zadanie

+

W ex_8_2.hpp znajduje się podstawowa scena. Zaimplementuj w niej PBR z użyciem tekstur. Stwórz nową parę shaderów, które będą obsługiwać PBRW oparciu o tekstury. Wyświetl statek zestawem tekstur w folderze textures/spaceshipPBR. Możesz też wziąć swój układ słoneczny z poprzednich zajęć. Ładowanie tekstur jest męczące, napisz klasę/strukturę PBRTexturesHandler, która przechowuje zestaw tekstur albedo, normal i arm. Posiada konstruktor, który ładuje tekstury po ścieżkach oraz funkcję activateTextures(int[] indices), która aktywuje tekstury z indeksami podanymi w tablicy. int[] indices.

+

PBR Multitexturing

+

Podobnie jak w przypadku zwykłych tekstur możemy mieszać tekstury przesyłane przez PBR, należy wtedy mieszać wszystkie tekstury z takim samym współczynnikiem. Jednak stopniowe przejście z jednej tekstury do drugiej często prowadzi to do nienaturalnych rezultatów, gdy próbujemy mieszać tekstury o różnej częstotliwości detali, jak na przykład poniżej: W prawdziwym świecie, przy stopniowej zmianie z kamiennego podłoża na piaszczyste, wiedzielibyśmy, jak piasek stopniowo zaczyna wypełniać szpary między kamieniami i przykrywać je coraz bardziej. Istnieje wiele metod mieszania tekstur np ta. My wykorzystamy technikę opartą na mapach wysokości, opisaną w https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting.

+
+

Mapa wysokości to dodatkowa tekstura, która wskazuje wysokość obiektów znajdujących się na teksturze względem płaskiej powierzchni. Może być również wykorzystana przy paralax mappingu czy tessalacji. Jest ona nazywana po angielsku heightmap, displacement map czy bump map w zależności od zastosowania. Przykładowe znajdują się w teksutrach.

+
+

Możemy te mapy wykorzystać do mieszania dwóch map tak, że wyświetlana będzie ta, która jest wyżej. Zaprezentować to możemy w poniższym jednowymiarowym schemacie. Niebieska linia reprezentuje mapę piasku a czerwona kamieni. Jako rezultat otrymujemy

+

Kod funkcji mieszającej:

+
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
+{
+    if (height1>height2){
+        return texture1;
+    }
+    else{
+        return texture2;
+    }
+}
+

To pozwala nam zmieszać dwie tekstury. Jednak samo w sobie nie powoduję przejścia z jednej do drugiej, to możemy osiągnąć modyfikując wysokość parametrem blend_ratio, który będzie z zakresu od 0 do 1. Na jednowymiarowym schemacie wartość wysokości wygląda to następująco. Daje nam to to poniższy efekt Kod nowej funkcji mieszającej:

+
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
+{
+    float h1=height1+1.0-blend_ratio;
+    float h2=height2+blend_ratio;
+    if (h1>h2){
+        return texture1;
+    }
+    else{
+        return texture2;
+    }
+}
+

To podejście dało nam bardziej naturalne przejście z piasku do kamieni. Widać jak kamienie stopniowo wyłaniają się spod piasku. Jednak pojawiły się artefakty w miejscach, gdzie wartości mapy wysokości są bardzo zbliżone. Wynika to z ograniczeń kwantyzacji. By zminimalizować te błędy, będziemy mieszać wartości tekstur, ale tylko, gdy wartości będą blisko siebie. Zdefiniujemy dodatkowy parametr mix_threshold, który będzie określał, jak blisko mają być wartości, żeby je mieszać. Jeżeli różnica między jedną wysokością a drugą będzie mniejsza od mix_threshold to będziemy je mieszać proporcjonalnie do ich różnicy podzielonej przez mix_threshold, w przeciwnym wypadku wybierzemy tą, która jest wyżej. W celu optymalizacji zamiast optymalizacji korzystamy z funkcji clamp

+
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
+{
+    float mix_threshold=0.1;
+    float h1=height1+1.0-blend_ratio;
+    float h2=height2+blend_ratio;
+    float havg=(h1+h2)/2.;
+    
+    h1 = clamp((h1-hav+0.5*mix_threshold)/(mix_threshold),0.,1.);
+    h2 = clamp((h2-hav+0.5*mix_threshold)/(mix_threshold),0.,1.);
+    
+    return (texture1*h1+texture2*h2)
+}
+

Stosunek w jakim je zmieszamy:

+

Finalna tekstura

+

Zadanie

+

W teksturach znajduje plik heightmap.png wykorzystaj go jako mapę wysokości przy rysowaniu jednej z planet. Mapa ta zawiera wartości, od 0 do 1. Dla różnych wysokości chcemy wykorzystywać różne tekstury, ustal na przykład, że poniżej 0,3 znajduje się woda, między 0,3 a 0,6 trawa, natomiast powyżej skały.

+

Zadanie*

+

Zmodyfikuj swój układ słoneczny z poprzednich zajęć, zmień w nim model oświetlenia z Phonga na PBR. Tekstury możesz pobrać na przykład z https://polyhaven.com/textures lub www.texturecan.com.

+ + diff --git a/grk/cw 6/Zadania 8.md b/grk/cw 6/Zadania 8.md new file mode 100644 index 0000000..f0a03e6 --- /dev/null +++ b/grk/cw 6/Zadania 8.md @@ -0,0 +1,125 @@ +# Physically based rendering + +Physically based rendering jest zbiorem technik renderowania, które bazują na teorii, która ma za zadanie odwzorowywać świat rzeczywisty. + +Podstawową dla obliczenia jest *rendering equation* następującej postaci. + +$$L_o(p,\omega_o)=\int_\Omega f_r(p,\omega_i,\omega_o)L_i(p,\omega_i)n\cdot \omega_i\ d\omega_i$$ +Całka przechodzi po wszystkich kierunkach na półkuli $\Omega$ oblicza wyjściowe oświetlenie $L_O$ bazując na wejściowym oświetleniu $L_i$ i funkcji $f_r$ znanej jako **BRDF**, czyli *bidirectional reflective distribution function*. W najbardziej ogólnym przypadku należałoby traktować $L_i$ jako dystrybucję i jest nadmiernym formalizmem w naszym przypadku, gdy będziemy wykorzystywać wyłącznie światła punktowe i kierunkowe. Dlatego o powyższej całce możemy myśleć jak o sumie po źródłach światła. + +By uzyskać realistyczny efekt, potrzebujemy realistycznej funkcji $f_r$. Wybierzemy *Cook-Torrance BRDF*, który jest najczęściej wykorzystywaną funkcją przy liczeniu PBR w czasie rzeczywistym. + + *Cook-Torrance BRDF* rozdzielamy na światło rozproszone (diffuse) i odbite kierunkowo (specular) + +$$f_r=k_d f_{lambert}+k_s f_{cook−torrance},$$ +gdzie $k_d$ i $k_s$ to są współczynniki, rozdzielające ile światła zostało rozproszone a ile odbite, ich suma musi być równa 1. komponent $f_{lambert}$ jest znany jako *Lambertian diffuse* i wynosi +$$f_{lambert}=\frac{c}{\pi}$$ +przy czym $c$ to jest jest kolorem powierzchni a $\pi$ odpowiada za normalizację. + +Część odbicia kierunkowego jest bardziej skomplikowana i wygląda następująco: + +$$f_{cook−torrance}=\frac{DFG}{4(w_o\cdot n)(w_i\cdot n)}$$ +gdzie $D$, $F$ i $G$ są funkcjami, które zależą od normalnej, wektora wejściowego i wyjściowego oraz parametrów chropowatości $\alpha$ i odbicia $F_0$. Te funkcje to odpowiednio: +- $D$ - **Normal distribution function** przybliża ilość powierzchni, która jest ustawiona prostopadle do wektora połówkowego korzystamy z funkcji Trowbridge-Reitz GGX $$D(n,h,\alpha) = \frac{\alpha^2}{π((n⋅h)^2(α^2−1)+1)^2},$$ +gdzie $h$ to: $$h = \frac{\omega_o+\omega_i}{||\omega_o+\omega_i||}$$ +- G - **Geometry function** opisuje stopień samo-zacieninienia. Wykorzystujemy Schlick-GGX +$$G(n,\omega_o,k)=\frac{n⋅\omega_o}{(n⋅\omega_o)(1−k)+k}$$ +przy czym $k$ wynosi +$$k=\frac{(\alpha+1)^2}{8}$$ +- F - **Fresnel equation** opisuje stopień odbicia w zależności od kąta padania. Wykorzystujemy aproksymację Fresnela-Schlicka +$$F(h,v,F_0)=F_0+(1−F_0)(1−(h⋅v))^5$$ +Parametr $F_0$ jest własnością materiału dla uproszczenia uznaje się, że niemetale mają wartość (0.04,0.4,0.4) a dla metali jest ona równa kolorowi obiektu. Wprowadza się **parametr metaliczności**, który jest współczynnikiem, jakim miksujemy między (0.04,0.4,0.4) a kolorem, żeby uzyskać $F_0$. + +### Zadanie +W projekcie znajduje się jedna kula, punktowe oświetlenie i cieniowanie Phonga. Zaimplementuj PBR i rozmieść kule w macierzy 10 x 10 zmieniając stopniowo w nich parametr chropowatości $\alpha$ i metaliczności, jak na poniższym obrazku. +![](./img/lighting_result.png) + +## Teksturowanie +Do uzyskania realistycznego efektu wykorzystuje się szereg tekstur: + +![](./img/textures.png) +Tekstury **Albedo** i **Normalnych** odpowiadają za kolor i normalne jak przy cieniowaniu Phonga. **Metalic** i **Roughness** przechowuje wartość metaliczności i chropowatości, które pojawiły się w BRDF. Natomiast **AO** (ambient occlision) odpowiada za stopień samo-zacienienia, który modyfikuje oświetlenie ambientowe w danym punkcie, uwzględnienie jej podkreśla detale obiektów. + +Karty graficzne posiadają ograniczenia, jeżeli chodzi o ilość tekstur, które można przesłać za jednym razem (współcześnie są to 32 tekstury). Może być to znaczące ograniczenie, dlatego **AO** **Roughness** i **Metalic** mogą być przesłane w jednej teksturze kolejno jako kanał r, g i b. Taką teksturę określa się jako arm od pierwszych liter nazw. + +### Zadanie +W `ex_8_2.hpp` znajduje się podstawowa scena. Zaimplementuj w niej PBR z użyciem tekstur. +Stwórz nową parę shaderów, które będą obsługiwać PBR![](./img/textures.png)W oparciu o tekstury. Wyświetl statek zestawem tekstur w folderze `textures/spaceshipPBR`. Możesz też wziąć swój układ słoneczny z poprzednich zajęć. +Ładowanie tekstur jest męczące, napisz klasę/strukturę `PBRTexturesHandler`, która przechowuje zestaw tekstur *albedo*, *normal* i *arm*. Posiada konstruktor, który ładuje tekstury po ścieżkach oraz funkcję `activateTextures(int[] indices)`, która aktywuje tekstury z indeksami podanymi w tablicy. `int[] indices`. + +## PBR Multitexturing +Podobnie jak w przypadku zwykłych tekstur możemy mieszać tekstury przesyłane przez PBR, należy wtedy mieszać wszystkie tekstury z takim samym współczynnikiem. Jednak stopniowe przejście z jednej tekstury do drugiej często prowadzi to do nienaturalnych rezultatów, gdy próbujemy mieszać tekstury o różnej częstotliwości detali, jak na przykład poniżej: +![](./img/blending1.webp) +W prawdziwym świecie, przy stopniowej zmianie z kamiennego podłoża na piaszczyste, wiedzielibyśmy, jak piasek stopniowo zaczyna wypełniać szpary między kamieniami i przykrywać je coraz bardziej. +Istnieje wiele metod mieszania tekstur np [ta](https://onlinelibrary.wiley.com/doi/10.1002/cav.1460). My wykorzystamy technikę opartą na mapach wysokości, opisaną w +[https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting](https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting). + +> Mapa wysokości to dodatkowa tekstura, która wskazuje wysokość obiektów znajdujących się na teksturze względem płaskiej powierzchni. Może być również wykorzystana przy paralax mappingu czy tessalacji. Jest ona nazywana po angielsku *heightmap*, *displacement map* czy *bump map* w zależności od zastosowania. Przykładowe znajdują się w teksutrach. + +Możemy te mapy wykorzystać do mieszania dwóch map tak, że wyświetlana będzie ta, która jest wyżej. Zaprezentować to możemy w poniższym jednowymiarowym schemacie. Niebieska linia reprezentuje mapę piasku a czerwona kamieni. +![](./img/2.png) +Jako rezultat otrymujemy +![](./img/3.webp) + +Kod funkcji mieszającej: +```C++ +float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio) +{ + if (height1>height2){ + return texture1; + } + else{ + return texture2; + } +} +``` + +To pozwala nam zmieszać dwie tekstury. Jednak samo w sobie nie powoduję przejścia z jednej do drugiej, to możemy osiągnąć modyfikując wysokość parametrem `blend_ratio`, który będzie z zakresu od 0 do 1. Na jednowymiarowym schemacie wartość wysokości wygląda to następująco. +![](./img/4.png) +Daje nam to to poniższy efekt +![](./img/5.webp) +Kod nowej funkcji mieszającej: +```C++ +float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio) +{ + float h1=height1+1.0-blend_ratio; + float h2=height2+blend_ratio; + if (h1>h2){ + return texture1; + } + else{ + return texture2; + } +} +``` +To podejście dało nam bardziej naturalne przejście z piasku do kamieni. Widać jak kamienie stopniowo wyłaniają się spod piasku. Jednak pojawiły się artefakty w miejscach, gdzie wartości mapy wysokości są bardzo zbliżone. Wynika to z ograniczeń kwantyzacji. By zminimalizować te błędy, będziemy mieszać wartości tekstur, ale tylko, gdy wartości będą blisko siebie. +![](./img/6.webp) +Zdefiniujemy dodatkowy parametr `mix_threshold`, który będzie określał, jak blisko mają być wartości, żeby je mieszać. Jeżeli różnica między jedną wysokością a drugą będzie mniejsza od `mix_threshold` to będziemy je mieszać proporcjonalnie do ich różnicy podzielonej przez `mix_threshold`, w przeciwnym wypadku wybierzemy tą, która jest wyżej. W celu optymalizacji zamiast optymalizacji korzystamy z funkcji clamp + +```C++ +float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio) +{ + float mix_threshold=0.1; + float h1=height1+1.0-blend_ratio; + float h2=height2+blend_ratio; + float havg=(h1+h2)/2.; + + h1 = clamp((h1-hav+0.5*mix_threshold)/(mix_threshold),0.,1.); + h2 = clamp((h2-hav+0.5*mix_threshold)/(mix_threshold),0.,1.); + + return (texture1*h1+texture2*h2) +} +``` + +Stosunek w jakim je zmieszamy: +![](./img/7.webp) + +Finalna tekstura +![](./img/8.webp) + +### Zadanie +W teksturach znajduje plik `heightmap.png` wykorzystaj go jako mapę wysokości przy rysowaniu jednej z planet. Mapa ta zawiera wartości, od 0 do 1. Dla różnych wysokości chcemy wykorzystywać różne tekstury, ustal na przykład, że poniżej 0,3 znajduje się woda, między 0,3 a 0,6 trawa, natomiast powyżej skały. + +### Zadanie* +Zmodyfikuj swój układ słoneczny z poprzednich zajęć, zmień w nim model oświetlenia z Phonga na PBR. Tekstury możesz pobrać na przykład z [https://polyhaven.com/textures](https://polyhaven.com/textures) +lub [www.texturecan.com](www.texturecan.com). \ No newline at end of file diff --git a/grk/cw 6/grk-cw6.vcxproj b/grk/cw 6/grk-cw6.vcxproj index 98418bb..98c4fe8 100644 --- a/grk/cw 6/grk-cw6.vcxproj +++ b/grk/cw 6/grk-cw6.vcxproj @@ -36,8 +36,8 @@ - - + + diff --git a/grk/cw 6/grk-cw6.vcxproj.filters b/grk/cw 6/grk-cw6.vcxproj.filters index 126ff2f..7131fb3 100644 --- a/grk/cw 6/grk-cw6.vcxproj.filters +++ b/grk/cw 6/grk-cw6.vcxproj.filters @@ -94,12 +94,6 @@ Shader Files - - Shader Files - - - Shader Files - Shader Files @@ -124,5 +118,11 @@ Shader Files + + Shader Files + + + Shader Files + \ No newline at end of file diff --git a/grk/cw 6/shaders/shader_pbr.frag b/grk/cw 6/shaders/shader_pbr.frag new file mode 100644 index 0000000..01d6ff1 --- /dev/null +++ b/grk/cw 6/shaders/shader_pbr.frag @@ -0,0 +1,132 @@ +#version 430 core + +float AMBIENT = 0.25f; +float PI = 3.14159f; + +uniform sampler2D depthMap; + +uniform vec3 cameraPos; + +uniform sampler2D colorTexture; + +uniform vec3 sunDir; +uniform vec3 sunColor; + +uniform vec3 lightPos; +uniform vec3 lightColor; + +uniform float metallic; +uniform float roughness; + +uniform float exposition; + +in vec3 vecNormal; +in vec3 worldPos; +in vec2 vtc; + +out vec4 outColor; + +in vec3 viewDirTS; +in vec3 lightDirTS; +in vec3 sunDirTS; + +float DistributionGGX(vec3 normal, vec3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(normal, H), 0.0); + float NdotH2 = NdotH * NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +float GeometrySmith(vec3 normal, vec3 V, vec3 lightDir, float roughness) +{ + float NdotV = max(dot(normal, V), 0.0); + float NdotL = max(dot(normal, lightDir), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +vec3 PBRLight(vec3 lightDir, vec3 radiance, vec3 normal, vec3 V, vec3 color) +{ + float diffuse = max(0, dot(normal, lightDir)); + + vec3 F0 = vec3(0.04); + F0 = mix(F0, color, metallic); + + vec3 H = normalize(V + lightDir); + + // cook-torrance brdf + float NDF = DistributionGGX(normal, H, roughness); + float G = GeometrySmith(normal, V, lightDir, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - metallic; + + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(normal, V), 0.0) * max(dot(normal, lightDir), 0.0) + 0.0001; + vec3 specular = numerator / denominator; + + // add to outgoing radiance Lo + float NdotL = max(dot(normal, lightDir), 0.0); + return (kD * color / PI + specular) * radiance * NdotL; +} + +vec3 toneMapping(vec3 color) +{ + float exposure = 0.03; + vec3 mapped = 1 - exp(-color * exposure); + return mapped; +} + +void main() +{ + vec3 normal = normalize(vecNormal); + + vec3 viewDir = normalize(cameraPos - worldPos); + + vec3 lightDir = normalize(lightPos - worldPos); + + vec4 textureColor = texture2D(colorTexture, vtc); + + float diffuse = max(0, dot(normal, lightDir)); + vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 300; + vec3 toneMappedColor = toneMapping(vec3(textureColor) * min(1, AMBIENT + diffuse) * distance); + //gamma correction + //toneMappedColor = pow(toneMappedColor, vec3(1.0/2.2)); + + vec3 ambient = AMBIENT * toneMappedColor; + vec3 attenuatedLightColor = lightColor / pow(length(lightPos - worldPos), 2); + vec3 illumination; + illumination = ambient + PBRLight(lightDir, attenuatedLightColor, normal, viewDir, toneMappedColor); + + //sun + illumination = illumination + PBRLight(sunDir, sunColor, normal, viewDir, toneMappedColor); + + outColor = vec4(vec3(1.0) - exp(-illumination * exposition), 1); +} diff --git a/grk/cw 6/shaders/shader_pbr.vert b/grk/cw 6/shaders/shader_pbr.vert new file mode 100644 index 0000000..ae1608d --- /dev/null +++ b/grk/cw 6/shaders/shader_pbr.vert @@ -0,0 +1,41 @@ +#version 430 core + +layout(location = 0) in vec3 vertexPosition; +layout(location = 1) in vec3 vertexNormal; +layout(location = 2) in vec2 vertexTexCoord; +layout(location = 3) in vec3 vertexTangent; +layout(location = 4) in vec3 vertexBitangent; + +uniform mat4 transformation; +uniform mat4 modelMatrix; + +out vec3 vecNormal; +out vec3 worldPos; +out vec2 vtc; + +uniform vec3 lightPos; +uniform vec3 cameraPos; +uniform vec3 sunDir; + +out vec3 viewDirTS; +out vec3 lightDirTS; +out vec3 sunDirTS; + +void main() +{ + worldPos = (modelMatrix * vec4(vertexPosition, 1)).xyz; + vecNormal = (modelMatrix * vec4(vertexNormal, 0)).xyz; + gl_Position = transformation * vec4(vertexPosition, 1.0); + + vtc = vec2(vertexTexCoord.x, 1.0 - vertexTexCoord.y); + + vec3 w_tangent = normalize(mat3(modelMatrix) * vertexTangent); + vec3 w_bitangent = normalize(mat3(modelMatrix) * vertexBitangent); + mat3 TBN = transpose(mat3(w_tangent, w_bitangent, vecNormal)); + + vec3 V = normalize(cameraPos - worldPos); + viewDirTS = TBN * V; + vec3 L = normalize(lightPos - worldPos); + lightDirTS = TBN * L; + sunDirTS = TBN * sunDir; +} diff --git a/grk/cw 6/shaders/shader_skybox.frag b/grk/cw 6/shaders/shader_skybox.frag index 0221dbd..96ba1a7 100644 --- a/grk/cw 6/shaders/shader_skybox.frag +++ b/grk/cw 6/shaders/shader_skybox.frag @@ -10,5 +10,5 @@ out vec4 out_color; void main() { vec4 textureColor = texture(skybox, texCoord); - out_color = vec4(vec3(textureColor) * lightColor * 0.03f, 1.0f); + out_color = vec4(vec3(textureColor) * lightColor * 0.3f, 1.0f); } \ No newline at end of file diff --git a/grk/cw 6/shaders/shader_sun.frag b/grk/cw 6/shaders/shader_sun.frag index cb8fd23..3b63a78 100644 --- a/grk/cw 6/shaders/shader_sun.frag +++ b/grk/cw 6/shaders/shader_sun.frag @@ -15,9 +15,6 @@ uniform sampler2D colorTexture; void main() { - vec3 lightDir = normalize(lightPos - worldPos); - vec3 normal = normalize(vecNormal); - vec4 textureColor = texture2D(colorTexture, vtc); - outColor = vec4(vec3(textureColor) * lightColor * 0.015f, 1.0); + outColor = vec4(vec3(textureColor) * lightColor * 0.15f, 1.0); } \ No newline at end of file diff --git a/grk/cw 6/shaders/shader_sun.vert b/grk/cw 6/shaders/shader_sun.vert index 0eca456..b9539ff 100644 --- a/grk/cw 6/shaders/shader_sun.vert +++ b/grk/cw 6/shaders/shader_sun.vert @@ -13,9 +13,6 @@ out vec2 vtc; void main() { - worldPos = (modelMatrix * vec4(vertexPosition, 1)).xyz; - vecNormal = (modelMatrix * vec4(vertexNormal, 1)).xyz; gl_Position = transformation * vec4(vertexPosition, 1.0); - vtc = vec2(vertexTexCoord.x, 1.0 - vertexTexCoord.y); } diff --git a/grk/cw 6/shaders/shader_tex.frag b/grk/cw 6/shaders/shader_tex.frag index 0b9bcca..f20795a 100644 --- a/grk/cw 6/shaders/shader_tex.frag +++ b/grk/cw 6/shaders/shader_tex.frag @@ -15,9 +15,10 @@ out vec4 outColor; vec3 outputColor; uniform sampler2D colorTexture; -vec3 toneMapping(vec3 color) { +vec3 toneMapping(vec3 color) +{ float exposure = 0.06; - vec3 mapped = 1 - exp(-color*exposure); + vec3 mapped = 1 - exp(-color * exposure); return mapped; } @@ -27,6 +28,7 @@ void main() vec3 normal = normalize(vecNormal); float diffuse = max(0, dot(normal, lightDir)); textureColor = texture2D(colorTexture, vtc); + vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 200; outputColor = vec3(textureColor) * min(1, AMBIENT + diffuse) * distance; diff --git a/grk/cw 6/src/Planet.hpp b/grk/cw 6/src/Planet.hpp index 6d512b4..c7b95c5 100644 --- a/grk/cw 6/src/Planet.hpp +++ b/grk/cw 6/src/Planet.hpp @@ -57,9 +57,10 @@ namespace textureMaterials { GLuint clouds; } */ -GLuint programSun; GLuint programDepth; GLuint programTex; +GLuint programPbr; +GLuint programSun; GLuint programSkyBox; Core::Shader_Loader shaderLoader; @@ -78,12 +79,15 @@ GLuint planetTex; glm::vec3 planetPos = glm::vec3(0.f, 0.f, 0.f); float planetSize = 0.005f; float planetRot = 0.f; +float planetRough = 0.5f; +float planetMetal = 0.5f; const char* const sunTexPaths[] = { "./textures/suns/sol.jpg", "./textures/suns/orange.jpg", "./textures/suns/lava.png", "./textures/suns/star.png", "./textures/suns/sun.jpg" }; int sunTexIndex = 20; GLuint sunTex; glm::vec3 sunPos = glm::vec3(20.f, 0.f, 20.f); +glm::vec3 sunDir = glm::vec3(1.f, 0.f, 1.f); float sunSize = 0.05f; const char* skyBoxPaths[] = { "./textures/skybox/space_rt.png", "./textures/skybox/space_lf.png", "./textures/skybox/space_up.png", "./textures/skybox/space_dn.png", @@ -108,7 +112,7 @@ unsigned int depthMapFBO; int HDR_WIDTH = 1024; int HDR_HEIGHT = 1024; -float lightPower = 100.f; +float lightPower = 10.f; glm::vec3 lightColor = glm::vec3(lightPower, lightPower, lightPower); glm::mat4 createCameraMatrix() { @@ -189,7 +193,6 @@ void initDepthMap() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } - void drawPlanet(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture) { glUseProgram(programTex); Core::SetActiveTexture(texture, "colorTexture", programTex, 0); @@ -227,37 +230,32 @@ void drawSkyBox(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint text Core::DrawContext(context); glUseProgram(0); } -/* -void drawObjectPBR(Core::RenderContext& context, glm::mat4 modelMatrix, glm::vec3 color, float roughness, float metallic) { +void drawObjectPBR(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture, float roughness, float metallic) { + glUseProgram(programPbr); + Core::SetActiveTexture(texture, "colorTexture", programPbr, 0); glm::mat4 viewProjectionMatrix = createPerspectiveMatrix() * createCameraMatrix(); glm::mat4 transformation = viewProjectionMatrix * modelMatrix; - glUniformMatrix4fv(glGetUniformLocation(program, "transformation"), 1, GL_FALSE, (float*)&transformation); - glUniformMatrix4fv(glGetUniformLocation(program, "modelMatrix"), 1, GL_FALSE, (float*)&modelMatrix); + glUniformMatrix4fv(glGetUniformLocation(programPbr, "transformation"), 1, GL_FALSE, (float*)&transformation); + glUniformMatrix4fv(glGetUniformLocation(programPbr, "modelMatrix"), 1, GL_FALSE, (float*)&modelMatrix); - glUniform1f(glGetUniformLocation(program, "exposition"), exposition); + glUniform1f(glGetUniformLocation(programPbr, "exposition"), lightPower); - glUniform1f(glGetUniformLocation(program, "roughness"), roughness); - glUniform1f(glGetUniformLocation(program, "metallic"), metallic); + glUniform1f(glGetUniformLocation(programPbr, "roughness"), roughness); + glUniform1f(glGetUniformLocation(programPbr, "metallic"), metallic); - glUniform3f(glGetUniformLocation(program, "color"), color.x, color.y, color.z); + glUniform3f(glGetUniformLocation(programPbr, "cameraPos"), cameraPos.x, cameraPos.y, cameraPos.z); - glUniform3f(glGetUniformLocation(program, "cameraPos"), cameraPos.x, cameraPos.y, cameraPos.z); + glUniform3f(glGetUniformLocation(programPbr, "sunDir"), sunDir.x, sunDir.y, sunDir.z); + glUniform3f(glGetUniformLocation(programPbr, "sunColor"), lightColor.x, lightColor.y, lightColor.z); - glUniform3f(glGetUniformLocation(program, "sunDir"), sunDir.x, sunDir.y, sunDir.z); - glUniform3f(glGetUniformLocation(program, "sunColor"), sunColor.x, sunColor.y, sunColor.z); + glUniform3f(glGetUniformLocation(programPbr, "lightPos"), sunPos.x, sunPos.y, sunPos.z); + glUniform3f(glGetUniformLocation(programPbr, "lightColor"), lightColor.x, lightColor.y, lightColor.z); - glUniform3f(glGetUniformLocation(program, "lightPos"), pointlightPos.x, pointlightPos.y, pointlightPos.z); - glUniform3f(glGetUniformLocation(program, "lightColor"), pointlightColor.x, pointlightColor.y, pointlightColor.z); - - glUniform3f(glGetUniformLocation(program, "spotlightConeDir"), spotlightConeDir.x, spotlightConeDir.y, spotlightConeDir.z); - glUniform3f(glGetUniformLocation(program, "spotlightPos"), spotlightPos.x, spotlightPos.y, spotlightPos.z); - glUniform3f(glGetUniformLocation(program, "spotlightColor"), spotlightColor.x, spotlightColor.y, spotlightColor.z); - glUniform1f(glGetUniformLocation(program, "spotlightPhi"), spotlightPhi); Core::DrawContext(context); - + glUseProgram(0); } -*/ + void renderScene(GLFWwindow* window) { glClearColor(0.5f, 0.0f, 0.25f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -268,7 +266,8 @@ void renderScene(GLFWwindow* window) { glm::mat4 planetRotate = glm::rotate(time * planetRot, glm::vec3(0, 1, 0)); glm::mat4 planetTranslate = glm::translate(planetPos); - drawPlanet(sphereContext, planetTranslate * planetRotate * planetScale, planetTex); + //drawPlanet(sphereContext, planetTranslate * planetRotate * planetScale, planetTex); + drawObjectPBR(sphereContext, planetTranslate * planetRotate * planetScale, planetTex, planetRough, planetMetal); //rysowanie słońca glm::mat4 sunScale = glm::scale(glm::vec3(sunSize)); @@ -314,6 +313,7 @@ void init(GLFWwindow* window) { programDepth = shaderLoader.CreateProgram("shaders/shader_smap.vert", "shaders/shader_smap.frag"); programTex = shaderLoader.CreateProgram("shaders/shader_tex.vert", "shaders/shader_tex.frag"); + programPbr = shaderLoader.CreateProgram("shaders/shader_pbr.vert", "shaders/shader_pbr.frag"); programSun = shaderLoader.CreateProgram("shaders/shader_sun.vert", "shaders/shader_sun.frag"); programSkyBox = shaderLoader.CreateProgram("shaders/shader_skybox.vert", "shaders/shader_skybox.frag"); @@ -329,6 +329,7 @@ void init(GLFWwindow* window) { void shutdown(GLFWwindow* window) { shaderLoader.DeleteProgram(programDepth); shaderLoader.DeleteProgram(programTex); + shaderLoader.DeleteProgram(programPbr); shaderLoader.DeleteProgram(programSun); shaderLoader.DeleteProgram(programSkyBox); } @@ -391,15 +392,31 @@ void processInput(GLFWwindow* window) Sleep(200); } - //siła światła - float powerSpeed = 1.f; + //jasność + float powerSpeed = 0.05f; - if (glfwGetKey(window, GLFW_KEY_O) == GLFW_PRESS && lightPower < 300.f) + if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS && lightPower < 25.f) lightPower += powerSpeed; - if (glfwGetKey(window, GLFW_KEY_P) == GLFW_PRESS && lightPower > 10.f) + if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS && lightPower > 2.5f) lightPower -= powerSpeed; lightColor = glm::vec3(lightPower, lightPower, lightPower); + + //chropowatość + float roughSpeed = 0.002f; + + if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS && planetRough < 1.f) + planetRough += roughSpeed; + if (glfwGetKey(window, GLFW_KEY_4) == GLFW_PRESS && planetRough > 0.f) + planetRough -= roughSpeed; + + //metaliczność + float metalSpeed = 0.002f; + + if (glfwGetKey(window, GLFW_KEY_5) == GLFW_PRESS && planetMetal < 1.f) + planetMetal += metalSpeed; + if (glfwGetKey(window, GLFW_KEY_6) == GLFW_PRESS && planetMetal > 0.f) + planetMetal -= metalSpeed; } // funkcja jest glowna petla diff --git a/grk/cw 6/textures/skybox/space_dn.png b/grk/cw 6/textures/skybox/space_dn.png index 4191f53..768915f 100644 Binary files a/grk/cw 6/textures/skybox/space_dn.png and b/grk/cw 6/textures/skybox/space_dn.png differ diff --git a/grk/cw 6/textures/skybox/space_up.png b/grk/cw 6/textures/skybox/space_up.png index 61626da..5abbf77 100644 Binary files a/grk/cw 6/textures/skybox/space_up.png and b/grk/cw 6/textures/skybox/space_up.png differ diff --git a/konspekt b/konspekt index 846850f..dd1fd5a 100644 --- a/konspekt +++ b/konspekt @@ -28,6 +28,6 @@ UP/DOWN - wielkość planety LEFT/RIGHT - obrót planety T/Y - tekstura planety U/I - tekstura słońca -O/P - siła światła - - +1/2 - jasność +3/4 - chropowatość +5/6 - metaliczność diff --git a/to-do.txt b/to-do.txt index a606d51..b734c70 100644 --- a/to-do.txt +++ b/to-do.txt @@ -1,15 +1,8 @@ -na 22.01.2024 +na 29.01.2024 https://andkok.faculty.wmi.amu.edu.pl/grk/ -Oliwia: -- znaleźć tekstury (done) -- perlin noise - poczytać i spróbować coś zrobić - -Kacper: -- skybox -- przyciski funkcyjne (done) - -Natalia: -- Bloom -- jak zostanie skończone, to perlin noise \ No newline at end of file +Do zrobienia: +- pbr (done) +- bloom +- perlin noise \ No newline at end of file