grk464933/cw 8/Zadania 8.md
2024-02-02 11:19:35 +01:00

125 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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_{cooktorrance},$$
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_{cooktorrance}=\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(α^21)+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)(1k)+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+(1F_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).