Compare commits
No commits in common. "master" and "bloom-new" have entirely different histories.
2
.gitignore
vendored
@ -5,4 +5,4 @@
|
||||
/.vs
|
||||
/grk/.vs
|
||||
/grk/Debug
|
||||
/grk/planet-editor/Debug
|
||||
/grk/cw 6/Debug
|
0
.vs/CMake Overview
Normal file
3
.vs/ProjectSettings.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"CurrentProjectSetting": "x64-Debug (domyślnie)"
|
||||
}
|
7
.vs/VSWorkspaceState.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"SelectedNode": "\\grk-cw.sln",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
BIN
.vs/dgrk-2023-planeta/v16/.suo
Normal file
BIN
.vs/slnx.sqlite
Normal file
Before ![]() (image error) Size: 2.2 MiB |
Before ![]() (image error) Size: 2.7 MiB |
Before ![]() (image error) Size: 1.8 MiB |
Before ![]() (image error) Size: 1.3 MiB |
144
grk/cw 6/Zadania 2.html
Normal file
@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<title>Zadania 2</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
ul.task-list{list-style: none;}
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="ładowanie-vao-i-vbo">Ładowanie VAO i VBO</h2>
|
||||
<p>W trakcie tych zajęć przećwiczymy ładowanie danych do pamięci. Tym razem, zamiast korzystać z gotowej funkcji utworzymy je samodzielnie. W openglu wykorzystujemy do tego VBO (Vertex Buffer Object) i VAO (Vertex Array Object). Pierwsza jest buforem, który zawiera dane modeli. Natomiast drugi zawiera informacje jak dane bufory interpretować.</p>
|
||||
<p>W zadaniu <code>2_1</code> będziemy przesyłać prostopadłościan. Tablica zawierająca jego definicję jest w pliku <code>Box.cpp</code>. Każdy wierzchołek składa się z ośmiu floatów, pierwsze cztery określają jego pozycję, a cztery kolejny określają jego kolor.</p>
|
||||
<p>Inicjalizacje będziemy wykonywać wewnątrz funkcji <code>init</code>. Pierwszym krokiem jest wygenerowanie jednego VAO i jednego VBO. Wykorzystuje się do tego odpowiednio funkcje <code>glGenVertexArrays</code> i <code>glGenBuffers</code>. Pierwszym argumentem jest liczba buforów czy array object, które tworzymy, drugim jest adres, w którym ma być bufor/array object umieszczony. W naszym przypadku pierwszym argumentem będzie 1, natomiast drugim będzie wskaźnik na zmienną <code>VAO</code> i <code>VBO</code> odpowiednio. Następnie należy aktywować <code>VAO</code> za pomocą funkcji <code>glBindVertexArray</code>, po czym podpiąć do niego bufor <code>VBO</code> za pomocą <code>glBindBuffer(GLenum target, GLuint buffer)</code> nasz target to <code>GL_ARRAY_BUFFER</code>, czyli bufor, który oznacza atrybuty wierzchołków.</p>
|
||||
<p>Kolejnym krokiem jest umieszczenie danych w buforze za pomocą funkcji <code>glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage)</code>. Pierwszym argumentem jest ponownie <code>GL_ARRAY_BUFFER</code>, drugi to rozmiar tablicy w bajtach, trzecim adres tablicy, a czwartym sposób używania tablicy, w naszym przypadku <code>GL_STATIC_DRAW</code>.</p>
|
||||
<p>Pozostaje opisanie atrybutów wierzchołków, musimy opisać, gdzie się znajdują, jaką mają strukturę i jak ma się do nich odnieść shader. My mamy 2 atrybuty, jest to pozycja i kolor. Pierwszym krokiem jest aktywacja atrybutów za pomocą <code>glEnableVertexAttribArray(GLuint index)</code>, przy czym po indeksie będą one odnajdywane przez shader. W naszym przypadku będą to odpowiednio 0 i 1. Następnie należy opisać jak GPU ma odczytywać atrybuty z bufora za pomocą funkcji <code>glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * offset)</code>. Jej argumenty to kolejno: * <code>index</code> - indeksy odpowiadające atrybutowi, * <code>size</code> - liczba elementów w atrybucie wierzchołka, może wynosić 1, 2, 3 lub 4, * <code>type</code> - typ danych jako enum, w naszym przypadku <code>GL_FLOAT</code>, * <code>normalized</code> - określa czy wartość ma być znormalizowana, u nas będzie to <code>GL_FALSE</code>, * <code>stride</code> - określa dystans pomiędzy atrybutami w kolejnych wierzchołkach * <code>offset</code> - wskaźnik na pierwszy atrybut w tablicy, licząc względem początku tablicy i typu (void *)</p>
|
||||
<p>Struktura naszego prostopadłościanu ma na przemian pozycje i kolory, dlatego w obu przypadkach stride będzie wynosił ośmiokrotność rozmiaru <em>floata</em>. Natomiast <em>offest</em> będzie wynosił zero i czterokrotność rozmiaru <em>floata</em>.</p>
|
||||
<p><img src="./img/stride_offest.jpg" /></p>
|
||||
<p>Na koniec uwolnij <code>VAO</code> za pomocą instrukcji: <code>glBindVertexArray(0);</code>.</p>
|
||||
<p>Pozostaje narysować prostopadłościan. W funkcji <code>renderScene</code> wywołaj <code>glBindVertexArray</code> z argumentem <code>VAO</code>. Następnie narysuj za pomocą <code>glDrawArrays(GLenum mode, GLint first, GLsizei count)</code>. Pierwszym argumentem jest typ rysowanego obiektu, w naszym przypadku jest to <code>GL_TRIANGLES</code>, indeks pierwszego wierzchołka, czyli 0 i liczba wierzchołków czyli 36.</p>
|
||||
<h3 id="zadanie">Zadanie</h3>
|
||||
<p>podążając za powyższymi instrukcjami zainicjalizuj box, następnie obróć go za pomocą funkcji <code>glm::eulerAngleXYZ</code> tak, żeby było widać trzy ściany prostopadłościany. Dodaj również obrót wokół osi Y w czasie z użyciem funkcji <code>glm::eulerAngleXYZ</code>.</p>
|
||||
<h3 id="zadanie-1">Zadanie*</h3>
|
||||
<p>Wykonaj zadania z <code>ex_2_1b.hpp</code>.</p>
|
||||
<h2 id="shadery">Shadery</h2>
|
||||
<p>Shadery są programami uruchamianymi na karcie graficznej. W openglu wykorzystujemy język GLSL, który jest bardzo podobny do C++, posiada on liczne słowa kluczowe i funkcje matematyczne. Istnieją różne rodzaje shaderów, w tej sekcji skupimy się na dwóch z nich: shader wierzchołków i shader fragmentów. Pierwszy rodzaj wykonują operacje na wierzchołkach, przykładowo w tym zadaniu odpowiada za przemnożenie macierzy obrotu przez wierzchołki. Natomiast drugi określa kolor konkretnego fragmentu/piksela. Shadery są łączone w <em>pipeline</em>, to znaczy wykonuje się je sekwencyjnie, dane z poprzedniego są wysyłane do następnego. W zadaniu <code>2_1</code> wykorzystujemy następujące shadery</p>
|
||||
<p>shader wierzchołków</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode glsl"><code class="sourceCode glsl"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#version 430 core</span></span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">layout</span>(<span class="dt">location</span> = <span class="dv">0</span>) <span class="dt">in</span> <span class="dt">vec4</span> vertexPosition;</span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">layout</span>(<span class="dt">location</span> = <span class="dv">1</span>) <span class="dt">in</span> <span class="dt">vec4</span> vertexColor;</span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">uniform</span> <span class="dt">mat4</span> transformation;</span>
|
||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> <span class="fu">main</span>()</span>
|
||||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a> <span class="bu">gl_Position</span> = transformation * vertexPosition;</span>
|
||||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>shader fragmentów</p>
|
||||
<div class="sourceCode" id="cb2"><pre class="sourceCode glsl"><code class="sourceCode glsl"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#version 430 core</span></span>
|
||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="dt">out</span> <span class="dt">vec4</span> out_color;</span>
|
||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> <span class="fu">main</span>()</span>
|
||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> out_color = <span class="dt">vec4</span>(<span class="fl">0.8</span>,<span class="fl">0.2</span>,<span class="fl">0.9</span>,<span class="fl">1.0</span>);</span>
|
||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>Shader wierzchołków odbiera 2 typy danych. Pierwszym są dane z bufora w liniach.</p>
|
||||
<pre><code>layout(location = 0) in vec4 vertexPosition;
|
||||
layout(location = 1) in vec4 vertexColor;</code></pre>
|
||||
<p>są one różne dla każdego wierzchołka. Zmienną, która ma odebrać te dane deklaruje się globalnie funkcją <code>main</code> i poprzedza się słowem kluczowym <code>in</code>. Prefiks <code>layout(location = ..)</code> jest opcjonalny i służy określeniu indeksu atrybutu, jest to ta sama wartość, którą ustawiliśmy w <code>glVertexAttribPointer</code>. Można je usunąć, wtedy o indeksie będzie decydować kolejność. Drugim typem jest <code>uniform</code>, w przeciwieństwie do danych z bufora, są one takie same dla każdego wierzchołka. W tym przypadku przesyłamy za jej pomocą macierz obrotu.</p>
|
||||
<p>Shader również wysyła dane. Domyślnie musi wysłać wyjściową pozycję wierzchołka, robi to przez zapisanie w <code>gl_Position</code> wektora 4-wymiarowego w funkcji <code>main</code>. Poza tym może również przesłać inne informacje. Wykonuje się to przez deklaracje zmiennej globalnej, którą poprzedza się słowem kluczowym <code>out</code>. Następnie należy ją wypełnić. W tym przypadku przesyłamy kolor wierzchołka.</p>
|
||||
<p>Shader fragmentów odbiera kolor z Shadera wierzchołków. Podobnie jak z obieraniem danych z buforu robimy to za pomocą słowa kluczowego <code>in</code>, w przypadku przesyłania zmiennej z jednego shadera do drugiego nazwy zmiennych muszą być <strong>takie same</strong> przy słowie kluczowym <code>out</code> i <code>in</code>. Zmiennej obranej nie można modyfikować.</p>
|
||||
<p>W najnowszej wersji opengla fragment shader nie ma domyślnego wyjścia na kolor, musimy sami je z definiować. Robimy to instrukcją <code>out vec4 out_color</code> następnie w funkcji <code>main</code> przypisujemy mu jakąś wartość.</p>
|
||||
<h3 id="zadanie-2">Zadanie</h3>
|
||||
<p>W tej chwili nasz prostopadłościan jest jednolitego koloru i nie możemy rozróżnić jego ścian. Przesłana przez nas wcześniej informacja o kolorze nie została wykorzystana. Bazując na powyższych informacjach prześlij wartość koloru zapisaną w <code>vertexColor</code> do shadera fragmentu i przypisz ją do wyjściowego koloru. Dodaj zmienną <code>in vec4 color</code> w shaderze fragmentów, następnie w funkcji <code>main</code> przypisz do niej wartość koloru. W shaderze fragmentów odbierz ją za pomocą <code>out</code> i przypisz do wyjściowego koloru.</p>
|
||||
<p>Zauważ, że kolor ścian nie jest jednolity, zamiast tego przechodzą gradientem od jednego koloru do drugiego. Dzieje się tak, ponieważ na etapie rasteryzacji kolor jest interpolowany. To znaczy: wartość jest uśredniana pomiędzy wierzchołkami trójkąta.</p>
|
||||
<h3 id="zadanie-3">Zadanie</h3>
|
||||
<p>Sprawdź, jak będzie wyglądać prostopadłościan z wyłączoną interpolacją. Dodaj przed <code>in</code> i <code>out</code> <code>color</code> słowo kluczowe <code>flat</code>.</p>
|
||||
<h3 id="zadanie-4">Zadanie</h3>
|
||||
<p>Prześlij czas od startu aplikacji do shadera fragmentów. Użyj funkcji <code>glfwGetTime</code>, by uzyskać czas. Utwórz zmienną <code>uniform</code> typu <code>float</code> we shaderze fragmentów. Następnie prześlij do niej wynik funkcji <code>glfwGetTime</code>. Aby przesłać czas do shadera, wykorzystaj funkcje <code>glUniform1f</code>. Pierwszym argumentem jest lokacja uniforma, drugim przypisywana wartość. Lokację uzyskamy za pomocą funckji <code>glGetUniformLocation(progam,"time")</code> pierwszym argumentem jest program, którego używamy a drugim nazwa zmiennej uniform. Podziel kolor przez czas, by uzyskać efekt, w którym prostopadłościan robi się czarny.</p>
|
||||
<h3 id="zadanie-5">Zadanie*</h3>
|
||||
<p>Wykorzystaj przesłany czas, żeby sprawić, żeby prostopadłościan znikał przez mieszanie go z kolorem tła. Wykorzystaj do tego następujące funkcje GLSL: <code>mix</code>, <code>sin</code>, <code>vec4</code>. Opis ich działania możesz znaleźć w dokumentacji https://docs.gl/.</p>
|
||||
<h3 id="zadanie-6">Zadanie</h3>
|
||||
<p>Prześlij pozycję lokalną i globalną pozycję wierzchołków do shadera fragmentów i wyświetl ją.</p>
|
||||
<p>W shaderze wierzchołków obok deklaracji <code>out vec4 color;</code> dodaj analogiczne o nazwie <code>pos_local</code> i <code>pos_global</code>. Do <code>pos_local</code> przypisz <code>vertexPosition</code>, a do <code>pos_global</code> przypisz <code>transformation * vertexPosition</code>. Podobnie dopisz odebranie ich w shaderze fragmentów. Użyj <code>pos_local</code>, następnie <code>pos_global</code> jako zamiast koloru. Dlaczego otrzymaliśmy taki efekt?</p>
|
||||
<h3 id="zadanie-7">Zadanie*</h3>
|
||||
<p>Użyj jednej ze zmiennych z poprzedniego zadania do zrobienia pasków na przynajmniej jednej ze ścian sześcianu. Wykorzystaj czas, żeby paski się przesuwały.</p>
|
||||
</body>
|
||||
</html>
|
105
grk/cw 6/Zadania 2.md
Normal file
@ -0,0 +1,105 @@
|
||||
## Ładowanie VAO i VBO
|
||||
|
||||
W trakcie tych zajęć przećwiczymy ładowanie danych do pamięci. Tym razem, zamiast korzystać z gotowej funkcji utworzymy je samodzielnie. W openglu wykorzystujemy do tego VBO (Vertex Buffer Object) i VAO (Vertex Array Object). Pierwsza jest buforem, który zawiera dane modeli. Natomiast drugi zawiera informacje jak dane bufory interpretować.
|
||||
|
||||
W zadaniu `2_1` będziemy przesyłać prostopadłościan. Tablica zawierająca jego definicję jest w pliku `Box.cpp`. Każdy wierzchołek składa się z ośmiu floatów, pierwsze cztery określają jego pozycję, a cztery kolejny określają jego kolor.
|
||||
|
||||
Inicjalizacje będziemy wykonywać wewnątrz funkcji `init`. Pierwszym krokiem jest wygenerowanie jednego VAO i jednego VBO. Wykorzystuje się do tego odpowiednio funkcje `glGenVertexArrays` i `glGenBuffers`. Pierwszym argumentem jest liczba buforów czy array object, które tworzymy, drugim jest adres, w którym ma być bufor/array object umieszczony.
|
||||
W naszym przypadku pierwszym argumentem będzie 1, natomiast drugim będzie wskaźnik na zmienną `VAO` i `VBO` odpowiednio. Następnie należy aktywować `VAO` za pomocą funkcji `glBindVertexArray`, po czym podpiąć do niego bufor `VBO` za pomocą `glBindBuffer(GLenum target, GLuint buffer)` nasz target to `GL_ARRAY_BUFFER`, czyli bufor, który oznacza atrybuty wierzchołków.
|
||||
|
||||
Kolejnym krokiem jest umieszczenie danych w buforze za pomocą funkcji `glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage)`. Pierwszym argumentem jest ponownie `GL_ARRAY_BUFFER`, drugi to rozmiar tablicy w bajtach, trzecim adres tablicy, a czwartym sposób używania tablicy, w naszym przypadku `GL_STATIC_DRAW`.
|
||||
|
||||
Pozostaje opisanie atrybutów wierzchołków, musimy opisać, gdzie się znajdują, jaką mają strukturę i jak ma się do nich odnieść shader. My mamy 2 atrybuty, jest to pozycja i kolor. Pierwszym krokiem jest aktywacja atrybutów za pomocą `glEnableVertexAttribArray(GLuint index)`, przy czym po indeksie będą one odnajdywane przez shader. W naszym przypadku będą to odpowiednio 0 i 1. Następnie należy opisać jak GPU ma odczytywać atrybuty z bufora za pomocą funkcji `glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * offset)`. Jej argumenty to kolejno:
|
||||
* `index` - indeksy odpowiadające atrybutowi,
|
||||
* `size` - liczba elementów w atrybucie wierzchołka, może wynosić 1, 2, 3 lub 4,
|
||||
* `type` - typ danych jako enum, w naszym przypadku `GL_FLOAT`,
|
||||
* `normalized` - określa czy wartość ma być znormalizowana, u nas będzie to `GL_FALSE`,
|
||||
* `stride` - określa dystans pomiędzy atrybutami w kolejnych wierzchołkach
|
||||
* `offset` - wskaźnik na pierwszy atrybut w tablicy, licząc względem początku tablicy i typu (void \*)
|
||||
|
||||
Struktura naszego prostopadłościanu ma na przemian pozycje i kolory, dlatego w obu przypadkach stride będzie wynosił ośmiokrotność rozmiaru *floata*. Natomiast *offest* będzie wynosił zero i czterokrotność rozmiaru *floata*.
|
||||
|
||||

|
||||
|
||||
Na koniec uwolnij `VAO` za pomocą instrukcji: `glBindVertexArray(0);`.
|
||||
|
||||
Pozostaje narysować prostopadłościan. W funkcji `renderScene` wywołaj `glBindVertexArray` z argumentem `VAO`. Następnie narysuj za pomocą `glDrawArrays(GLenum mode, GLint first, GLsizei count)`. Pierwszym argumentem jest typ rysowanego obiektu, w naszym przypadku jest to `GL_TRIANGLES`, indeks pierwszego wierzchołka, czyli 0 i liczba wierzchołków czyli 36.
|
||||
|
||||
### Zadanie
|
||||
podążając za powyższymi instrukcjami zainicjalizuj box, następnie obróć go za pomocą funkcji `glm::eulerAngleXYZ` tak, żeby było widać trzy ściany prostopadłościany. Dodaj również obrót wokół osi Y w czasie z użyciem funkcji `glm::eulerAngleXYZ`.
|
||||
|
||||
### Zadanie*
|
||||
Wykonaj zadania z `ex_2_1b.hpp`.
|
||||
|
||||
|
||||
## Shadery
|
||||
|
||||
Shadery są programami uruchamianymi na karcie graficznej. W openglu wykorzystujemy język GLSL, który jest bardzo podobny do C++, posiada on liczne słowa kluczowe i funkcje matematyczne. Istnieją różne rodzaje shaderów, w tej sekcji skupimy się na dwóch z nich: shader wierzchołków i shader fragmentów. Pierwszy rodzaj wykonują operacje na wierzchołkach, przykładowo w tym zadaniu odpowiada za przemnożenie macierzy obrotu przez wierzchołki. Natomiast drugi określa kolor konkretnego fragmentu/piksela. Shadery są łączone w *pipeline*, to znaczy wykonuje się je sekwencyjnie, dane z poprzedniego są wysyłane do następnego.
|
||||
W zadaniu `2_1` wykorzystujemy następujące shadery
|
||||
|
||||
shader wierzchołków
|
||||
```GLSL
|
||||
#version 430 core
|
||||
|
||||
layout(location = 0) in vec4 vertexPosition;
|
||||
layout(location = 1) in vec4 vertexColor;
|
||||
|
||||
uniform mat4 transformation;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = transformation * vertexPosition;
|
||||
}
|
||||
```
|
||||
|
||||
shader fragmentów
|
||||
```GLSL
|
||||
#version 430 core
|
||||
|
||||
|
||||
out vec4 out_color;
|
||||
void main()
|
||||
{
|
||||
out_color = vec4(0.8,0.2,0.9,1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Shader wierzchołków odbiera 2 typy danych. Pierwszym są dane z bufora w liniach.
|
||||
```
|
||||
layout(location = 0) in vec4 vertexPosition;
|
||||
layout(location = 1) in vec4 vertexColor;
|
||||
```
|
||||
są one różne dla każdego wierzchołka. Zmienną, która ma odebrać te dane deklaruje się globalnie funkcją `main` i poprzedza się słowem kluczowym `in`. Prefiks `layout(location = ..)` jest opcjonalny i służy określeniu indeksu atrybutu, jest to ta sama wartość, którą ustawiliśmy w `glVertexAttribPointer`. Można je usunąć, wtedy o indeksie będzie decydować kolejność.
|
||||
Drugim typem jest `uniform`, w przeciwieństwie do danych z bufora, są one takie same dla każdego wierzchołka. W tym przypadku przesyłamy za jej pomocą macierz obrotu.
|
||||
|
||||
Shader również wysyła dane. Domyślnie musi wysłać wyjściową pozycję wierzchołka, robi to przez zapisanie w `gl_Position` wektora 4-wymiarowego w funkcji `main`. Poza tym może również przesłać inne informacje. Wykonuje się to przez deklaracje zmiennej globalnej, którą poprzedza się słowem kluczowym `out`. Następnie należy ją wypełnić. W tym przypadku przesyłamy kolor wierzchołka.
|
||||
|
||||
Shader fragmentów odbiera kolor z Shadera wierzchołków. Podobnie jak z obieraniem danych z buforu robimy to za pomocą słowa kluczowego `in`, w przypadku przesyłania zmiennej z jednego shadera do drugiego nazwy zmiennych muszą być **takie same** przy słowie kluczowym `out` i `in`. Zmiennej obranej nie można modyfikować.
|
||||
|
||||
W najnowszej wersji opengla fragment shader nie ma domyślnego wyjścia na kolor, musimy sami je z definiować. Robimy to instrukcją `out vec4 out_color` następnie w funkcji `main` przypisujemy mu jakąś wartość.
|
||||
|
||||
### Zadanie
|
||||
W tej chwili nasz prostopadłościan jest jednolitego koloru i nie możemy rozróżnić jego ścian. Przesłana przez nas wcześniej informacja o kolorze nie została wykorzystana. Bazując na powyższych informacjach prześlij wartość koloru zapisaną w `vertexColor` do shadera fragmentu i przypisz ją do wyjściowego koloru. Dodaj zmienną `in vec4 color` w shaderze fragmentów, następnie w funkcji `main` przypisz do niej wartość koloru. W shaderze fragmentów odbierz ją za pomocą `out` i przypisz do wyjściowego koloru.
|
||||
|
||||
Zauważ, że kolor ścian nie jest jednolity, zamiast tego przechodzą gradientem od jednego koloru do drugiego. Dzieje się tak, ponieważ na etapie rasteryzacji kolor jest interpolowany. To znaczy: wartość jest uśredniana pomiędzy wierzchołkami trójkąta.
|
||||
|
||||
### Zadanie
|
||||
Sprawdź, jak będzie wyglądać prostopadłościan z wyłączoną interpolacją. Dodaj przed `in` i `out` ` color` słowo kluczowe `flat`.
|
||||
|
||||
### Zadanie
|
||||
Prześlij czas od startu aplikacji do shadera fragmentów. Użyj funkcji `glfwGetTime`, by uzyskać czas. Utwórz zmienną `uniform` typu `float` we shaderze fragmentów. Następnie prześlij do niej wynik funkcji `glfwGetTime`. Aby przesłać czas do shadera, wykorzystaj funkcje `glUniform1f`. Pierwszym argumentem jest lokacja uniforma, drugim przypisywana wartość. Lokację uzyskamy za pomocą funckji `glGetUniformLocation(progam,"time")` pierwszym argumentem jest program, którego używamy a drugim nazwa zmiennej uniform.
|
||||
Podziel kolor przez czas, by uzyskać efekt, w którym prostopadłościan robi się czarny.
|
||||
|
||||
### Zadanie*
|
||||
Wykorzystaj przesłany czas, żeby sprawić, żeby prostopadłościan znikał przez mieszanie go z kolorem tła. Wykorzystaj do tego następujące funkcje GLSL: `mix`, `sin`, `vec4`. Opis ich działania możesz znaleźć w dokumentacji https://docs.gl/.
|
||||
|
||||
### Zadanie
|
||||
|
||||
Prześlij pozycję lokalną i globalną pozycję wierzchołków do shadera fragmentów i wyświetl ją.
|
||||
|
||||
W shaderze wierzchołków obok deklaracji `out vec4 color;` dodaj analogiczne o nazwie `pos_local` i `pos_global`. Do `pos_local` przypisz `vertexPosition`, a do `pos_global` przypisz `transformation * vertexPosition`. Podobnie dopisz odebranie ich w shaderze fragmentów. Użyj `pos_local`, następnie `pos_global` jako zamiast koloru. Dlaczego otrzymaliśmy taki efekt?
|
||||
|
||||
### Zadanie*
|
||||
Użyj jednej ze zmiennych z poprzedniego zadania do zrobienia pasków na przynajmniej jednej ze ścian sześcianu. Wykorzystaj czas, żeby paski się przesuwały.
|
||||
|
101
grk/cw 6/Zadania 4.html
Normal file
@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<title>Zadania 4</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
ul.task-list{list-style: none;}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.code-block {
|
||||
background-color: #f4f4f4;
|
||||
border-left: 5px solid #f36d33;
|
||||
margin: 20px 0;
|
||||
padding: 10px 20px;
|
||||
font-family: Consolas, "Courier New", Courier, monospace;
|
||||
color: #333;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="ładowanie-obiektów-za-pomocą-assimpa">Ładowanie obiektów za pomocą Assimp</h2>
|
||||
<p>W projekcie zaimplementowano ładowanie modeli przy użyciu biblioteki Assimp. Obiekty są ładowane za pomocą funkcji <code>loadModelToContext(std::string path, Core::RenderContext& context)</code>. Pierwszym argumentem tej funkcji jest ścieżka, pod którą znajduje się model, a drugim jest referencja do <code>RenderContext</code>. Ta struktura przechowuje informacje o modelu, m.in. o jego VAO czy liczbie wierzchołków. Rysowanie obiektu odbywa się za pomocą funkcji <code>Core::DrawContext(Core::RenderContext& context)</code>. Obecnie na przykładzie rysowana jest sfera ładowana z pliku.</p>
|
||||
<p>Podczas poprzednich zadań zdefiniowaliśmy funkcje tworzące macierze widoku i projekcji. Aby narysować model, należy najpierw zdefiniować macierz modelu, przemnożyć ją przez macierz kamery i macierz widoku, a następnie wysłać ją do GPU i dopiero potem narysować model. Te operacje są bardzo powtarzalne, dlatego można je przenieść do osobnej funkcji. W pliku <code>ex_4_1.hpp</code> znajduje się funkcja <code>drawObjectColor</code>, która przyjmuje rysowany obiekt jako <code>Core::RenderContext&</code>, macierz modelu jako <code>glm::mat4</code> oraz kolor jako <code>glm::vec3</code>. <h3 id="zadanie">Zadanie</h3> Wszystkie obiekty rysowane przez <code>drawObjectColor</code> są w jednym kolorze – napraw to. Wewnątrz funkcji przekaż kolor jako <code>uniform</code> do GPU (za pomocą funkcji <code>glUniform3f</code>) i odpowiednio zmodyfikuj shader fragmentów, aby wyznaczyć go jako kolor wyjściowy.</p>
|
||||
<p>Korzystając z tej funkcji, stwórz układ słoneczny z przynajmniej jedną planetą, która posiada księżyc. Planeta powinna poruszać się wokół słońca, a księżyc wokół planety.</p>
|
||||
<h3 id="zadanie">Zadanie*</h3>
|
||||
<p>Rozbuduj układ planetarny do przynajmniej 5 planet i pasa asteroid. Ściągnij z internetu/stwórz kilka prostych modeli asteroid, z których zbudujesz pas asteroid.</p>
|
||||
<h3 id="zadanie-1">Zadanie</h3>
|
||||
<p>Celem tego zadania jest dodanie statku, który będzie latać po układzie planetarnym.</p>
|
||||
<p>Załaduj model statku, który jest w pliku <code>spaceship.obj</code>. Stwórz zmienne globalne <code>spaceshipPos</code> oraz <code>spaceshipDir</code>, które będą określać pozycję i kierunek, w którym statek się porusza. Później będziemy je zmieniać za pomocą przycisków, na razie wewnątrz funkcji <code>processInput</code> przypisz do nich odpowiednio <code>cameraPos+1.5*cameraDir+glm::vec3(0,-0.5f,0)</code> oraz <code>cameraDir</code>. W ten sposób po prawidłowym ustawieniu macierzy statek będzie znajdował się zawsze przed kamerą.</p>
|
||||
<p>Przesuń i obróć statek w odpowiedni sposób. Przesunięcie zrealizujemy przez translację do <code>spaceshipPos</code> natomiast macierz statku liczy się tak samo, jak macierz kamery, tylko zamiast <code>-cameraDir</code> bierzemy <code>spaceshipDir</code> i na końcu trzeba tę macierz odwrócić (lub transponować, co jest tym samym, ponieważ mówimy o macierzy ortonormalnej).</p>
|
||||
<h3 id="zadanie-2">Zadanie</h3>
|
||||
<h3 id="zadanie-3">Zadanie*</h3>
|
||||
<p>Obecnie klawisze regulują ustawienia kamery, do której jest podpięty statek. Zmodyfikuj aplikację, aby klawisze umożliwiały poruszanie się statkiem, a kamera podążała za nim. W tym celu w obsłudze klawiatury modyfikuj wektory <code>spaceshipPos</code> i <code>spaceshipDir</code>. Następnie dostosuj <code>cameraPos</code> i <code>cameraDir</code> w zależności od wektorów <code>spaceshipPos</code> i <code>spaceshipDir</code>.</p>
|
||||
<h3 id="zadanie-4">Zadanie*</h3>
|
||||
<p>Obecnie szybkość ruchu statku lub kamery zależy od liczby klatek, co może powodować różne efekty na różnych komputerach, co nie jest pożądane. Aby to skorygować, musimy obliczyć czas, jaki upłynął między poszczególnymi klatkami i dostosować do niego przesunięcia oraz obroty. Utwórz zmienne globalne <code>float lastFrameTime</code> oraz <code>float deltaTime</code>, następnie w funkcji <code>renderScene</code> dodaj oblicz <code>deltaTime = time-lastFrameTime</code> i przypisz do zmiennej <code>lastFrameTime</code> wartość <code>time</code>. Nie chcemy, żeby wartość <code>deltaTime</code> była zbyt duża, gdy nagle spadnie liczba klatek, dlatego ucinamy ją od góry przez <code>0.1</code>.</p>
|
||||
<p>Wykorzystaj <code>deltaTime</code> w funkcji <code>processInput</code> aby uniezależnić prędkość poruszania się od liczby klatek na sekundę.</p>
|
||||
<h3 id="zadanie-5">Zadanie*</h3>
|
||||
<p>Zastąp model statku innym modelem.</p>
|
||||
|
||||
<h2 id="bufor-głębokości">Bufor głębokości</h2>
|
||||
<p>Bufor głębokości rejestruje odległość danego piksela od kamery. Pozwala to podczas rysowania kolejnych obiektów odrzucić piksele, które znajdowałyby się za już narysowanymi. Ten mechanizm jest automatycznie, żeby go uruchomić, wystarczy dodać instrukcję <code>glEnable(GL_DEPTH_TEST)</code>, poza tym przed rysowaniem klatki należy wyczyścić bufor głębokości, co robimy w instrukcji <code>glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)</code>.</p>
|
||||
<h3 id="zadanie-7">Zadanie</h3>
|
||||
<p>Sprawdź, co się stanie, gdy nie włączymy <code>glEnable(GL_DEPTH_TEST)</code> oraz sprawdź co, się dzieje, gdy nie czyścimy żadnego bufora lub gdy czyścimy tylko bufor koloru czy tylko bufor głębokości. Dlaczego dzieje się to, co widzisz?</p>
|
||||
<h2 id="wizualizacja-bufora-głębokości">Wizualizacja bufora głębokości</h2>
|
||||
<p>W tej części zwizualizujemy jak wygląda bufor głębokości przy pomocy skali szarości. Zrealizujemy poprzez napisanie odpowiedniego shadera.</p>
|
||||
<blockquote>
|
||||
<p><strong>Uwaga</strong> nie jest to faktycznie rysowanie bufora głębokości, to wymagałoby stworzenie FrameBufferObject renderowanie głębokości do niego i narysowanie wyniku na ekranie. Zrobimy to na późniejszych zajęciach przy okazji rysowania cieni.</p>
|
||||
</blockquote>
|
||||
<p>Wykorzystamy wbudowaną zmienną <code>gl_FragCoord</code> we fragment shaderze. Zawiera ona informacje o pozycji fragmentu.</p>
|
||||
<h3 id="zadanie-8">Zadanie</h3>
|
||||
<p>We fragment shaderze podmień wartości R G B w shaderze fragmentów na <code>gl_FragCoord.z</code>.</p>
|
||||
<p>Zauważ, że obiekty są bardzo jasne i stają się ciemniejsze, dopiero gdy kamera podjedzie bardzo blisko. Wynika to z tego, że wartości <code>z</code> w <code>gl_FragCoord</code> nie są liniowe ze względu na rzutowanie perspektywiczne omówione na poprzednich zajęciach. Poniższy wykres prezentuje przykładową różnicę między faktyczną wartością a wartością w <code>gl_FragCoord</code>. <img src="./img/z_depth_graph2.jpg" /></p>
|
||||
<p>My chcielibyśmy wyświetlać je liniowo. W tym celu będziemy musieli wrócić do współrzędnych w przestrzeni świata. Zauważ, że wartości <code>gl_FragCoord.z</code> są z zakresu od <span class="math inline">\([0,1]\)</span> a nie <span class="math inline">\([-1,1]\)</span> jak są zapisane współrzędne w przestrzeni ekranu. Dlatego pierwszym krokiem będzie przekonwertowanie ich (poprzez pomnożenie przez 2 i odjęcie 1). Współrzędne w przestrzeni ekranu obliczamy wzorem <span class="math display">\[z'=-\frac{(n + f)}{(n - f)}- \frac{(2 n f)}{z(n - f)}.\]</span>My chcemy obliczyć <span class="math inline">\(z\)</span> po przekształceniu wzoru otrzymujemy: <span class="math display">\[z=\frac{-2nf}{z'(n-f)+n+f}.\]</span> <h3 id="zadanie-9">Zadanie</h3> We fragment shaderze uwórz funkcję, która oblicza <span class="math inline">\(z\)</span> i wyświetl zlinearyzowaną odległość. Pamiętaj, że wartość <span class="math inline">\(z\)</span> jest z zakresu od <span class="math inline">\(n\)</span> do <span class="math inline">\(f\)</span>, dlatego zmień podziel ją przez <span class="math inline">\(-f\)</span> przed rysowaniem.</p>
|
||||
<h3 id="zadanie-9">Zadanie</h3>
|
||||
<p>Wykorzystaj informację o odległości, żeby dodać do sceny efekt mgły. Zmieszaj kolor (funkcja "mix" w glsl) obiektu z kolorem tła, jako współczynnik weź wartość z poprzedniego zadania.</p>
|
||||
<h2 id="kreatywne-wykorzystanie-bufora-głębokości">Kreatywne wykorzystanie bufora głębokości</h2>
|
||||
<p>Czasem chcielibyśmy, żeby niektóre wyświetlane elementy były inaczej traktowane przez bufor głębokości. Przykładowo chcielibyśmy stworzyć bardziej złożone tło dla naszej sceny. Chcemy wtedy, żeby to tło było ,,za’’ każdym innym obiektem w scenie. Możemy to osiągnąć poprzez namalowanie tła na początku, a następnie usunięcie zawartości bufora głębokości.</p>
|
||||
<h3 id="zadanie-10">Zadanie*</h3>
|
||||
<p>Dodaj jakiś rodzaj tła w sposób opisany powyżej. Mogą być to na przykład małe sfery udające gwiazdy. Prostokąt w przestrzeni ekranu, który będzie zmieniał kolory czy kręcący się prostopadłościan.</p>
|
||||
<h2 id="kreatywne-wykorzystanie-bufora-głębokości">Implementacja ruchu kamery w symulatorze kosmicznym bez synchronizacji modelu statku</h2>
|
||||
<h3 id="zadanie-6">Zadanie*</h3>
|
||||
<p>W tym ćwiczeniu zajmiemy się implementacją systemu kontroli kamery, który pozwoli na interaktywne sterowanie widokiem w symulatorze lotu. Będzie to wymagało obsługi wejścia myszy do kontroli ruchów pitch i yaw. Dodaj nastepujace zmienne globalne do kodu.
|
||||
<pre class="code-block"><code>
|
||||
float pitch = 0.0f;
|
||||
float yaw = -90.0f;
|
||||
float lastX = 800.0f / 2.0;
|
||||
float lastY = 600.0 / 2.0;
|
||||
bool firstMouse = true;
|
||||
</code></pre></p>
|
||||
Zaimplementuj w funkcji processinput logikę odpowiedzialną za przetwarzanie wejścia myszy. Użyj funkcji glfwGetCursorPos do uzyskania bieżącej pozycji kursora. Dodaj zmienną sensitivity do regulacji czułości myszy. Na początku sprawdzamy, czy jest to pierwsze uchwycenie pozycji myszy po uruchomieniu aplikacji. Jeśli tak, to zapisujemy bieżącą pozycję myszy jako punkt odniesienia (lastX i lastY) i ustawiamy zmienną firstMouse na false. To zapobiega gwałtownemu skoku kamery, gdybyśmy po raz pierwszy poruszyli myszą. Następnie obliczamy różnicę (xoffset i yoffset) między aktualną pozycją kursora a ostatnią zapisaną pozycją. Zauważ, że wartość yoffset jest odwrócona, ponieważ w systemach okienkowych współrzędna y zwykle rośnie w dół ekranu, a chcemy, aby ruch w górę odpowiadał wzrostowi kąta widzenia kamery. Po obliczeniu przesunięć skalujemy je wartością sensitivity, która pozwala na dostosowanie reakcji kamery na ruch myszy. Mała wartość czułości sprawi, że kamera będzie reagować łagodniej, podczas gdy większa wartość uczyni ruchy bardziej gwałtownymi. Na koniec, dodajemy przeskalowane przesunięcia do naszych kątów orientacji kamery: yaw (odpowiedzialny za obrót wokół osi pionowej) i pitch (odpowiedzialny za obrót wokół osi poziomej). Trzeba tu uważać, aby nie przekroczyć pewnych granic. Na przykład, nie chcemy, aby kamera "przewracała się" do góry nogami, dlatego ograniczamy kąt pitch do wartości pomiędzy -89 a 89 stopni. Dodaj logikę do ograniczenia kąta pitch, aby uniknąć "przewrócenia" kamery.
|
||||
<pre class="code-block"><code> if (pitch > 89.0f)
|
||||
pitch = 89.0f;
|
||||
if (pitch < -89.0f)
|
||||
pitch = -89.0f;
|
||||
</code></pre></p>
|
||||
Zastosuj funkcje trygonometryczne do przeliczenia kątów na wektor kierunku kamery:<pre class="code-block"><code>
|
||||
glm::vec3 front;
|
||||
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
|
||||
front.y = sin(glm::radians(pitch));
|
||||
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
|
||||
cameraDir = glm::normalize(front);</code></pre></p>
|
||||
|
||||
Na zakończenie ćwiczenia oczekujemy, że symulator będzie poprawnie implementował ruch kamery, lecz dołączony model statku kosmicznego nie będzie jeszcze obracał się razem z kamerą.
|
||||
|
||||
</body>
|
||||
</html>
|
74
grk/cw 6/Zadania 4.md
Normal file
@ -0,0 +1,74 @@
|
||||
## Ładowanie obiektów za pomocą assimpa
|
||||
|
||||
W projekcie zaimplentowane jest ładowanie modeli z użyciem biblioteki assimp. Obiekty ładuje się za pomocą funkcji `loadModelToContext(std::string path, Core::RenderContext& context)` pierwszym jej argumentem jest ścieżka, pod którą znajduje się model, drugim argumentem jest referencja do `RenderContext`. Ta struktura przechowuje informacje o modelu, m.in. jego VAO czy liczba wierzchołku. Rysuje się go za pomocą funkcji `Core::DrawContext(Core::RenderContext& context)`. Teraz przykładowo rysowana jest sfera ładowana z pliku.
|
||||
|
||||
W trakcie poprzednich zadań zdefiniowaliśmy funkcje tworzące macierze widoku i projekcji. Aby narysować model, należy najpierw zdefiniować macierz modelu, przemnożyć ją przez macierz kamery i macierz widoku i wysłać ją do GPU i dopiero narysować model. Te operacje są bardzo powtarzalne i można je przenieść do osobnej funkcji. Dlatego w `ex_4_1.hpp` znajduje funkcja `drawObjectColor` która przyjmuje rysowany obiekt jako `Core::RenderContext&`, macierz modelu jako `glm::mat4` i kolor jako `glm::vec3`.
|
||||
### Zadanie
|
||||
Wszystkie obiekty rysowane przez `drawObjectColor` rysują się na jeden kolor, napraw to. Wewnątrz funkcji prześlij kolor jako `uniform` do GPU (za pomocą funkcji `glUniform3f`) i tak zmodyfikuj shader fragmentów, żeby ustalić go kolorem wyjściowym.
|
||||
|
||||
Korzystając z tej funkcji, stwórz układ słoneczny z przynajmniej jedną planetą, która posiada księżyc. Planeta powinna poruszać się wokół słońca a księżyc wokół planety.
|
||||
|
||||
### Zadanie*
|
||||
Rozbuduj układ planetarny do przynajmniej 5 planet i pasa asteroid. Ściągnij z internetu/stwórz kilka prostych modeli asteroid, z których zbudujesz pas asteroid.
|
||||
|
||||
### Zadanie
|
||||
Celem tego zadania jest dodanie statku, który będzie latać po układzie planetarnym.
|
||||
|
||||
Załaduj model statku, który jest w pliku `spaceship.obj`. Stwórz zmienne globalne `spaceshipPos` oraz `spaceshipDir`, które będą określać pozycję i kierunek, w którym statek się porusza. Później będziemy je zmieniać za pomocą przycisków, na razie wewnątrz funkcji `processInput` przypisz do nich odpowiednio `cameraPos+1.5*cameraDir+glm::vec3(0,-0.5f,0)` oraz `cameraDir`. W ten sposób po prawidłowym ustawieniu macierzy statek będzie znajdował się zawsze przed kamerą.
|
||||
|
||||
Przesuń i obróć statek w odpowiedni sposób. Przesunięcie zrealizujemy przez translację do `spaceshipPos` natomiast macierz statku liczy się tak samo, jak macierz kamery, tylko zamiast `cameraDir` bierzemy `spaceshipDir` i na końcu trzeba tę macierz odwrócić (lub transponować, co jest tym samym, ponieważ mówimy o macierzy ortonormalnej).
|
||||
|
||||
Możliwe, że model nie jest skierowany w stronę tej samej osi, co domyślnie w openGL, w tym wypadku musisz dodać dodatkową rotację o stały kąt, która naprawi ten problem.
|
||||
|
||||
### Zadanie
|
||||
|
||||
### Zadanie*
|
||||
W tej chwili klawisze ustawiają kamerę, do której podczepiony jest statek. Zmodyfikuj aplikację, żeby klawisze przesuwały statek, a kamera była do niego podczepiona. Aby to zrobić, wystarczy w obsłudze klawiatury modyfikować wektory `spaceshipPos` i `spaceshipDir`. Natomiast `cameraPos` i `cameraDir` uzależnić od wektorów `spaceshipPos` i `spaceshipDir`.
|
||||
|
||||
### Zadanie*
|
||||
W tej chwili szybkość poruszania się statku/kamery jest uzależniona od liczby klatek, co daje różne efekty na różnych komputerach i jest raczej niepożądane. Aby to naprawić, musimy obliczyć, ile czasu minęło między klatkami i od tego uzależnić przesunięcia oraz obroty. Utwórz zmienne globalne `float lastFrameTime` oraz `float deltaTime`, następnie w funkcji `renderScene` dodaj oblicz `deltaTime = time-lastFrameTime` i przypisz do zmiennej `lastFrameTime` wartość `time`. Nie chcemy, żeby wartość `deltaTime` była zbyt duża, gdy nagle spadnie liczba klatek, dlatego ucinamy ją od góry przez `0.1`.
|
||||
|
||||
Wykorzystaj `deltaTime` w funkcji `processInput` aby uniezależnić prędkość poruszania się od liczby klatek na sekundę.
|
||||
|
||||
### Zadanie*
|
||||
Podmień model statku na jakiś inny.
|
||||
|
||||
### Zadanie**
|
||||
dodaj obsługę myszki, która będzie obracać kamerą.
|
||||
|
||||
## Bufor głębokości
|
||||
|
||||
Bufor głębokości zapisuje, w jakiej odległości od kamery znajduje dany piksel. Dzięki temu przy rysowaniu kolejnych obiektów można odrzucić te piksele, które znajdowałyby się za narysowanymi. Ten mechanizm jest automatycznie, żeby go uruchomić, wystarczy dodać instrukcję `glEnable(GL_DEPTH_TEST)`, poza tym przed rysowaniem klatki należy wyczyścić bufor głębokości, co robimy w instrukcji `glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)`.
|
||||
|
||||
### Zadanie
|
||||
Sprawdź, co się stanie, gdy nie włączymy `glEnable(GL_DEPTH_TEST)` oraz sprawdź co, się dzieje, gdy nie czyścimy żadnego bufora lub gdy czyścimy tylko bufor koloru czy tylko bufor głębokości. Dlaczego dzieje się to, co widzisz?
|
||||
|
||||
|
||||
## Wizualizacja bufora głębokości
|
||||
|
||||
W tej części zwizualizujemy jak wygląda bufor głębokości przy pomocy skali szarości. Zrealizujemy poprzez napisanie odpowiedniego shadera.
|
||||
|
||||
> **Uwaga** nie jest to faktycznie rysowanie bufora głębokości, to wymagałoby stworzenie FrameBufferObject renderowanie głębokości do niego i narysowanie wyniku na ekranie. Zrobimy to na późniejszych zajęciach przy okazji rysowania cieni.
|
||||
|
||||
Wykorzystamy wbudowaną zmienną `gl_FragCoord` we fragment shaderze. Zawiera ona informacje o pozycji fragmentu.
|
||||
|
||||
### Zadanie
|
||||
We fragment shaderze podmień wartości R G B w shaderze fragmentów na `gl_FragCoord.z`.
|
||||
|
||||
Zauważ, że obiekty są bardzo jasne i stają się ciemniejsze, dopiero gdy kamera podjedzie bardzo blisko. Wynika to z tego, że wartości `z` w `gl_FragCoord` nie są liniowe ze względu na rzutowanie perspektywiczne omówione na poprzednich zajęciach. Poniższy wykres prezentuje przykładową różnicę między faktyczną wartością a wartością w `gl_FragCoord`.
|
||||

|
||||
|
||||
My chcielibyśmy wyświetlać je liniowo. W tym celu będziemy musieli wrócić do współrzędnych w przestrzeni świata.
|
||||
Zauważ, że wartości `gl_FragCoord.z` są z zakresu od $[0,1]$ a nie $[-1,1]$ jak są zapisane współrzędne w przestrzeni ekranu. Dlatego pierwszym krokiem będzie przekonwertowanie ich (poprzez pomnożenie przez 2 i odjęcie 1). Współrzędne w przestrzeni ekranu obliczamy wzorem $$z'=-\frac{(n + f)}{(n - f)}- \frac{(2 n f)}{z(n - f)}.$$My chcemy obliczyć $z$ po przekształceniu wzoru otrzymujemy:
|
||||
$$z=\frac{-2nf}{z'(n-f)+n+f}.$$
|
||||
### Zadanie
|
||||
We fragment shaderze uwórz funkcję, która oblicza $z$ i wyświetl zlinearyzowaną odległość. Pamiętaj, że wartość $z$ jest z zakresu od $n$ do $f$, dlatego zmień podziel ją przez $f$ przed rysowaniem.
|
||||
|
||||
### Zadanie*
|
||||
Wykorzystaj informację o odległości, żeby dodać do sceny efekt mgły. Zmieszaj kolor obiektu z kolorem tła, jako współczynnik weź wartość z poprzedniego zadania.
|
||||
|
||||
## Kreatywne wykorzystanie bufora głębokości
|
||||
Czasem chcielibyśmy, żeby niektóre wyświetlane elementy były inaczej traktowane przez bufor głębokości. Przykładowo chcielibyśmy stworzyć bardziej złożone tło dla naszej sceny. Chcemy wtedy, żeby to tło było ,,za'' każdym innym obiektem w scenie. Możemy to osiągnąć poprzez namalowanie tła na początku, a następnie usunięcie zawartości bufora głębokości.
|
||||
|
||||
### Zadanie
|
||||
Dodaj jakiś rodzaj tła w sposób opisany powyżej. Mogą być to na przykład małe sfery udające gwiazdy. Prostokąt w przestrzeni ekranu, który będzie zmieniał kolory czy kręcący się prostopadłościan.
|
92
grk/cw 6/Zadania 5.md
Normal file
@ -0,0 +1,92 @@
|
||||
## Oświetlenie Phonga
|
||||
W trakcie tych zajęć zajmiemy się implementacją modelu oświetlenia Phonga. Na poprzednich zajęciach zbudowaliśmy układ słoneczny. Wykorzystamy go w trakcie tych zajęć. Jeżeli go zrobiłeś, to przekopiuj do `ex_5_1.hpp` kod z poprzednich zajęć. W przeciwnym wypadku wykorzystaj ten, który jest zaimplementowany w `ex_5_1.hpp`. W zadaniu będzie nam potrzebny statek latający przed kamerą, jak nie ma tego w Twojej scenie to skopiuj to z `ex_5_1.hpp`.
|
||||
|
||||
|
||||
## Zadanie - *diffuse*
|
||||
Oblicz we fragment shaderze oświetlenie obiektu przy użyciu modelu Phonga dla odbicia rozproszonego (*diffuse)
|
||||
1. Przekaż źródło światła. Na razie przyjmiemy kierunkowy model oświetlenia, dlatego źródło będzie opisane jako wektor kierunkowy:
|
||||
|
||||
- Prześlij do shadera fragmentów zmienna typu `uniform vec3` (nazwij ją np. `lightDir`), w której będzie się znajdować wektor kierunkowy.
|
||||
- Należy to zrobić podobnie do tego, jak przesyłany jest kolor.
|
||||
- Jako kierunek światła wybierz dowolny wektor jednostkowy. (Możesz wziąć dowolny niezerowy wektor następnie go znormalizować).
|
||||
|
||||
* Dodatkowo prześlij drugą zmienną `uniform vec3 lightColor`, w której umieścimy kolor światła. Prześlij tam wartości ze zmiennej `glm::vec3 lightColor`.
|
||||
|
||||
2. Oblicz natężenie w shaderze fragmentów:
|
||||
- prześlij normalne z shadera fragmentów do shadera wierzchołków
|
||||
- znormalizuj wektor normalny przed użyciem go w obliczeniach (uśrednianie wektorów normalnych wierzchołków może spowodować, że przestaje one być jednostkowe).
|
||||
|
||||
- Natężenie to iloczyn skalarny wektora normalnego powierzchni i odwrotnego wektora kierunku padania światła. Skorzystaj z funkcji `dot`.
|
||||
|
||||
- Natężenie nie może być ujemne. Przytnij natężenie do zera przy użyciu: `x = max(x, 0.0)`
|
||||
3. Zastosuj obliczone natężenie, aby zmodyfikować kolor obiektu:
|
||||
- Przemnóż kolor RGB fragmentu przez obliczone natężenie i przez kolor światła z `lightColor`.
|
||||
|
||||
## Zadanie - obsługa obrotów
|
||||
Dlaczego oświetlenie statku nie zmienia się podczas jego obracania?
|
||||
|
||||
(Wektory normalne są w układzie lokalnym modelu, a wektor padania światła w układzie świata)
|
||||
|
||||
Należy wykonać transformacje wektorów normalnych do przestrzeni świata:
|
||||
- Prześlij macierz modelu rysowanego obiektu (_model Matrix_) jako osobna zmienna do vertex shadera (`uniform mat4`).
|
||||
- Przemnóż przez te macierz wektor normalny wierzchołka przed przesłaniem go do shadera fragmentów.
|
||||
- Współrzędna **w** dopisana do wektora przed mnożeniem przez macierz powinna być ustawiona na 0. Wynika to z tego, że w przypadku transformacji wektorów reprezentujących kierunki w przestrzeni, nie chcemy dokonywać translacji — np. wektor normalny do powierzchni zależy od orientacji obiektu, ale nie od jego pozycji (przesunięcia) w przestrzeni świata.
|
||||
|
||||
|
||||
## Zadanie - *specular*
|
||||
Uzupełnił model o czynnik odbicia zwierciadlanego (*specular*). W tym celu:
|
||||
|
||||
1. Potrzebny będzie wektor od rysowanego fragmentu do pozycji kamery:
|
||||
- Wyślij pozycje kamery (`cameraPos`) jako kolejna zmienna do fragment shadera.
|
||||
- Podobnie jak wektory normalne prześlij z vertex do fragment shadera pozycje wierzchołków (`vertexPosition`) w przestrzeni świata (czyli pomnożone przez macierz **modelMatrix**). Pamiętaj, że tym razem wektory reprezentują punkty, a nie kierunki - współrzędna **w** przed mnożeniem musi być ustawiona na 1. W wyniku rasteryzacji otrzymamy w shaderze fragmentu jego pozycję (nazywaną pozycją fragmentu)
|
||||
- Oblicz wektor **V** (_view direction_) jako znormalizowaną różnice pozycji kamery i pozycji fragmentu.
|
||||
|
||||
2. Oblicz natężenie światła odbitego we _fragment shaderze_:
|
||||
- Oblicz wektor kierunku odbicia światła **R** przy użyciu funkcji [`reflect`](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/reflect.xhtml). Pamiętaj, żeby przesłać do funkcji odwrócony wektor kierunku światła.
|
||||
- Oblicz natężenie: iloczyn skalarny **V** i **R**, przycięty do zera ( `max(...,0.0)` ), a następnie podniesiony do wysokiej potęgi (np. 8, 50, 1000), która jest miara połyskliwości powierzchni.
|
||||
|
||||
3. Ustal ostateczny kolor piksela na `objectColor * diffuse + lightColor * specular`. Oznacza to najprostszy przypadek, gdy kolor światła odbitego jest biały.
|
||||
|
||||
|
||||
## Zadanie - oświetlenie punktowe
|
||||
W układzie planetarnym obiektem oświetlającym powinno być słońce, dlatego zamień oświetlenie kierunkowe na punktowe:
|
||||
|
||||
- Zamiast przesyłać (w `lightDir`) kierunek światła, prześlij pozycję słońca do fragment shadera (taką jak ustawiłeś w punkcie powyżej) jako uniform vec3 (nazwij go `lightPos`).
|
||||
- Oblicz kierunek światła odejmując od pozycji fragmentu pozycję światła, znormalizuj wynik. Zapisz wynik w zmiennej `lightDir`.
|
||||
- Słońce będzie czarne, nie martw się tym, tylko spójrz na punkt następny.
|
||||
|
||||
## Zadanie - shader słońca
|
||||
Źródło światła znajduje się wewnątrz kuli, która reprezentuje słońce, dlatego jest czarna. By to naprawić, utwórz osobny shader, który będzie odpowiadać za renderowanie słońca.
|
||||
|
||||
Celem tego zadania jest stworzenie shadera (**shader_4_sun.vert**_ i **shader_4_sun.frag**), który będzie odpowiadał wyłącznie za rysowanie słońca. Poprzednie shadery (**shader_4_1.vert**_ i **shader_4_1.frag**) nadal mają rysować pozostałe obiekty. a) zainicjalizuj _program_ (shadery): - Pliki **shader_4_sun.vert** i **shader_4_sun.frag** są identyczne z 4_1** przed zmianami, będą punktem wyjścia dla _shadera_ słońca.
|
||||
|
||||
1. Utwórz zmienną globalną `GLuint programSun` na adres shadera słońca. Stwórz _program_ za pomocą `shaderLoader.CreateProgram` analogicznie jak tworzy się `program` z **shader_4_1.vert** i **shader_4_1.frag** (parametry wejściowe to ścieżka do shadera wierzchołków i fragmentów **shader_4_sun.vert** i **shader_4_sun.frag**).
|
||||
|
||||
2. W skomplikowanym projekcie różne typy obiektów rysuje się przy użyciu różnych shaderów, zate potrzebna jest w programie architektura, która na to pozwala. Ustaw odpowiedni _program_ (shadery) do rysowania słońca:
|
||||
- funkcja `drawObject` korzysta z globalnej zmiennej `program`, żeby wskazywać shadery do rysowania. Dodaj argument do funkcji, który będziesz przekazywać adres _programu_, który ma być wykorzystany do rysowania.
|
||||
- dodaj odpowiedni _program_ w wywołaniach `drawObject`.
|
||||
|
||||
## Osłabienie światła, tone mapping
|
||||
### Zadanie - Osłabienie światła (attenuation)
|
||||
Światło pochodzące z punktowego źródła traci na sile wraz z dystansem. Wynika to z tego, że rozprasza się na większą powierzchnię.
|
||||
Dodaj ten efekt do shadera. Zamiast brać kolor światła bezpośrednio, podziel go przez kwadrat dystansu od źródła świata.
|
||||
|
||||
#### Tone mapping
|
||||
Przez obecną zmianę scena stała się ciemna. Wymaga to od nas zmiany 'koloru' światła na wartości dużo większe niż do tej pory. Jeśli teraz to zrobimy i przesadzimy w drugą stronę, otrzymamy efekt prześwietlenia. Wynika to z ograniczenia zakresu kolorów do $[0,1]$ (obsługą wyższych wartości nazywamy HDR). Rozwiązaniem jest pracowanie na wartościach powyżej 1 wykorzystanie *tone mappingu* do przeniesienia ich w zakres $[0,1]$. Istnieje wiele wzorów, które są wykorzystywane do tego, jeden z nich to:
|
||||
|
||||
$$C_{mapped} = 1-e^{-C * E},$$
|
||||
gdzie C to kolor sceny a E to parametr ekspozycji (z zakresu $(0,\infty)$, który może być dostosowany w zależności od jasności.
|
||||
### Zadanie - Tone mapping*
|
||||
Zwiększ siłę słońca przynajmniej stukrotnie. Zaimplementuj powyższą metodę tone mappingu i dodaj możliwość sterowania ekspozycją za pomocą klawiszy 1 i 2.
|
||||
|
||||
### Zadanie*
|
||||
Dodaj drugie źródło oświetlenia w postaci reflektora statku. Reflektor świeci tylko w określonym stożku,dlatego oprócz pozycji `spotPos` posiada również kierunek `spotDir` i kąt świecenia $\phi$. Po obliczeniu dla niego `lightDir` należy sprawdzić, czy iloczyn skalarny pomiędzy `lightDir` a `spodDir` jest większy niż $\cos\phi$ . Jeżeli nie jest, to stożek nie świeci w tym miejscu. Można ułatwić sobie implementację wielu źródeł światła poprzez przeniesienie obliczeń oświetlenia do funkcji, która przyjmuje kierunek światła i siłę naświetlenie.
|
||||
|
||||
Zwróć uwagę, że SpotDir to co innego niż light Dir w poprzednich zadaniach
|
||||

|
||||
## Zmień shader słońca na bardziej realistyczny.
|
||||
### Zadanie*
|
||||
|
||||
Na poniższym obrazku jest zdjęcie słońca. Jest ono ciemniejsze na brzegach spróbuj uzyskać podobny efekt. Przydadzą się wektory z poprzednich punktów jak wektor normalny i wektor **V**.
|
||||
|
||||

|
196
grk/cw 6/Zadania 7.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<title>Zadania 7</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
ul.task-list{list-style: none;}
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="normal-mapping">Normal Mapping</h1>
|
||||
<p>W tej części będziemy dalej modyfikować shadery <strong>shader_5_tex</strong> poprzez implementację normal mappingu.</p>
|
||||
<p>Obliczenia dla map normalnych należy wykonywać w przestrzeni stycznych. Przestrzeń styczna jest wyliczana dla każdego punktu w obiekcie. Jej celem jest takie przekształcenie przestrzeni, żeby wektor normalny był wektorem jednostkowym (0,1,0).</p>
|
||||
<p>Do wyliczenia przestrzeni stycznej potrzebujemy dla każdego wierzchołka oprócz wektora normalnego wektor styczny i bistyczny (<em>tangent</em> i <em>bitangent</em>). Są one wyliczane przez bibliotekę <code>Assimp</code>.</p>
|
||||
<h3 id="wykonaj-kopię-shaderów-shader_4_tex.vert-shader_4_tex.frag">Wykonaj kopię shaderów shader_5_tex.vert shader_5_tex.frag</h3>
|
||||
<h3 id="przenieś-obliczenia-światła-do-przestrzeni-stycznej.">Przenieś obliczenia światła do przestrzeni stycznej.</h3>
|
||||
<ol type="1">
|
||||
<li><p>Oblicz macierz <strong>TBN</strong>.</p>
|
||||
<p>Macierz <strong>TBN</strong> to macierz 3x3 wyznaczana przez wektory <em>tangent</em>, <em>bitangent</em> i <em>normal</em>, służy do przenoszenia wektorów z przestrzeni świata do przestrzeni stycznej.</p>
|
||||
<ol type="1">
|
||||
<li>W <strong>vertex shaderze</strong> przekrztałć wektory <code>vertexNormal</code>, <code>vertexTangent</code> i <code>vertexBitangent</code> do przestrzeni świata (przemnóż macierz modelu przez te wektory, tak jak to robiliśmy wcześniej z wektorem normalnym, z uwzględnieniem zmiennej w=0) i zapisz wyniki odpowiednio w zmiennych <code>normal</code>, <code>tangent</code> i <code>bitangent</code>.
|
||||
<ol type="1">
|
||||
<li>Stwórz macierz 3x3 TBN jako transpose(mat3(tangent, bitangent, normal)). Macierz transponujemy, aby szybko uzyskać jej odwrotność (możemy tak zrobić przy założeniu, ze jest ortogonalna).</li>
|
||||
</ol></li>
|
||||
</ol></li>
|
||||
</ol>
|
||||
<ol start="2" type="1">
|
||||
<li>Przenieś wektor światła i wektor widoku do przestrzeni stycznych
|
||||
<ol type="1">
|
||||
<li><p>Musimy przekształcić wektor światła (L) i wektor widoku (V) do przestrzeni stycznych. Zrobimy to w vertex shaderze. W tym celu przenieś potrzebne dane dotyczące światła i kamery (uniformy <code>lightPos</code> i <code>cameraPos</code>) z <strong>fragment shadera</strong> do <strong>vertex shadera.</strong></p></li>
|
||||
<li><p>Oblicz wektor <code>viewDir</code> jako znormalizowana różnice <code>cameraPos</code> i <code>worldPos</code> (tu jeszcze działamy w przestrzeni świata). Analogicznie oblicz <code>lightDir</code> jako różnicę <code>lightPos</code> i <code>worldPos</code></p></li>
|
||||
<li><p>Przekształć wektory <code>viewDir</code> i <code>lightDir</code> do przestrzeni stycznej mnożąc je przez macierz <strong>TBN</strong>. Wynik zapisz w zmiennych <code>viewDirTS</code> i <code>lightDirTS</code> odpowiednio.</p></li>
|
||||
<li><p>Przekaż <code>viewDirTS</code> i <code>lightDirTS</code> do fragment shadera. (zadeklaruj je jako zmienne <code>out</code>)</p>
|
||||
<blockquote>
|
||||
<p>(Sufiks TS oznacza tangent space. Ważne jest, aby oznaczać (np. dopisując coś do nazwy zmiennej) w jakiej przestrzeni znajdują się używane wektory, tak aby poprawnie wykonywać obliczenia. Trzeba zawsze zwracać uwagę na to, w jakiej przestrzeni działamy.)</p>
|
||||
</blockquote></li>
|
||||
</ol></li>
|
||||
<li>Przekształć <strong>fragment shader</strong>, by obsługiwał <strong>tangent space</strong>
|
||||
<ol type="1">
|
||||
<li>Nie potrzebujemy już we <strong>fragment shaderze</strong> informacji na temat pozycji fragmentu i wektora normalnego geometrii, skasuj wiec zmienne przekazujące te wektory pomiędzy shaderami.</li>
|
||||
<li>wektora <code>lightDir</code> powinniśmy użyć wektora <code>lightDirTS</code> (należy go dodatkowo znormalizować), a jako wektor widoku V powinniśmy użyć wektora <code>viewDirTS</code> (również należy go znormalizować). Jako wektora N użyj na razie wektora vec3(0,0,1).</li>
|
||||
</ol></li>
|
||||
</ol>
|
||||
<p>Efekt finalny powinien wyglądać tak samo, jak przed jakąkolwiek zmianą. Następnym krokiem będzie wykorzystanie map normalnych.</p>
|
||||
<h3 id="wykorzystaj-normalmapy">Wykorzystaj normalmapy</h3>
|
||||
<ol type="1">
|
||||
<li>Chcemy wczytywać normalne z tekstury, w tym celu dodaj we <strong>fragment shaderze</strong> dodatkowy sampler do czytania map normalnych, nazwij go <code>normalSampler</code>. Pobierz z niego wektor normalny analogicznie, jak czytamy kolor zwykłej tekstury z samplera <code>textureSampler</code> i zapisz go w zmiennej <code>N</code>.
|
||||
<ol start="2" type="1">
|
||||
<li>Ponieważ w teksturze wartości są w przedziale <span class="math inline">\([0,1]\)</span>, musimy jeszcze przekształcić je do przedziału <span class="math inline">\([-1,1]\)</span>. W tym celu przemnóż wektor N przez 2 i odejmij 1. Na koniec warto jeszcze znormalizować wektor normalny, aby uniknąć błędów związanych z precyzja lub kompresja tekstury.</li>
|
||||
<li>Wczytaj pliki zawierające mapy normalnych w kodzie C++ W tym celu załaduj przy użyciu funkcji <code>Core::LoadTexture</code> mapy normalnych dla wszystkich modeli. Maja one taką samą nazwę jak zwykle tekstury, tyle że z suffiksem "_normals".</li>
|
||||
<li>Zmodyfikuj na koniec funkcje <code>drawObjectTexture</code>. Dodaj do niej nowy argument <code>GLuint normalmapId</code>, który będzie służył do przekazywania id tekstury zawierającej mapę normalnych. Przy użyciu funkcji <code>Core::SetActiveTexture</code> załaduj <code>normalmapId</code> jako <code>normalSampler</code> i ustaw jednostkę teksturowania nr 1. Argument odpowiadający za normalne w miejscach wywołania funkcji <code>drawObjectTexture</code>.</li>
|
||||
</ol></li>
|
||||
</ol>
|
||||
<h3 id="zadanie">Zadanie</h3>
|
||||
<p>Ustaw mapy normalne do statku planety i księżyca (lub przynajmniej 3 obiektów, jeżeli rysujesz swoją scenę). Wykorzystaj multitexturing na statku, musisz w takim wypadku mieszać zarówno tekstury koloru i normalanych.</p>
|
||||
<h2 id="skybox">SkyBox</h2>
|
||||
<p>Cubemapy są specjalnym rodzajem tekstur. Zawieją one 6 tekstur, każda z niej odpowiada za inną ścianę sześcianu. Nie służy ona do teksturowania zwykłego sześcianu, pozwala ona bowiem próbkować po wektorze kierunku. To znaczy, możemy o tym myśleć jak o kostce, w której środku się znaleźliśmy, co obrazuje poniższy rysunek. W przeciwieństwie do zwykłych tekstur samplujemy ją nie za pomocą dwuwymiarowych współrzędnych UV, ale za pomocą wektora trójwymiarowego, który odpowiada kierunkowi promienia. <img src="./img/cubemaps_sampling.png" /> Jednym z zastosowań Cubemapy jest wyświetlanie skyboxa, czyli dalekiego tła dla sceny. Może to być na przykład rozgwieżdżone niebo z górami na horyzoncie albo obraz dalekiej galaktyki.</p>
|
||||
<h3 id="ładowanie-cubemapy">Ładowanie cubemapy</h3>
|
||||
<p>Cubemapę generujemy podobnie jak inne tekstury, ale przy bindowaniu należy podać <code>GL_TEXTURE_CUBE_MAP</code>.</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>glGenTextures(<span class="dv">1</span>, &textureID);</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);</span></code></pre></div>
|
||||
<p>Skoro składa się ona z 6 tekstur, to należy każdą z nich załadować za pomocą <code>void glTexImage2D( GLenum target, ...)</code> <code>taget</code> wskazuje którą z tekstur ładujemy. Możliwe wartości rozpisane są w tabeli poniżej |Layer number|Texture target | Orientation| |—-|——————–:|———–:| |0|<code>GL_TEXTURE_CUBE_MAP_POSITIVE_X</code> | Right | |1|<code>GL_TEXTURE_CUBE_MAP_NEGATIVE_X</code>|Left| |2|<code>GL_TEXTURE_CUBE_MAP_POSITIVE_Y</code>|Top| |3|<code>GL_TEXTURE_CUBE_MAP_NEGATIVE_Y</code>|Bottom| |4|<code>GL_TEXTURE_CUBE_MAP_POSITIVE_Z</code>|Back| |5|<code>GL_TEXTURE_CUBE_MAP_NEGATIVE_Z</code>|Front|</p>
|
||||
<p>Możemy je ładować w pętli biorąc za kolejne targety <code>GL_TEXTURE_CUBE_MAP_POSITIVE_X+i</code>, ale należy pamiętać o powyższej kolejności. Poniższy kod ładuje do wszystkich 6 ścian tę samą teksturę, która znajduje się pod <code>filepath</code>.</p>
|
||||
<div class="sourceCode" id="cb2"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> w, h;</span>
|
||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span>(<span class="dt">unsigned</span> <span class="dt">int</span> i = <span class="dv">0</span>; i < <span class="dv">6</span>; i++)</span>
|
||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">unsigned</span> <span class="dt">char</span>* image = SOIL_load_image(filepath, &w, &h, <span class="dv">0</span>, SOIL_LOAD_RGBA);</span>
|
||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> glTexImage2D(</span>
|
||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, </span>
|
||||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span>, GL_RGBA, w, h, <span class="dv">0</span>, GL_RGBA, GL_UNSIGNED_BYTE, image</span>
|
||||
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> );</span>
|
||||
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>Na koniec pozostaje ustawić parametry opisujące zachowanie tekstury:</p>
|
||||
<div class="sourceCode" id="cb3"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</span>
|
||||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</span>
|
||||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);</span>
|
||||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);</span>
|
||||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); </span></code></pre></div>
|
||||
<h3 id="zadanie-1">Zadanie*</h3>
|
||||
<p>Napisz funkcję, która będzie ładować cubmapę bazującą na tablicy 6 stringów i załaduj do niej tekstury z foldera <code>skybox</code>.</p>
|
||||
<h3 id="rysowanie-skyboxa">Rysowanie skyboxa</h3>
|
||||
<p>Skybox jest sześcianem, wewnątrz którego zamieszamy naszą scenę, przedstawia on dalekie tło, dzięki temu dostajemy iluzję głębi i przestrzeni. Do tego potrzebujemy narysować sześcian i narysować go odpowiednim shaderem. Rysowanie jest bardzo proste, polega wyłącznie na wyświetleniu koloru tekstury. Aby próbkować, teksturę potrzebujemy przesłać pozycję w przestrzeni modelu do shadera fragmentów.</p>
|
||||
<div class="sourceCode" id="cb4"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#</span><span class="er">version 430 core</span></span>
|
||||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>layout(location = <span class="dv">0</span>) in vec3 vertexPosition;</span>
|
||||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>uniform mat4 transformation;</span>
|
||||
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>out vec3 texCoord;</span>
|
||||
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> main()</span>
|
||||
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a> texCoord = vertexPosition;</span>
|
||||
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a> gl_Position = transformation * vec4(vertexPosition, <span class="fl">1.0</span>);</span>
|
||||
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<blockquote>
|
||||
<p>shader_skybox.vert</p>
|
||||
</blockquote>
|
||||
<p>W shaderze fragmentów wystarczy odebrać pozycję i próbkować za jej pomocą teksturę.</p>
|
||||
<div class="sourceCode" id="cb5"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#</span><span class="er">version 430 core</span></span>
|
||||
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>uniform samplerCube skybox;</span>
|
||||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>in vec3 texCoord;</span>
|
||||
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>out vec4 out_color;</span>
|
||||
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> main()</span>
|
||||
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a> out_color = texture(skybox,texCoord);</span>
|
||||
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<blockquote>
|
||||
<p>shader_skybox.frag</p>
|
||||
</blockquote>
|
||||
<h3 id="zadanie-2">Zadanie*</h3>
|
||||
<p>W modelach znajduje się <code>cube.obj</code>, załaduj go i narysuj shaderami <code>shader_skybox.vert</code> i <code>shader_skybox.vert</code>. Pamiętaj o przesłaniu macierzy transformacji i tekstury skyboxa. Aktywujemy ją za pomocą instrukcji:</p>
|
||||
<div class="sourceCode" id="cb6"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);</span></code></pre></div>
|
||||
<p>Skybox potencjalnie zasłania pewne obiekty, które są umieszczone trochę dalej. Wynika to z tego, że renderowanie go nadpisuje bufor głębokości. Dlatego narysuj cubemapę z wyłączonym testem głębokości na samym początku. Następnie włącz test głębokości dla reszty sceny. Dezaktywację wykonasz za pomocą instrukcji <code>glDisable(GL_DEPTH_TEST);</code>, natomiast aktywację za pomocą<code>glEnable(GL_DEPTH_TEST);</code>.</p>
|
||||
<p>Skybox reprezentuje obiekty, które są bardzo daleko. Tę iluzję możemy utracić, gdy kamera przysunie się zbyt blisko do skyboxa. Aby tego uniknąć, musimy tak umieścić skybox, by kamera zawsze była w jego środku. Przesuń skybox do pozycji kamery z użyciem macierzy translacji.</p>
|
||||
</body>
|
||||
</html>
|
148
grk/cw 6/Zadania 7.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Normal Mapping
|
||||
|
||||
W tej części będziemy dalej modyfikować shadery **shader_5_tex** poprzez implementację normal mappingu.
|
||||
|
||||
Obliczenia dla map normalnych należy wykonywać w przestrzeni stycznych. Przestrzeń styczna jest wyliczana dla każdego punktu w obiekcie. Jej celem jest takie przekształcenie przestrzeni, żeby wektor normalny był wektorem jednostkowym (0,1,0).
|
||||
|
||||
Do wyliczenia przestrzeni stycznej potrzebujemy dla każdego wierzchołka oprócz wektora normalnego wektor styczny i bistyczny (*tangent* i *bitangent*). Są one wyliczane przez bibliotekę `Assimp`.
|
||||
|
||||
### Wykonaj kopię shaderów shader_4_tex.vert shader_4_tex.frag
|
||||
|
||||
### Przenieś obliczenia światła do przestrzeni stycznej.
|
||||
|
||||
1) Oblicz macierz **TBN**.
|
||||
|
||||
Macierz **TBN** to macierz 3x3 wyznaczana przez wektory *tangent*, *bitangent* i *normal*, służy do przenoszenia wektorów z przestrzeni świata do przestrzeni stycznej.
|
||||
|
||||
1. W **vertex shaderze** przekrztałć wektory `vertexNormal`, `vertexTangent` i `vertexBitangent` do przestrzeni świata (przemnóż macierz modelu przez te wektory, tak jak to robiliśmy wcześniej z wektorem normalnym, z uwzględnieniem zmiennej w=0) i zapisz wyniki odpowiednio w zmiennych `normal`, `tangent` i `bitangent`.
|
||||
1. Stwórz macierz 3x3 TBN jako transpose(mat3(tangent, bitangent, normal)). Macierz transponujemy, aby szybko uzyskać jej odwrotność (możemy tak zrobić przy założeniu, ze jest ortogonalna).
|
||||
|
||||
2. Przenieś wektor światła i wektor widoku do przestrzeni stycznych
|
||||
1. Musimy przekształcić wektor światła (L) i wektor widoku (V) do przestrzeni stycznych. Zrobimy to w vertex shaderze. W tym celu przenieś potrzebne dane dotyczące światła i kamery (uniformy `lightPos` i `cameraPos`) z **fragment shadera** do **vertex shadera.**
|
||||
|
||||
2. Oblicz wektor `viewDir` jako znormalizowana różnice `cameraPos` i `fragPos` (tu jeszcze działamy w przestrzeni świata). Analogicznie oblicz `lightDir` jako różnicę `lightPos` i `fragPos`
|
||||
|
||||
3. Przekształć wektory `viewDir` i `lightDir` do przestrzeni stycznej mnożąc je przez macierz **TBN**. Wynik zapisz w zmiennych`viewDirTS` i `lightDirTS` odpowiednio.
|
||||
|
||||
4. Przekaż `viewDirTS` i `lightDirTS` do fragment shadera. (zadeklaruj je jako zmienne `out`)
|
||||
|
||||
> (Sufiks TS oznacza tangent space. Ważne jest, aby oznaczać (np. dopisując coś do nazwy zmiennej) w jakiej przestrzeni znajdują się używane wektory, tak aby poprawnie wykonywać obliczenia. Trzeba zawsze zwracać uwagę na to, w jakiej przestrzeni działamy.)
|
||||
|
||||
3. Przekształć **fragment shader**, by obsługiwał **tangent space**
|
||||
1. Nie potrzebujemy już we **fragment shaderze** informacji na temat pozycji fragmentu i wektora normalnego geometrii, skasuj wiec zmienne przekazujące te wektory pomiędzy shaderami.
|
||||
2. wektora `lightDir` powinniśmy użyć wektora `lightDirTS` (należy go dodatkowo znormalizować), a jako wektor widoku V powinniśmy użyć wektora `viewDirTS` (również należy go znormalizować). Jako wektora N użyj na razie wektora vec3(0,0,1).
|
||||
|
||||
Efekt finalny powinien wyglądać tak samo, jak przed jakąkolwiek zmianą. Następnym krokiem będzie wykorzystanie map normalnych.
|
||||
|
||||
### Wykorzystaj normalmapy
|
||||
|
||||
1. Chcemy wczytywać normalne z tekstury, w tym celu dodaj we **fragment shaderze** dodatkowy sampler do czytania map normalnych, nazwij go `normalSampler`. Pobierz z niego wektor normalny analogicznie, jak czytamy kolor zwykłej tekstury z samplera `textureSampler` i zapisz go w zmiennej `N`.
|
||||
2. Ponieważ w teksturze wartości są w przedziale $[0,1]$, musimy jeszcze przekształcić je do przedziału $[-1,1]$. W tym celu przemnóż wektor N przez 2 i odejmij 1.
|
||||
Na koniec warto jeszcze znormalizować wektor normalny, aby uniknąć błędów związanych z precyzja lub kompresja tekstury.
|
||||
3. Wczytaj pliki zawierające mapy normalnych w kodzie C++ W tym celu załaduj przy użyciu funkcji `Core::LoadTexture` mapy normalnych dla wszystkich modeli. Maja one taką samą nazwę jak zwykle tekstury, tyle że z suffiksem "_normals".
|
||||
4. Zmodyfikuj na koniec funkcje `drawObjectTexture`. Dodaj do niej nowy argument `GLuint normalmapId`, który będzie służył do przekazywania id tekstury zawierającej mapę normalnych. Przy użyciu funkcji `Core::SetActiveTexture` załaduj `normalmapId` jako `normalSampler` i ustaw jednostkę teksturowania nr 1.
|
||||
Argument odpowiadający za normalne w miejscach wywołania funkcji `drawObjectTexture`.
|
||||
|
||||
|
||||
### Zadanie*
|
||||
Ustaw mapy normalne do statku planety i księżyca (lub przynajmniej 3 obiektów, jeżeli rysujesz swoją scenę). Wykorzystaj multitexturing na statku, musisz w takim wypadku mieszać zarówno tekstury koloru i normalanych.
|
||||
|
||||
## SkyBox
|
||||
Cubemapy są specjalnym rodzajem tekstur. Zawieją one 6 tekstur, każda z niej odpowiada za inną ścianę sześcianu. Nie służy ona do teksturowania zwykłego sześcianu, pozwala ona bowiem próbkować po wektorze kierunku. To znaczy, możemy o tym myśleć jak o kostce, w której środku się znaleźliśmy, co obrazuje poniższy rysunek. W przeciwieństwie do zwykłych tekstur samplujemy ją nie za pomocą dwuwymiarowych współrzędnych UV, ale za pomocą wektora trójwymiarowego, który odpowiada kierunkowi promienia.
|
||||

|
||||
Jednym z zastosowań Cubemapy jest wyświetlanie skyboxa, czyli dalekiego tła dla sceny. Może to być na przykład rozgwieżdżone niebo z górami na horyzoncie albo obraz dalekiej galaktyki.
|
||||
|
||||
### Ładowanie cubemapy
|
||||
Cubemapę generujemy podobnie jak inne tekstury, ale przy bindowaniu należy podać `GL_TEXTURE_CUBE_MAP`.
|
||||
```C++
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
|
||||
```
|
||||
Skoro składa się ona z 6 tekstur, to należy każdą z nich załadować za pomocą `void glTexImage2D( GLenum target, ...)` `taget` wskazuje którą z tekstur ładujemy. Możliwe wartości rozpisane są w tabeli poniżej
|
||||
|Layer number|Texture target | Orientation|
|
||||
|----|--------------------:|-----------:|
|
||||
|0|`GL_TEXTURE_CUBE_MAP_POSITIVE_X` | Right |
|
||||
|1|`GL_TEXTURE_CUBE_MAP_NEGATIVE_X`|Left|
|
||||
|2|`GL_TEXTURE_CUBE_MAP_POSITIVE_Y`|Top|
|
||||
|3|`GL_TEXTURE_CUBE_MAP_NEGATIVE_Y`|Bottom|
|
||||
|4|`GL_TEXTURE_CUBE_MAP_POSITIVE_Z`|Back|
|
||||
|5|`GL_TEXTURE_CUBE_MAP_NEGATIVE_Z`|Front|
|
||||
|
||||
Możemy je ładować w pętli biorąc za kolejne targety `GL_TEXTURE_CUBE_MAP_POSITIVE_X+i`, ale należy pamiętać o powyższej kolejności. Poniższy kod ładuje do wszystkich 6 ścian tę samą teksturę, która znajduje się pod `filepath`.
|
||||
|
||||
```C++
|
||||
|
||||
int w, h;
|
||||
unsigned char *data;
|
||||
for(unsigned int i = 0; i < 6; i++)
|
||||
{
|
||||
unsigned char* image = SOIL_load_image(filepath, &w, &h, 0, SOIL_LOAD_RGBA);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
|
||||
0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data
|
||||
);
|
||||
}
|
||||
```
|
||||
Na koniec pozostaje ustawić parametry opisujące zachowanie tekstury:
|
||||
```C++
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
```
|
||||
|
||||
### Zadanie
|
||||
Napisz funkcję, która będzie ładować cubmapę bazującą na tablicy 6 stringów i załaduj do niej tekstury z foldera `skybox`.
|
||||
|
||||
### Rysowanie skyboxa
|
||||
Skybox jest sześcianem, wewnątrz którego zamieszamy naszą scenę, przedstawia on dalekie tło, dzięki temu dostajemy iluzję głębi i przestrzeni. Do tego potrzebujemy narysować sześcian i narysować go odpowiednim shaderem. Rysowanie jest bardzo proste, polega wyłącznie na wyświetleniu koloru tekstury. Aby próbkować, teksturę potrzebujemy przesłać pozycję w przestrzeni modelu do shadera fragmentów.
|
||||
|
||||
```C++
|
||||
#version 430 core
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
|
||||
uniform mat4 transformation;
|
||||
|
||||
out vec3 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
texCoord = vertexPosition;
|
||||
gl_Position = transformation * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
|
||||
```
|
||||
> shader_skybox.vert
|
||||
|
||||
W shaderze fragmentów wystarczy odebrać pozycję i próbkować za jej pomocą teksturę.
|
||||
|
||||
```C++
|
||||
#version 430 core
|
||||
|
||||
uniform samplerCube skybox;
|
||||
|
||||
in vec3 texCoord;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_color = texture(skybox,texCoord);
|
||||
}
|
||||
|
||||
```
|
||||
> shader_skybox.frag
|
||||
|
||||
### Zadanie
|
||||
W modelach znajduje się `cube.obj`, załaduj go i narysuj shaderami `shader_skybox.vert` i `shader_skybox.vert`. Pamiętaj o przesłaniu macierzy transformacji i tekstury skyboxa. Aktywujemy ją za pomocą instrukcji:
|
||||
```C++
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
|
||||
```
|
||||
|
||||
Skybox potencjalnie zasłania pewne obiekty, które są umieszczone trochę dalej. Wynika to z tego, że renderowanie go nadpisuje bufor głębokości. Dlatego narysuj cubemapę z wyłączonym testem głębokości na samym początku. Następnie włącz test głębokości dla reszty sceny.
|
||||
Dezaktywację wykonasz za pomocą instrukcji `glDisable(GL_DEPTH_TEST);`, natomiast aktywację za pomocą`glEnable(GL_DEPTH_TEST);`.
|
||||
|
||||
Skybox reprezentuje obiekty, które są bardzo daleko. Tę iluzję możemy utracić, gdy kamera przysunie się zbyt blisko do skyboxa. Aby tego uniknąć, musimy tak umieścić skybox, by kamera zawsze była w jego środku. Przesuń skybox do pozycji kamery z użyciem macierzy translacji.
|
||||
|
150
grk/cw 6/Zadania 8.html
Normal file
@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<title>Zadania 8</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
ul.task-list{list-style: none;}
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="physically-based-rendering">Physically based rendering</h1>
|
||||
<p>Physically based rendering jest zbiorem technik renderowania, które bazują na teorii, która ma za zadanie odwzorowywać świat rzeczywisty.</p>
|
||||
<p>Podstawową dla obliczenia jest <em>rendering equation</em> następującej postaci.</p>
|
||||
<p><span class="math display">\[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\]</span> Całka przechodzi po wszystkich kierunkach na półkuli <span class="math inline">\(\Omega\)</span> oblicza wyjściowe oświetlenie <span class="math inline">\(L_O\)</span> bazując na wejściowym oświetleniu <span class="math inline">\(L_i\)</span> i funkcji <span class="math inline">\(f_r\)</span> znanej jako <strong>BRDF</strong>, czyli <em>bidirectional reflective distribution function</em>. W najbardziej ogólnym przypadku należałoby traktować <span class="math inline">\(L_i\)</span> 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.</p>
|
||||
<p>By uzyskać realistyczny efekt, potrzebujemy realistycznej funkcji <span class="math inline">\(f_r\)</span>. Wybierzemy <em>Cook-Torrance BRDF</em>, który jest najczęściej wykorzystywaną funkcją przy liczeniu PBR w czasie rzeczywistym.</p>
|
||||
<p><em>Cook-Torrance BRDF</em> rozdzielamy na światło rozproszone (diffuse) i odbite kierunkowo (specular)</p>
|
||||
<p><span class="math display">\[f_r=k_d f_{lambert}+k_s f_{cook−torrance},\]</span> gdzie <span class="math inline">\(k_d\)</span> i <span class="math inline">\(k_s\)</span> to są współczynniki, rozdzielające ile światła zostało rozproszone a ile odbite, ich suma musi być równa 1. komponent <span class="math inline">\(f_{lambert}\)</span> jest znany jako <em>Lambertian diffuse</em> i wynosi <span class="math display">\[f_{lambert}=\frac{c}{\pi}\]</span> przy czym <span class="math inline">\(c\)</span> to jest jest kolorem powierzchni a <span class="math inline">\(\pi\)</span> odpowiada za normalizację.</p>
|
||||
<p>Część odbicia kierunkowego jest bardziej skomplikowana i wygląda następująco:</p>
|
||||
<p><span class="math display">\[f_{cook−torrance}=\frac{DFG}{4(w_o\cdot n)(w_i\cdot n)}\]</span> gdzie <span class="math inline">\(D\)</span>, <span class="math inline">\(F\)</span> i <span class="math inline">\(G\)</span> są funkcjami, które zależą od normalnej, wektora wejściowego i wyjściowego oraz parametrów chropowatości <span class="math inline">\(\alpha\)</span> i odbicia <span class="math inline">\(F_0\)</span>. Te funkcje to odpowiednio: - <span class="math inline">\(D\)</span> - <strong>Normal distribution function</strong> przybliża ilość powierzchni, która jest ustawiona prostopadle do wektora połówkowego korzystamy z funkcji Trowbridge-Reitz GGX <span class="math display">\[D(n,h,\alpha) = \frac{\alpha^2}{π((n⋅h)^2(α^2−1)+1)^2},\]</span> gdzie <span class="math inline">\(h\)</span> to: <span class="math display">\[h = \frac{\omega_o+\omega_i}{||\omega_o+\omega_i||}\]</span> - G - <strong>Geometry function</strong> opisuje stopień samo-zacieninienia. Wykorzystujemy Schlick-GGX <span class="math display">\[G(n,\omega_o,k)=\frac{n⋅\omega_o}{(n⋅\omega_o)(1−k)+k}\]</span> przy czym <span class="math inline">\(k\)</span> wynosi <span class="math display">\[k=\frac{(\alpha+1)^2}{8}\]</span> - F - <strong>Fresnel equation</strong> opisuje stopień odbicia w zależności od kąta padania. Wykorzystujemy aproksymację Fresnela-Schlicka <span class="math display">\[F(h,v,F_0)=F_0+(1−F_0)(1−(h⋅v))^5\]</span> Parametr <span class="math inline">\(F_0\)</span> 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ę <strong>parametr metaliczności</strong>, który jest współczynnikiem, jakim miksujemy między (0.04,0.4,0.4) a kolorem, żeby uzyskać <span class="math inline">\(F_0\)</span>.</p>
|
||||
<h3 id="zadanie">Zadanie</h3>
|
||||
<p>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 <span class="math inline">\(\alpha\)</span> i metaliczności, jak na poniższym obrazku. <img src="./img/lighting_result.png" /></p>
|
||||
<h2 id="teksturowanie">Teksturowanie</h2>
|
||||
<p>Do uzyskania realistycznego efektu wykorzystuje się szereg tekstur:</p>
|
||||
<p><img src="./img/textures.png" /> Tekstury <strong>Albedo</strong> i <strong>Normalnych</strong> odpowiadają za kolor i normalne jak przy cieniowaniu Phonga. <strong>Metalic</strong> i <strong>Roughness</strong> przechowuje wartość metaliczności i chropowatości, które pojawiły się w BRDF. Natomiast <strong>AO</strong> (ambient occlision) odpowiada za stopień samo-zacienienia, który modyfikuje oświetlenie ambientowe w danym punkcie, uwzględnienie jej podkreśla detale obiektów.</p>
|
||||
<p>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 <strong>AO</strong> <strong>Roughness</strong> i <strong>Metalic</strong> 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.</p>
|
||||
<h3 id="zadanie-1">Zadanie</h3>
|
||||
<p>W <code>ex_8_2.hpp</code> 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 src="./img/textures.png" />W oparciu o tekstury. Wyświetl statek zestawem tekstur w folderze <code>textures/spaceshipPBR</code>. Możesz też wziąć swój układ słoneczny z poprzednich zajęć. Ładowanie tekstur jest męczące, napisz klasę/strukturę <code>PBRTexturesHandler</code>, która przechowuje zestaw tekstur <em>albedo</em>, <em>normal</em> i <em>arm</em>. Posiada konstruktor, który ładuje tekstury po ścieżkach oraz funkcję <code>activateTextures(int[] indices)</code>, która aktywuje tekstury z indeksami podanymi w tablicy. <code>int[] indices</code>.</p>
|
||||
<h2 id="pbr-multitexturing">PBR Multitexturing</h2>
|
||||
<p>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 src="./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 <a href="https://onlinelibrary.wiley.com/doi/10.1002/cav.1460">ta</a>. My wykorzystamy technikę opartą na mapach wysokości, opisaną w <a href="https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting">https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting</a>.</p>
|
||||
<blockquote>
|
||||
<p>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 <em>heightmap</em>, <em>displacement map</em> czy <em>bump map</em> w zależności od zastosowania. Przykładowe znajdują się w teksutrach.</p>
|
||||
</blockquote>
|
||||
<p>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 src="./img/2.png" /> Jako rezultat otrymujemy <img src="./img/3.webp" /></p>
|
||||
<p>Kod funkcji mieszającej:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>float3 blend(float3 texture1, <span class="dt">float</span> height1, float3 texture2, <span class="dt">float</span> height2, <span class="dt">float</span> blend_ratio)</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (height1>height2){</span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> texture1;</span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> }</span>
|
||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>{</span>
|
||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> texture2;</span>
|
||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> }</span>
|
||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>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 <code>blend_ratio</code>, który będzie z zakresu od 0 do 1. Na jednowymiarowym schemacie wartość wysokości wygląda to następująco. <img src="./img/4.png" /> Daje nam to to poniższy efekt <img src="./img/5.webp" /> Kod nowej funkcji mieszającej:</p>
|
||||
<div class="sourceCode" id="cb2"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>float3 blend(float3 texture1, <span class="dt">float</span> height1, float3 texture2, <span class="dt">float</span> height2, <span class="dt">float</span> blend_ratio)</span>
|
||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> h1=height1+<span class="fl">1.0</span>-blend_ratio;</span>
|
||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> h2=height2+blend_ratio;</span>
|
||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (h1>h2){</span>
|
||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> texture1;</span>
|
||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> }</span>
|
||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>{</span>
|
||||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> texture2;</span>
|
||||
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> }</span>
|
||||
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>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 src="./img/6.webp" /> Zdefiniujemy dodatkowy parametr <code>mix_threshold</code>, 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 <code>mix_threshold</code> to będziemy je mieszać proporcjonalnie do ich różnicy podzielonej przez <code>mix_threshold</code>, w przeciwnym wypadku wybierzemy tą, która jest wyżej. W celu optymalizacji zamiast optymalizacji korzystamy z funkcji clamp</p>
|
||||
<div class="sourceCode" id="cb3"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>float3 blend(float3 texture1, <span class="dt">float</span> height1, float3 texture2, <span class="dt">float</span> height2, <span class="dt">float</span> blend_ratio)</span>
|
||||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>{</span>
|
||||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> mix_threshold=<span class="fl">0.1</span>;</span>
|
||||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> h1=height1+<span class="fl">1.0</span>-blend_ratio;</span>
|
||||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> h2=height2+blend_ratio;</span>
|
||||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">float</span> havg=(h1+h2)/<span class="fl">2.</span>;</span>
|
||||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> </span>
|
||||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> h1 = clamp((h1-hav+<span class="fl">0.5</span>*mix_threshold)/(mix_threshold),<span class="fl">0.</span>,<span class="fl">1.</span>);</span>
|
||||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a> h2 = clamp((h2-hav+<span class="fl">0.5</span>*mix_threshold)/(mix_threshold),<span class="fl">0.</span>,<span class="fl">1.</span>);</span>
|
||||
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a> </span>
|
||||
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (texture1*h1+texture2*h2)</span>
|
||||
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
|
||||
<p>Stosunek w jakim je zmieszamy: <img src="./img/7.webp" /></p>
|
||||
<p>Finalna tekstura <img src="./img/8.webp" /></p>
|
||||
<h3 id="zadanie-2">Zadanie</h3>
|
||||
<p>W teksturach znajduje plik <code>heightmap.png</code> 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.</p>
|
||||
<h3 id="zadanie-3">Zadanie*</h3>
|
||||
<p>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 <a href="https://polyhaven.com/textures">https://polyhaven.com/textures</a> lub <a href="www.texturecan.com">www.texturecan.com</a>.</p>
|
||||
</body>
|
||||
</html>
|
125
grk/cw 6/Zadania 8.md
Normal file
@ -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.
|
||||

|
||||
|
||||
## 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](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.
|
||||

|
||||
Jako rezultat otrymujemy
|
||||

|
||||
|
||||
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.
|
||||

|
||||
Daje nam to to poniższy efekt
|
||||

|
||||
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.
|
||||

|
||||
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:
|
||||

|
||||
|
||||
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](https://polyhaven.com/textures)
|
||||
lub [www.texturecan.com](www.texturecan.com).
|
@ -11,13 +11,6 @@
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="imgui\imgui.cpp" />
|
||||
<ClCompile Include="imgui\imgui_demo.cpp" />
|
||||
<ClCompile Include="imgui\imgui_draw.cpp" />
|
||||
<ClCompile Include="imgui\imgui_impl_glfw.cpp" />
|
||||
<ClCompile Include="imgui\imgui_impl_opengl3.cpp" />
|
||||
<ClCompile Include="imgui\imgui_tables.cpp" />
|
||||
<ClCompile Include="imgui\imgui_widgets.cpp" />
|
||||
<ClCompile Include="src\Camera.cpp" />
|
||||
<ClCompile Include="src\main.cpp" />
|
||||
<ClCompile Include="src\Render_Utils.cpp" />
|
||||
@ -29,15 +22,6 @@
|
||||
<ClCompile Include="src\Texture.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="imgui\imconfig.h" />
|
||||
<ClInclude Include="imgui\imgui.h" />
|
||||
<ClInclude Include="imgui\imgui_impl_glfw.h" />
|
||||
<ClInclude Include="imgui\imgui_impl_opengl3.h" />
|
||||
<ClInclude Include="imgui\imgui_impl_opengl3_loader.h" />
|
||||
<ClInclude Include="imgui\imgui_internal.h" />
|
||||
<ClInclude Include="imgui\imstb_rectpack.h" />
|
||||
<ClInclude Include="imgui\imstb_textedit.h" />
|
||||
<ClInclude Include="imgui\imstb_truetype.h" />
|
||||
<ClInclude Include="src\Camera.h" />
|
||||
<ClInclude Include="src\Planet.hpp" />
|
||||
<ClInclude Include="src\objload.h" />
|
||||
@ -52,10 +36,6 @@
|
||||
<ClInclude Include="src\Texture.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="shaders\shaderBlur.frag" />
|
||||
<None Include="shaders\shaderBlur.vert" />
|
||||
<None Include="shaders\shader_bloom_final.frag" />
|
||||
<None Include="shaders\shader_bloom_final.vert" />
|
||||
<None Include="shaders\shader_pbr.frag" />
|
||||
<None Include="shaders\shader_pbr.vert" />
|
||||
<None Include="shaders\shader_skybox.frag" />
|
||||
@ -68,7 +48,7 @@
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{3952C396-B1C6-44CD-96DD-C1AC15D32978}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>grk-planet-editor</RootNamespace>
|
||||
<RootNamespace>grk-cw6</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>Planeta</ProjectName>
|
||||
</PropertyGroup>
|
||||
@ -115,9 +95,7 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>Default</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalHeaderUnitDependencies>
|
||||
</AdditionalHeaderUnitDependencies>
|
||||
<AdditionalIncludeDirectories>D:\STUDIA\Planeta\grk\cw 6\src\imgui-master;D:\STUDIA\Planeta\grk\cw 6\src\imgui-master\backends;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
@ -136,6 +114,7 @@
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp14</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>D:\STUDIA\Planeta\grk\cw 6\src\imgui-master;D:\STUDIA\Planeta\grk\cw 6\src\imgui-master\backends;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
@ -19,12 +19,6 @@
|
||||
<Filter Include="Source Files\SOIL">
|
||||
<UniqueIdentifier>{0af44075-33f4-4953-b1d6-1d28d61d758f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\imgui">
|
||||
<UniqueIdentifier>{89bb425a-a179-4919-bea5-bb8b2e3b0156}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\imgui">
|
||||
<UniqueIdentifier>{f790849c-60c5-48d9-9e1a-f3ae462f4f36}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\Render_Utils.cpp">
|
||||
@ -54,27 +48,6 @@
|
||||
<ClCompile Include="src\SOIL\image_helper.c">
|
||||
<Filter>Source Files\SOIL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_demo.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_draw.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_impl_glfw.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_impl_opengl3.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_tables.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="imgui\imgui_widgets.cpp">
|
||||
<Filter>Source Files\imgui</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\objload.h">
|
||||
@ -113,33 +86,6 @@
|
||||
<ClInclude Include="src\Planet.hpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imconfig.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imgui.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imgui_impl_glfw.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imgui_impl_opengl3.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imgui_impl_opengl3_loader.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imgui_internal.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imstb_rectpack.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imstb_textedit.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="imgui\imstb_truetype.h">
|
||||
<Filter>Header Files\imgui</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="shaders\shader_sun.frag">
|
||||
@ -154,6 +100,18 @@
|
||||
<None Include="shaders\shader_tex.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\test.vert">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\test.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shader_smap.vert">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shader_noise.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shader_skybox.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
@ -166,17 +124,5 @@
|
||||
<None Include="shaders\shader_pbr.vert">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shader_bloom_final.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shader_bloom_final.vert">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shaderBlur.frag">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
<None Include="shaders\shaderBlur.vert">
|
||||
<Filter>Shader Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
BIN
grk/cw 6/img/2.png
Normal file
After ![]() (image error) Size: 6.7 KiB |
BIN
grk/cw 6/img/3.webp
Normal file
After ![]() (image error) Size: 35 KiB |
BIN
grk/cw 6/img/384px-PerlinNoiseDotProducts.png
Normal file
After ![]() (image error) Size: 62 KiB |
BIN
grk/cw 6/img/384px-PerlinNoiseInterpolated.png
Normal file
After ![]() (image error) Size: 54 KiB |
BIN
grk/cw 6/img/4.png
Normal file
After ![]() (image error) Size: 7.0 KiB |
BIN
grk/cw 6/img/5.webp
Normal file
After ![]() (image error) Size: 35 KiB |
BIN
grk/cw 6/img/6.webp
Normal file
After ![]() (image error) Size: 3.9 KiB |
BIN
grk/cw 6/img/7.webp
Normal file
After ![]() (image error) Size: 2.9 KiB |
BIN
grk/cw 6/img/8.webp
Normal file
After ![]() (image error) Size: 34 KiB |
1254
grk/cw 6/img/Figure_1.eps
Normal file
BIN
grk/cw 6/img/PerlinNoiseDotProducts.png
Normal file
After ![]() (image error) Size: 178 KiB |
BIN
grk/cw 6/img/PerlinNoiseGradientGrid.png
Normal file
After ![]() (image error) Size: 41 KiB |
BIN
grk/cw 6/img/PerlinNoiseInterpolated.png
Normal file
After ![]() (image error) Size: 144 KiB |
BIN
grk/cw 6/img/blending1.webp
Normal file
After ![]() (image error) Size: 32 KiB |
BIN
grk/cw 6/img/camera.jpg
Normal file
After ![]() (image error) Size: 17 KiB |
BIN
grk/cw 6/img/capture.webm
Normal file
BIN
grk/cw 6/img/coordinate_systems.jpg
Normal file
After ![]() (image error) Size: 79 KiB |
BIN
grk/cw 6/img/cubemaps_sampling.png
Normal file
After ![]() (image error) Size: 27 KiB |
BIN
grk/cw 6/img/gamma_correction_brightness.png
Normal file
After ![]() (image error) Size: 44 KiB |
BIN
grk/cw 6/img/lighting_result.png
Normal file
After ![]() (image error) Size: 190 KiB |
BIN
grk/cw 6/img/noise_examples.jpg
Normal file
After ![]() (image error) Size: 329 KiB |
BIN
grk/cw 6/img/shadow_mapping_projection.png
Normal file
After ![]() (image error) Size: 9.8 KiB |
BIN
grk/cw 6/img/shadows1.bmp
Normal file
After ![]() (image error) Size: 978 KiB |
BIN
grk/cw 6/img/spotlight.png
Normal file
After ![]() (image error) Size: 16 KiB |
BIN
grk/cw 6/img/stride_offest.jpg
Normal file
After ![]() (image error) Size: 34 KiB |
BIN
grk/cw 6/img/stride_offest_2_2.jpg
Normal file
After ![]() (image error) Size: 36 KiB |
BIN
grk/cw 6/img/struktura.jpg
Normal file
After ![]() (image error) Size: 16 KiB |
BIN
grk/cw 6/img/sun.png
Normal file
After ![]() (image error) Size: 2.0 MiB |
BIN
grk/cw 6/img/textures.png
Normal file
After ![]() (image error) Size: 260 KiB |
BIN
grk/cw 6/img/z_depth_graph.jpg
Normal file
After ![]() (image error) Size: 80 KiB |
BIN
grk/cw 6/img/z_depth_graph2.jpg
Normal file
After ![]() (image error) Size: 72 KiB |
@ -1,4 +1,4 @@
|
||||
#version 430 core
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
in vec2 TexCoords;
|
||||
@ -23,7 +23,7 @@ void main()
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 1; i < count; ++i)
|
||||
for(int i = 1; i < 5; ++i)
|
||||
{
|
||||
result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
|
||||
result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
|
@ -1,4 +1,4 @@
|
||||
#version 430 core
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec2 aTexCoords;
|
||||
|
@ -1,4 +1,4 @@
|
||||
#version 430 core
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
in vec2 TexCoords;
|
||||
@ -10,16 +10,14 @@ uniform float exposure;
|
||||
|
||||
void main()
|
||||
{
|
||||
const float gamma = 1.5;
|
||||
const float gamma = 2.6;
|
||||
vec3 hdrColor = texture(scene, TexCoords).rgb;
|
||||
vec3 bloomColor = texture(bloomBlur, TexCoords).rgb;
|
||||
if(bloom)
|
||||
hdrColor += bloomColor; // additive blending
|
||||
|
||||
// tone mapping
|
||||
vec3 result = vec3(1.0) - exp(-hdrColor * exposure);
|
||||
|
||||
// gamma correction
|
||||
// also gamma correct while we're at it
|
||||
result = pow(result, vec3(1.0 / gamma));
|
||||
FragColor = vec4(result, 1.0);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
#version 430 core
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec2 aTexCoords;
|
||||
|
@ -7,7 +7,6 @@ float AMBIENT = 0.05;
|
||||
float PI = 3.14159;
|
||||
|
||||
uniform sampler2D colorTexture;
|
||||
uniform sampler2D textureNormal;
|
||||
|
||||
uniform float exposition;
|
||||
uniform float metallic;
|
||||
@ -22,18 +21,19 @@ uniform vec3 lightPos;
|
||||
uniform vec3 lightColor;
|
||||
|
||||
uniform bool atmosphereCheck;
|
||||
uniform bool toneMappingCheck;
|
||||
|
||||
uniform float time;
|
||||
|
||||
uniform float cloudIntensity;
|
||||
uniform float cloudMotion;
|
||||
uniform float cloudBrightness;
|
||||
uniform float u_time;
|
||||
uniform float cloudLight;
|
||||
uniform float cloudIntensity;
|
||||
uniform float cloudSpeed;
|
||||
uniform float cloudLightness;
|
||||
|
||||
in vec3 vecNormal;
|
||||
in vec3 worldPos;
|
||||
in vec2 vtc;
|
||||
|
||||
//out vec4 outColor;
|
||||
|
||||
in vec3 viewDirTS;
|
||||
in vec3 lightDirTS;
|
||||
in vec3 sunDirTS;
|
||||
@ -112,18 +112,17 @@ vec3 toneMapping(vec3 color)
|
||||
return mapped;
|
||||
}
|
||||
|
||||
float random (in vec2 st)
|
||||
{
|
||||
float random (in vec2 st) {
|
||||
return fract(sin(dot(st.xy,
|
||||
vec2(32.9898,128.233)))*
|
||||
vec2(12.9898,78.233)))*
|
||||
43758.5453123);
|
||||
}
|
||||
|
||||
float noise (in vec2 st)
|
||||
{
|
||||
float noise (in vec2 st) {
|
||||
vec2 i = floor(st);
|
||||
vec2 f = fract(st);
|
||||
|
||||
// Four corners in 2D of a tile
|
||||
float a = random(i);
|
||||
float b = random(i + vec2(1.0, 0.0));
|
||||
float c = random(i + vec2(0.0, 1.0));
|
||||
@ -137,13 +136,12 @@ float noise (in vec2 st)
|
||||
}
|
||||
|
||||
#define OCTAVES 6
|
||||
float fbm (in vec2 st)
|
||||
{
|
||||
float fbm (in vec2 st) {
|
||||
// Initial values
|
||||
float value = 0.0;
|
||||
float amplitude = .5;
|
||||
float frequency = 0.;
|
||||
|
||||
//
|
||||
// Loop of octaves
|
||||
for (int i = 0; i < OCTAVES; i++) {
|
||||
value += amplitude * noise(st);
|
||||
@ -153,12 +151,11 @@ float fbm (in vec2 st)
|
||||
return value;
|
||||
}
|
||||
|
||||
vec3 noiseColor()
|
||||
{
|
||||
vec4 noiseColor() {
|
||||
vec2 st = vtc.xy;
|
||||
vec2 mirroredSt = vec2(1.0 - st.x, st.y);
|
||||
|
||||
float timeOffset = time * cloudMotion;
|
||||
float timeOffset = u_time * cloudSpeed;
|
||||
st.x -= timeOffset;
|
||||
mirroredSt.x += timeOffset; // Inverse direction for mirrored effect
|
||||
|
||||
@ -170,69 +167,54 @@ vec3 noiseColor()
|
||||
|
||||
float blend = smoothstep(0.45, 0.55, st.x);
|
||||
|
||||
vec3 noiseColor = mix(color, mirroredColor, blend);
|
||||
vec3 finalColor = mix(color, mirroredColor, blend);
|
||||
|
||||
vec4 noiseColor = vec4(finalColor, 1.0);
|
||||
|
||||
return noiseColor;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
void main() {
|
||||
vec3 normal = normalize(vecNormal);
|
||||
vec3 viewDir = normalize(cameraPos - worldPos);
|
||||
vec3 lightDir = normalize(lightPos - worldPos);
|
||||
|
||||
vec3 textureColor = texture2D(colorTexture, vtc).rgb;
|
||||
vec3 N = texture2D(textureNormal, vtc).rgb;
|
||||
vec4 textureColor = texture2D(colorTexture, vtc);
|
||||
|
||||
vec3 normalTexture = normalize((N * 2.0 - 1.0));
|
||||
|
||||
float diffuseNormal = max(0, dot(normalTexture, lightDir));
|
||||
float atmosphereDot = dot(normal, viewDir);
|
||||
vec3 atmosphereColor = vec3(0.04, 0.2, 1.0);
|
||||
|
||||
if (atmosphereCheck)
|
||||
{
|
||||
float atmosphereDot = dot(normal, viewDir);
|
||||
vec3 atmosphereColor = vec3(0.0, 0.44, 1.0);
|
||||
textureColor = mix(textureColor, atmosphereColor, pow(1 - atmosphereDot, 3));
|
||||
}
|
||||
textureColor = mix(textureColor, vec4(atmosphereColor / atmosphereDot, 1.0), 0.1);
|
||||
|
||||
vec3 diffuseColor = textureColor * min(1, AMBIENT + diffuseNormal);
|
||||
vec3 toneMappedColor;
|
||||
|
||||
if (toneMappingCheck)
|
||||
{
|
||||
vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 10;
|
||||
toneMappedColor = toneMapping(diffuseColor * distance);
|
||||
//gamma correction
|
||||
toneMappedColor = pow(toneMappedColor, vec3(1.0/2.2));
|
||||
}
|
||||
else
|
||||
toneMappedColor = textureColor;
|
||||
float diffuse = max(0, dot(normal, lightDir));
|
||||
vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 10;
|
||||
vec3 toneMappedColor = toneMapping(vec3(textureColor) * 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 = ambient + PBRLight(lightDir, attenuatedLightColor, normal, viewDir, toneMappedColor);
|
||||
vec3 illumination;
|
||||
illumination = ambient + PBRLight(lightDir, attenuatedLightColor, normal, viewDir, toneMappedColor);
|
||||
|
||||
//sun
|
||||
illumination = illumination + PBRLight(sunDir, sunColor, normal, viewDir, toneMappedColor);
|
||||
|
||||
|
||||
|
||||
vec3 pbrColor = vec3(1.0) - exp(-illumination * exposition);
|
||||
|
||||
vec3 finalColor;
|
||||
vec4 noiseColor = noiseColor() * min(1, AMBIENT + cloudLightness * diffuse) * vec4(lightColor, 0.0) / cloudLight;
|
||||
|
||||
if (atmosphereCheck)
|
||||
{
|
||||
vec3 noiseColor = noiseColor() * min(1, 20.0 * max(0.02, dot(normal, lightDir))) * lightColor * cloudBrightness;
|
||||
finalColor = mix(pbrColor, noiseColor, noiseColor.r);
|
||||
}
|
||||
vec3 mixedColor = mix(pbrColor.rgb, noiseColor.rgb, noiseColor.r);
|
||||
|
||||
|
||||
float brightness = dot(mixedColor, vec3(0.2126, 0.7152, 0.0722));
|
||||
if(brightness > 1.5)
|
||||
BrightColor = vec4(mixedColor, 1.0);
|
||||
else
|
||||
finalColor = pbrColor;
|
||||
|
||||
float brightness = dot(finalColor, vec3(0.2126, 0.7152, 0.0722));
|
||||
|
||||
if(brightness > 0.2)
|
||||
BrightColor = vec4(finalColor, 1.0);
|
||||
else
|
||||
BrightColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
BrightColor = vec4(0.5, 0.5, 0.5, 1.0);
|
||||
|
||||
outColor = vec4(finalColor, 1.0);
|
||||
outColor = vec4(mixedColor, 1.0);
|
||||
}
|
14
grk/cw 6/shaders/shader_skybox.frag
Normal file
@ -0,0 +1,14 @@
|
||||
#version 430 core
|
||||
|
||||
uniform samplerCube skybox;
|
||||
uniform vec3 lightColor;
|
||||
|
||||
in vec3 texCoord;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 textureColor = texture(skybox, texCoord);
|
||||
out_color = vec4(vec3(textureColor) * lightColor * 0.35f, 1.0f);
|
||||
}
|
44
grk/cw 6/shaders/shader_sun.frag
Normal file
@ -0,0 +1,44 @@
|
||||
#version 430 core
|
||||
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 lightColor;
|
||||
|
||||
uniform vec3 cameraPos;
|
||||
|
||||
uniform bool atmosphereCheck;
|
||||
|
||||
in vec3 vecNormal;
|
||||
in vec3 worldPos;
|
||||
in vec2 vtc;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
uniform sampler2D colorTexture;
|
||||
|
||||
vec3 toneMapping(vec3 color)
|
||||
{
|
||||
float exposure = 0.06;
|
||||
vec3 mapped = 1 - exp(-color * exposure);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 normal = normalize(vecNormal);
|
||||
vec3 viewDir = normalize(cameraPos - worldPos);
|
||||
|
||||
vec4 textureColor = texture2D(colorTexture, vtc);
|
||||
|
||||
float atmosphereDot = dot(normal, viewDir);
|
||||
vec3 atmosphereColor = vec3(1.0, 0.04, 0.01);
|
||||
|
||||
if (atmosphereCheck)
|
||||
textureColor = mix(textureColor, vec4(atmosphereColor / atmosphereDot, 1.0), 0.25);
|
||||
|
||||
vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 25;
|
||||
vec3 toneMappedColor = toneMapping(vec3(textureColor) * distance);
|
||||
//gamma correction
|
||||
toneMappedColor = pow(toneMappedColor, vec3(1.0/2.2));
|
||||
|
||||
outColor = vec4(toneMappedColor * lightColor * 0.2f, 1.0);
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
#version 430 core
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
layout (location = 1) out vec4 BrightColor;
|
||||
|
||||
float AMBIENT = 0.05;
|
||||
float PI = 3.14159;
|
||||
|
||||
uniform sampler2D colorTexture;
|
||||
uniform sampler2D textureNormal;
|
||||
|
||||
uniform vec3 color;
|
||||
uniform vec3 lightPos;
|
||||
@ -16,21 +12,18 @@ uniform vec3 lightColor;
|
||||
uniform vec3 cameraPos;
|
||||
|
||||
uniform bool atmosphereCheck;
|
||||
uniform bool toneMappingCheck;
|
||||
|
||||
uniform float time;
|
||||
|
||||
uniform float cloudIntensity;
|
||||
uniform float cloudMotion;
|
||||
uniform float cloudBrightness;
|
||||
uniform float u_time;
|
||||
uniform float cloudLight;
|
||||
uniform float cloudIntensity;
|
||||
uniform float cloudSpeed;
|
||||
uniform float cloudLightness;
|
||||
|
||||
in vec3 vecNormal;
|
||||
in vec3 worldPos;
|
||||
in vec2 vtc;
|
||||
|
||||
in vec3 viewDirTS;
|
||||
in vec3 lightDirTS;
|
||||
in vec3 sunDirTS;
|
||||
out vec4 outColor;
|
||||
|
||||
vec3 toneMapping(vec3 color)
|
||||
{
|
||||
@ -68,7 +61,7 @@ float fbm (in vec2 st) {
|
||||
float value = 0.0;
|
||||
float amplitude = .5;
|
||||
float frequency = 0.;
|
||||
|
||||
//
|
||||
// Loop of octaves
|
||||
for (int i = 0; i < OCTAVES; i++) {
|
||||
value += amplitude * noise(st);
|
||||
@ -78,11 +71,11 @@ float fbm (in vec2 st) {
|
||||
return value;
|
||||
}
|
||||
|
||||
vec3 noiseColor() {
|
||||
vec4 noiseColor() {
|
||||
vec2 st = vtc.xy;
|
||||
vec2 mirroredSt = vec2(1.0 - st.x, st.y);
|
||||
|
||||
float timeOffset = time * cloudMotion;
|
||||
float timeOffset = u_time * cloudSpeed;
|
||||
st.x -= timeOffset;
|
||||
mirroredSt.x += timeOffset; // Inverse direction for mirrored effect
|
||||
|
||||
@ -94,7 +87,9 @@ vec3 noiseColor() {
|
||||
|
||||
float blend = smoothstep(0.45, 0.55, st.x);
|
||||
|
||||
vec3 noiseColor = mix(color, mirroredColor, blend);
|
||||
vec3 finalColor = mix(color, mirroredColor, blend);
|
||||
|
||||
vec4 noiseColor = vec4(finalColor, 1.0);
|
||||
|
||||
return noiseColor;
|
||||
}
|
||||
@ -107,52 +102,22 @@ void main()
|
||||
|
||||
float diffuse = max(0, dot(normal, lightDir));
|
||||
|
||||
vec3 textureColor = texture2D(colorTexture, vtc).rgb;
|
||||
vec3 N = texture2D(textureNormal, vtc).rgb;
|
||||
vec4 textureColor = texture2D(colorTexture, vtc);
|
||||
|
||||
vec3 normalTexture = normalize((N * 2.0 - 1.0));
|
||||
|
||||
float diffuseNormal = max(0, dot(normalTexture, lightDir));
|
||||
float atmosphereDot = dot(normal, viewDir);
|
||||
vec3 atmosphereColor = vec3(0.04, 0.2, 1.0);
|
||||
|
||||
if (atmosphereCheck)
|
||||
{
|
||||
float atmosphereDot = dot(normal, viewDir);
|
||||
vec3 atmosphereColor = vec3(0.0, 0.44, 1.0);
|
||||
textureColor = mix(textureColor, atmosphereColor, pow(1 - atmosphereDot, 3));
|
||||
}
|
||||
textureColor = mix(textureColor, vec4(atmosphereColor / atmosphereDot, 1.0), 0.05);
|
||||
|
||||
vec3 diffuseColor = textureColor * min(1, AMBIENT + diffuse) * min(1, AMBIENT + diffuseNormal) * lightColor;
|
||||
vec3 toneMappedColor;
|
||||
vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 10000;
|
||||
vec3 toneMappedColor = toneMapping(vec3(textureColor) * min(1, AMBIENT + diffuse) * distance);
|
||||
//gamma correction
|
||||
//toneMappedColor = pow(toneMappedColor, vec3(1.0/2.2));
|
||||
|
||||
if (toneMappingCheck)
|
||||
{
|
||||
vec3 distance = lightColor / pow(length(lightPos - worldPos), 2.0) * 1000;
|
||||
toneMappedColor = toneMapping(diffuseColor * distance);
|
||||
vec4 noiseColor = noiseColor() * min(1, AMBIENT + cloudLightness) * vec4(lightColor, 0.0) / cloudLight;
|
||||
|
||||
//gamma correction
|
||||
toneMappedColor = pow(toneMappedColor, vec3(1.0/2.2));
|
||||
}
|
||||
else
|
||||
toneMappedColor = diffuseColor;
|
||||
|
||||
vec3 finalColor;
|
||||
|
||||
if (atmosphereCheck)
|
||||
{
|
||||
vec3 noiseColor = noiseColor() * min(1, 20.0 * max(0.02, dot(normal, lightDir))) * lightColor * cloudBrightness;
|
||||
finalColor = mix(toneMappedColor, noiseColor, noiseColor.r);
|
||||
}
|
||||
else
|
||||
finalColor = toneMappedColor;
|
||||
|
||||
float brightness = dot(finalColor, vec3(0.2126, 0.7152, 0.0722));
|
||||
|
||||
if(brightness > 0.2)
|
||||
BrightColor = vec4(finalColor, 1.0);
|
||||
else
|
||||
BrightColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
|
||||
outColor = vec4(finalColor, 1.0);
|
||||
vec3 mixedColor = mix(toneMappedColor.rgb, noiseColor.rgb, noiseColor.r);
|
||||
|
||||
outColor = vec4(mixedColor, 1.0);
|
||||
}
|
21
grk/cw 6/shaders/shader_tex.vert
Normal file
@ -0,0 +1,21 @@
|
||||
#version 430 core
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec3 vertexNormal;
|
||||
layout(location = 2) in vec2 vertexTexCoord;
|
||||
|
||||
uniform mat4 transformation;
|
||||
uniform mat4 modelMatrix;
|
||||
|
||||
out vec3 vecNormal;
|
||||
out vec3 worldPos;
|
||||
out vec2 vtc;
|
||||
|
||||
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);
|
||||
}
|
@ -16,79 +16,49 @@
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h> // intptr_t
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
GLuint programTex;
|
||||
GLuint programPbr;
|
||||
GLuint programSun;
|
||||
GLuint programSkyBox;
|
||||
GLuint programBlur;
|
||||
GLuint programBloomFinal;
|
||||
GLuint programBlur;
|
||||
|
||||
Core::Shader_Loader shaderLoader;
|
||||
|
||||
Core::RenderContext sphereContext;
|
||||
Core::RenderContext cubeContext;
|
||||
|
||||
const char* const planetTexPaths[] = { "./textures/planets/ceres.jpg", "./textures/planets/desert.png", "./textures/planets/dirty.png", "./textures/planets/earth.jpg",
|
||||
"./textures/planets/eris.jpg", "./textures/planets/haumea.jpg", "./textures/planets/Icy.png", "./textures/planets/jupiter.jpg", "./textures/planets/kora.png",
|
||||
"./textures/planets/makemake.jpg", "./textures/planets/mars.jpg", "./textures/planets/mchowa.png", "./textures/planets/mercury.png", "./textures/planets/saturn.jpg",
|
||||
"./textures/planets/Savannah.png", "./textures/planets/snow-foot.png", "./textures/planets/Swamp.png", "./textures/planets/Tropical.png", "./textures/planets/uranus.jpg",
|
||||
"./textures/planets/venus.jpg", "./textures/planets/Volcanic.png", "./textures/planets/watermelon.png", "./textures/planets/wood.png" };
|
||||
int planetTexIndex = 0;
|
||||
const char* const planetTexPaths[] = { "./textures/planets/earth.png", "./textures/planets/mercury.png", "./textures/planets/venus.jpg", "./textures/planets/mars.jpg",
|
||||
"./textures/planets/jupiter.jpg", "./textures/planets/saturn.jpg", "./textures/planets/uranus.jpg", "./textures/planets/neptune.jpg", "./textures/planets/icy.png",
|
||||
"./textures/planets/volcanic.png", "./textures/planets/desert.png", "./textures/planets/tropical.png", "./textures/planets/toxic.jpg", "./textures/planets/swamp.png",
|
||||
"./textures/planets/savannah.png", "./textures/planets/alpine.png", "./textures/planets/ceres.jpg", "./textures/planets/eris.jpg", "./textures/planets/haumea.jpg",
|
||||
"./textures/planets/makemake.jpg" };
|
||||
int planetTexIndex = 80;
|
||||
GLuint planetTex;
|
||||
|
||||
const char* const normalTexPaths[] = { "./textures/planets/normalne/ceres.png", "./textures/planets/normalne/desert.png", "./textures/planets/normalne/dirty.png", "./textures/planets/normalne/earth_normalmap.png",
|
||||
"./textures/planets/normalne/eris.png", "./textures/planets/normalne/haumea.png", "./textures/planets/normalne/icy.png", "./textures/planets/normalne/jupiter.png", "./textures/planets/normalne/kora.png",
|
||||
"./textures/planets/normalne/makemake.png", "./textures/planets/normalne/mars.png", "./textures/planets/normalne/mchowa.png", "./textures/planets/normalne/mercury.png", "./textures/planets/normalne/saturn.png",
|
||||
"./textures/planets/normalne/savannah.png", "./textures/planets/normalne/snow-foot.png", "./textures/planets/normalne/swamp.png", "./textures/planets/normalne/tropical.png", "./textures/planets/normalne/uranus.png",
|
||||
"./textures/planets/normalne/venus.png", "./textures/planets/normalne/volcanic.png", "./textures/planets/normalne/watermelon.png", "./textures/planets/normalne/wood.png" };
|
||||
GLuint normalTex;
|
||||
|
||||
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/lava.png", "./textures/suns/sol.jpg", "./textures/suns/orange.jpg", "./textures/suns/star.png", "./textures/suns/sun.jpg" };
|
||||
int sunTexIndex = 0;
|
||||
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;
|
||||
|
||||
bool glowCheck = false;
|
||||
bool atmosphereCheck = false;
|
||||
bool lightingCheck = false;
|
||||
bool skyBoxCheck = false;
|
||||
bool toneMappingCheck = false;
|
||||
|
||||
const char* skyBoxPaths0[] = { "./textures/skybox/bkg1_right.png", "./textures/skybox/bkg1_left.png", "./textures/skybox/bkg1_top.png", "./textures/skybox/bkg1_bot.png",
|
||||
"./textures/skybox/bkg1_front.png", "./textures/skybox/bkg1_back.png" };
|
||||
const char* skyBoxPaths1[] = { "./textures/skybox/right.png", "./textures/skybox/left.png", "./textures/skybox/top.png", "./textures/skybox/bot.png",
|
||||
"./textures/skybox/front.png", "./textures/skybox/back.png" };
|
||||
const char* skyBoxPaths2[] = { "./textures/skybox/bkg2_right1.png", "./textures/skybox/bkg2_left2.png", "./textures/skybox/bkg2_top3.png", "./textures/skybox/bkg2_bottom4.png",
|
||||
"./textures/skybox/bkg2_front5.png", "./textures/skybox/bkg2_back6.png" };
|
||||
const char* skyBoxPaths3[] = { "./textures/skybox/space_rt.png", "./textures/skybox/space_lf.png", "./textures/skybox/space_up.png", "./textures/skybox/space_dn.png",
|
||||
const char* skyBoxPaths[] = { "./textures/skybox/space_rt.png", "./textures/skybox/space_lf.png", "./textures/skybox/space_up.png", "./textures/skybox/space_dn.png",
|
||||
"./textures/skybox/space_bk.png", "./textures/skybox/space_ft.png" };
|
||||
const char** skyBoxArrays[] = { skyBoxPaths0, skyBoxPaths1, skyBoxPaths2, skyBoxPaths3 };
|
||||
int skyBoxIndex = 0;
|
||||
GLuint skyBoxTex;
|
||||
|
||||
glm::vec3 skyBoxPos = glm::vec3(0.f, 0.f, 0.f);
|
||||
float skyBoxSize = 3.f;
|
||||
float skyBoxRot = 0.f;
|
||||
|
||||
glm::vec3 cameraPos = glm::vec3(-2.f, 0.f, 0.f);
|
||||
glm::vec3 cameraDir = glm::vec3(1.f, 0.f, 0.f);
|
||||
@ -102,6 +72,7 @@ unsigned int colorBuffers[2];
|
||||
|
||||
unsigned int rboDepth;
|
||||
|
||||
|
||||
unsigned int pingpongFBO[2];
|
||||
unsigned int pingpongColorbuffers[2];
|
||||
|
||||
@ -109,17 +80,16 @@ int HDR_WIDTH;
|
||||
int HDR_HEIGHT;
|
||||
|
||||
bool bloom = true;
|
||||
float bloom_exposure = 0.5f;
|
||||
float bloom_exposure = 1.f;
|
||||
int blur_count = 1;
|
||||
|
||||
float lightPower = 8.f;
|
||||
glm::vec3 lightColor = glm::vec3(lightPower, lightPower, lightPower);
|
||||
|
||||
float cloudLight = 20.f;
|
||||
float cloudIntensity = 15.f;
|
||||
float cloudMotion = 0.f;
|
||||
float cloudBrightness = 0.08f;
|
||||
|
||||
float PI = 3.14159f;
|
||||
float cloudSpeed = 0.07f;
|
||||
float cloudLightness = 30.f;
|
||||
|
||||
glm::mat4 createCameraMatrix() {
|
||||
glm::vec3 cameraSide = glm::normalize(glm::cross(cameraDir, glm::vec3(0.f, 1.f, 0.f)));
|
||||
@ -144,6 +114,7 @@ glm::mat4 createPerspectiveMatrix() {
|
||||
float n = 0.01f;
|
||||
float f = 200.f;
|
||||
float fov = 105.f;
|
||||
float PI = 3.14159265359f;
|
||||
float S = 1 / (tan((fov / 2) * (PI / 180)));
|
||||
|
||||
perspectiveMatrix = glm::mat4({
|
||||
@ -187,121 +158,6 @@ void renderQuad()
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void showGUI() {
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (ImGui::Begin("General"))
|
||||
{
|
||||
ImGui::Checkbox("Sky Box", &skyBoxCheck);
|
||||
|
||||
if (skyBoxCheck) {
|
||||
ImGui::Text("Texture");
|
||||
if (ImGui::Button("Prev", ImVec2(60, 20))) {
|
||||
--skyBoxIndex;
|
||||
|
||||
if (skyBoxIndex < 0)
|
||||
skyBoxIndex = sizeof(skyBoxArrays) / sizeof(skyBoxArrays[0]) - 1;
|
||||
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxArrays[skyBoxIndex]);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Next", ImVec2(60, 20))) {
|
||||
++skyBoxIndex;
|
||||
|
||||
if (skyBoxIndex > sizeof(skyBoxArrays) / sizeof(skyBoxArrays[0]) - 1)
|
||||
skyBoxIndex = 0;
|
||||
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxArrays[skyBoxIndex]);
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("Rotation", &skyBoxRot, 0.0f, 360.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Tone Mapping", &toneMappingCheck);
|
||||
ImGui::SliderInt("Bloom Level", &blur_count, 1, 5, "%d", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::Checkbox("Lighting Model: ", &lightingCheck);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (lightingCheck) {
|
||||
ImGui::Text("PBR");
|
||||
ImGui::SliderFloat("Roughness", &planetRough, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SliderFloat("Metallicity", &planetMetal, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
}
|
||||
else
|
||||
ImGui::Text("Diffuse");
|
||||
|
||||
ImGui::SliderFloat("Exposure", &lightPower, 1.0f, 20.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
}ImGui::End();
|
||||
|
||||
if(ImGui::Begin("Planet"))
|
||||
{
|
||||
ImGui::Text("Texture");
|
||||
if (ImGui::Button("Prev", ImVec2(60, 20))) {
|
||||
--planetTexIndex;
|
||||
|
||||
if (planetTexIndex < 0)
|
||||
planetTexIndex = sizeof(planetTexPaths) / sizeof(planetTexPaths[0]) - 1;
|
||||
|
||||
planetTex = Core::LoadTexture(planetTexPaths[planetTexIndex]);
|
||||
normalTex = Core::LoadTexture(normalTexPaths[planetTexIndex]);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Next", ImVec2(60, 20))) {
|
||||
++planetTexIndex;
|
||||
|
||||
if (planetTexIndex > sizeof(planetTexPaths) / sizeof(planetTexPaths[0]) - 1)
|
||||
planetTexIndex = 0;
|
||||
|
||||
planetTex = Core::LoadTexture(planetTexPaths[planetTexIndex]);
|
||||
normalTex = Core::LoadTexture(normalTexPaths[planetTexIndex]);
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("Size", &planetSize, 0.001f, 0.01f, "%.5f", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SliderFloat("Rotation", &planetRot, -2.0f, 2.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
ImGui::Checkbox("Atmosphere", &atmosphereCheck);
|
||||
if (atmosphereCheck) {
|
||||
ImGui::Text("Clouds");
|
||||
ImGui::SliderFloat("Intensity", &cloudIntensity, 0.0f, 100.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SliderFloat("Brightness", &cloudBrightness, 0.0f, 0.20f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SliderFloat("Motion", &cloudMotion, -0.5f, 0.5f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
}
|
||||
}ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sun"))
|
||||
{
|
||||
ImGui::Text("Texture");
|
||||
if (ImGui::Button("Prev", ImVec2(60, 20))) {
|
||||
--sunTexIndex;
|
||||
|
||||
if (sunTexIndex < 0)
|
||||
sunTexIndex = sizeof(sunTexPaths) / sizeof(sunTexPaths[0]) - 1;
|
||||
|
||||
sunTex = Core::LoadTexture(sunTexPaths[sunTexIndex]);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Next", ImVec2(60, 20))) {
|
||||
++sunTexIndex;
|
||||
|
||||
if (sunTexIndex > sizeof(sunTexPaths) / sizeof(sunTexPaths[0]) - 1)
|
||||
sunTexIndex = 0;
|
||||
|
||||
sunTex = Core::LoadTexture(sunTexPaths[sunTexIndex]);
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("Size", &sunSize, 0.01f, 0.1f, "%.4f", ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
ImGui::Checkbox("Glow", &glowCheck);
|
||||
|
||||
}ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
void initHDR(GLFWwindow* window) {
|
||||
glfwGetWindowSize(window, &HDR_WIDTH, &HDR_HEIGHT);
|
||||
glGenFramebuffers(1, &hdrFBO);
|
||||
@ -366,11 +222,9 @@ void shaderBloomConfig() {
|
||||
glUniform1i(glGetUniformLocation(programBloomFinal, "bloomBlur"), 1);
|
||||
}
|
||||
|
||||
void drawPlanetTex(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture, GLuint textureNormal) {
|
||||
void drawPlanetTex(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture) {
|
||||
glUseProgram(programTex);
|
||||
Core::SetActiveTexture(texture, "colorTexture", programTex, 0);
|
||||
Core::SetActiveTexture(textureNormal, "textureNormal", programTex, 1);
|
||||
|
||||
glm::mat4 viewProjectionMatrix = createPerspectiveMatrix() * createCameraMatrix();
|
||||
glm::mat4 transformation = viewProjectionMatrix * modelMatrix;
|
||||
glUniformMatrix4fv(glGetUniformLocation(programTex, "transformation"), 1, GL_FALSE, (float*)&transformation);
|
||||
@ -381,24 +235,21 @@ void drawPlanetTex(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint t
|
||||
|
||||
glUniform3f(glGetUniformLocation(programTex, "cameraPos"), cameraPos.x, cameraPos.y, cameraPos.z);
|
||||
|
||||
glUniform1f(glGetUniformLocation(programTex, "time"), glfwGetTime());
|
||||
|
||||
float time = glfwGetTime();
|
||||
glUniform1f(glGetUniformLocation(programTex, "u_time"), time);
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudLight"), cloudLight);
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudIntensity"), cloudIntensity);
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudMotion"), cloudMotion);
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudBrightness"), cloudBrightness);
|
||||
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudSpeed"), cloudSpeed);
|
||||
glUniform1f(glGetUniformLocation(programTex, "cloudLightness"), cloudLightness);
|
||||
glUniform1i(glGetUniformLocation(programTex, "atmosphereCheck"), atmosphereCheck);
|
||||
glUniform1i(glGetUniformLocation(programTex, "toneMappingCheck"), toneMappingCheck);
|
||||
|
||||
Core::DrawContext(context);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void drawPlanetPbr(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture, GLuint textureNormal) {
|
||||
void drawPlanetPbr(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture) {
|
||||
glUseProgram(programPbr);
|
||||
Core::SetActiveTexture(texture, "colorTexture", programPbr, 0);
|
||||
Core::SetActiveTexture(textureNormal, "textureNormal", programPbr, 1);
|
||||
|
||||
glm::mat4 viewProjectionMatrix = createPerspectiveMatrix() * createCameraMatrix();
|
||||
glm::mat4 transformation = viewProjectionMatrix * modelMatrix;
|
||||
glUniformMatrix4fv(glGetUniformLocation(programPbr, "transformation"), 1, GL_FALSE, (float*)&transformation);
|
||||
@ -416,14 +267,14 @@ void drawPlanetPbr(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint t
|
||||
glUniform3f(glGetUniformLocation(programPbr, "lightPos"), sunPos.x, sunPos.y, sunPos.z);
|
||||
glUniform3f(glGetUniformLocation(programPbr, "lightColor"), lightColor.x, lightColor.y, lightColor.z);
|
||||
|
||||
glUniform1f(glGetUniformLocation(programPbr, "time"), glfwGetTime());
|
||||
|
||||
float time = glfwGetTime();
|
||||
glUniform1f(glGetUniformLocation(programPbr, "u_time"), time);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudLight"), cloudLight);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudIntensity"), cloudIntensity);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudMotion"), cloudMotion);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudBrightness"), cloudBrightness);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudSpeed"), cloudSpeed);
|
||||
glUniform1f(glGetUniformLocation(programPbr, "cloudLightness"), cloudLightness);
|
||||
|
||||
glUniform1i(glGetUniformLocation(programPbr, "atmosphereCheck"), atmosphereCheck);
|
||||
glUniform1i(glGetUniformLocation(programPbr, "toneMappingCheck"), toneMappingCheck);
|
||||
|
||||
Core::DrawContext(context);
|
||||
glUseProgram(0);
|
||||
@ -432,7 +283,6 @@ void drawPlanetPbr(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint t
|
||||
void drawSun(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture) {
|
||||
glUseProgram(programSun);
|
||||
Core::SetActiveTexture(texture, "colorTexture", programSun, 0);
|
||||
|
||||
glm::mat4 viewProjectionMatrix = createPerspectiveMatrix() * createCameraMatrix();
|
||||
glm::mat4 transformation = viewProjectionMatrix * modelMatrix;
|
||||
glUniformMatrix4fv(glGetUniformLocation(programSun, "transformation"), 1, GL_FALSE, (float*)&transformation);
|
||||
@ -443,10 +293,7 @@ void drawSun(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture
|
||||
|
||||
glUniform3f(glGetUniformLocation(programSun, "cameraPos"), cameraPos.x, cameraPos.y, cameraPos.z);
|
||||
|
||||
glUniform1f(glGetUniformLocation(programSun, "time"), glfwGetTime());
|
||||
|
||||
glUniform1i(glGetUniformLocation(programSun, "glowCheck"), glowCheck);
|
||||
glUniform1i(glGetUniformLocation(programSun, "toneMappingCheck"), toneMappingCheck);
|
||||
glUniform1i(glGetUniformLocation(programSun, "atmosphereCheck"), atmosphereCheck);
|
||||
|
||||
Core::DrawContext(context);
|
||||
glUseProgram(0);
|
||||
@ -455,7 +302,6 @@ void drawSun(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture
|
||||
void drawSkyBox(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint texture) {
|
||||
glUseProgram(programSkyBox);
|
||||
Core::SetActiveSkyBox(texture, "skybox", programSkyBox, 0);
|
||||
|
||||
glm::mat4 viewProjectionMatrix = createPerspectiveMatrix() * createCameraMatrix();
|
||||
glm::mat4 transformation = viewProjectionMatrix * modelMatrix;
|
||||
glUniformMatrix4fv(glGetUniformLocation(programSkyBox, "transformation"), 1, GL_FALSE, (float*)&transformation);
|
||||
@ -467,11 +313,12 @@ void drawSkyBox(Core::RenderContext& context, glm::mat4 modelMatrix, GLuint text
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void drawBlur() {
|
||||
void drawPlanetBlur() {
|
||||
bool horizontal = true, first_iteration = true;
|
||||
unsigned int amount = 10;
|
||||
glUseProgram(programBlur);
|
||||
glUniform1i(glGetUniformLocation(programBlur, "count"), blur_count);
|
||||
for (unsigned int i = 0; i < blur_count * 2; i++)
|
||||
for (unsigned int i = 0; i < amount; i++)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
|
||||
glUniform1i(glGetUniformLocation(programBlur, "horizontal"), horizontal);
|
||||
@ -497,43 +344,37 @@ void drawBlur() {
|
||||
}
|
||||
|
||||
void renderScene(GLFWwindow* window) {
|
||||
glClearColor(0.0f, 0.0f, 0.05f, 1.0f);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
float time = glfwGetTime();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
//rysowanie planety
|
||||
glm::mat4 planetScale = glm::scale(glm::vec3(planetSize));
|
||||
glm::mat4 planetRotate = glm::rotate(time * planetRot, glm::vec3(0, 1, 0));
|
||||
glm::mat4 planetTranslate = glm::translate(planetPos);
|
||||
|
||||
if (lightingCheck)
|
||||
drawPlanetPbr(sphereContext, planetTranslate * planetRotate * planetScale, planetTex, normalTex);
|
||||
drawPlanetPbr(sphereContext, planetTranslate * planetRotate * planetScale, planetTex);
|
||||
else
|
||||
drawPlanetTex(sphereContext, planetTranslate * planetRotate * planetScale, planetTex, normalTex);
|
||||
drawPlanetTex(sphereContext, planetTranslate * planetRotate * planetScale, planetTex);
|
||||
|
||||
//rysowanie słońca
|
||||
glm::mat4 sunScale = glm::scale(glm::vec3(sunSize));
|
||||
glm::mat4 sunRotate = glm::rotate(PI, glm::vec3(0, 1, 0));
|
||||
glm::mat4 sunTranslate = glm::translate(sunPos);
|
||||
|
||||
drawSun(sphereContext, sunTranslate * sunRotate * sunScale, sunTex);
|
||||
drawSun(sphereContext, sunTranslate * sunScale, sunTex);
|
||||
|
||||
//rysowanie skyboxa
|
||||
skyBoxPos = cameraPos;
|
||||
glm::mat4 skyBoxScale = glm::scale(glm::vec3(skyBoxSize));
|
||||
glm::mat4 skyBoxRotate = glm::rotate(glm::radians(skyBoxRot), glm::vec3(0, 1, 0));
|
||||
glm::mat4 skyBoxTranslate = glm::translate(skyBoxPos);
|
||||
|
||||
if (skyBoxCheck)
|
||||
drawSkyBox(cubeContext, skyBoxTranslate * skyBoxRotate * skyBoxScale, skyBoxTex);
|
||||
drawSkyBox(cubeContext, skyBoxTranslate * skyBoxScale, skyBoxTex);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
drawBlur();
|
||||
|
||||
showGUI();
|
||||
drawPlanetBlur();
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
@ -562,60 +403,16 @@ void loadModelToContext(std::string path, Core::RenderContext& context) {
|
||||
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
//tekstura planety
|
||||
if (key == GLFW_KEY_T && action == GLFW_PRESS) {
|
||||
++planetTexIndex;
|
||||
|
||||
if (planetTexIndex > sizeof(planetTexPaths) / sizeof(planetTexPaths[0]) - 1)
|
||||
planetTexIndex = 0;
|
||||
|
||||
planetTex = Core::LoadTexture(planetTexPaths[planetTexIndex]);
|
||||
normalTex = Core::LoadTexture(normalTexPaths[planetTexIndex]);
|
||||
}
|
||||
else if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
--planetTexIndex;
|
||||
|
||||
if (planetTexIndex < 0)
|
||||
planetTexIndex = sizeof(planetTexPaths) / sizeof(planetTexPaths[0]) - 1;
|
||||
|
||||
planetTex = Core::LoadTexture(planetTexPaths[planetTexIndex]);
|
||||
normalTex = Core::LoadTexture(normalTexPaths[planetTexIndex]);
|
||||
}
|
||||
if (key == GLFW_KEY_T && action == GLFW_PRESS)
|
||||
planetTex = Core::LoadTexture(planetTexPaths[std::abs(++planetTexIndex % 20)]);
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS && planetTexIndex > 0)
|
||||
planetTex = Core::LoadTexture(planetTexPaths[std::abs(--planetTexIndex % 20)]);
|
||||
|
||||
//tekstura słońca
|
||||
if (key == GLFW_KEY_U && action == GLFW_PRESS) {
|
||||
++sunTexIndex;
|
||||
|
||||
if (sunTexIndex > sizeof(sunTexPaths) / sizeof(sunTexPaths[0]) - 1)
|
||||
sunTexIndex = 0;
|
||||
|
||||
sunTex = Core::LoadTexture(sunTexPaths[sunTexIndex]);
|
||||
}
|
||||
else if (key == GLFW_KEY_I && action == GLFW_PRESS) {
|
||||
--sunTexIndex;
|
||||
|
||||
if (sunTexIndex < 0)
|
||||
sunTexIndex = sizeof(sunTexPaths) / sizeof(sunTexPaths[0]) - 1;
|
||||
|
||||
sunTex = Core::LoadTexture(sunTexPaths[sunTexIndex]);
|
||||
}
|
||||
|
||||
//tekstura skyboxa
|
||||
if (key == GLFW_KEY_G && action == GLFW_PRESS) {
|
||||
++skyBoxIndex;
|
||||
|
||||
if (skyBoxIndex > sizeof(skyBoxArrays) / sizeof(skyBoxArrays[0]) - 1)
|
||||
skyBoxIndex = 0;
|
||||
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxArrays[skyBoxIndex]);
|
||||
}
|
||||
else if (key == GLFW_KEY_H && action == GLFW_PRESS) {
|
||||
--skyBoxIndex;
|
||||
|
||||
if (skyBoxIndex < 0)
|
||||
skyBoxIndex = sizeof(skyBoxArrays) / sizeof(skyBoxArrays[0]) - 1;
|
||||
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxArrays[skyBoxIndex]);
|
||||
}
|
||||
if (key == GLFW_KEY_U && action == GLFW_PRESS)
|
||||
sunTex = Core::LoadTexture(sunTexPaths[std::abs(++sunTexIndex % 5)]);
|
||||
if (key == GLFW_KEY_I && action == GLFW_PRESS && sunTexIndex > 0)
|
||||
sunTex = Core::LoadTexture(sunTexPaths[std::abs(--sunTexIndex % 5)]);
|
||||
|
||||
//atmosfera
|
||||
if (key == GLFW_KEY_O && action == GLFW_PRESS)
|
||||
@ -625,24 +422,12 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod
|
||||
if (key == GLFW_KEY_P && action == GLFW_PRESS)
|
||||
lightingCheck = !lightingCheck;
|
||||
|
||||
//poświata słońca
|
||||
if (key == GLFW_KEY_E && action == GLFW_PRESS)
|
||||
glowCheck = !glowCheck;
|
||||
|
||||
//skybox
|
||||
if (key == GLFW_KEY_Q && action == GLFW_PRESS)
|
||||
skyBoxCheck = !skyBoxCheck;
|
||||
|
||||
//tonemapping
|
||||
if (key == GLFW_KEY_F && action == GLFW_PRESS)
|
||||
toneMappingCheck = !toneMappingCheck;
|
||||
|
||||
//bloom
|
||||
if (key == GLFW_KEY_M && action == GLFW_PRESS) {
|
||||
if (key == GLFW_KEY_B && action == GLFW_PRESS) {
|
||||
blur_count++;
|
||||
if (blur_count > 5)
|
||||
blur_count = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//obsluga wejscia
|
||||
@ -672,23 +457,23 @@ void processInput(GLFWwindow* window)
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS && planetSize < 0.01f)
|
||||
planetSize += sizeSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS && planetSize > 0.001f)
|
||||
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS && planetSize > 0.001f)
|
||||
planetSize -= sizeSpeed;
|
||||
|
||||
//obrót planety
|
||||
float rotationSpeed = 0.0025f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS && planetRot < 2.0f)
|
||||
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS && planetRot < 1.f)
|
||||
planetRot += rotationSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS && planetRot > -2.0f)
|
||||
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS && planetRot > -1.f)
|
||||
planetRot -= rotationSpeed;
|
||||
|
||||
//jasność
|
||||
float powerSpeed = 0.05f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS && lightPower < 20.f)
|
||||
if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS && lightPower < 16.f)
|
||||
lightPower += powerSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS && lightPower > 1.f)
|
||||
if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS && lightPower > 2.f)
|
||||
lightPower -= powerSpeed;
|
||||
|
||||
lightColor = glm::vec3(lightPower, lightPower, lightPower);
|
||||
@ -698,7 +483,7 @@ void processInput(GLFWwindow* window)
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS && planetRough < 1.f)
|
||||
planetRough += roughSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_4) == GLFW_PRESS && planetRough > 0.f)
|
||||
if (glfwGetKey(window, GLFW_KEY_4) == GLFW_PRESS && planetRough > 0.f)
|
||||
planetRough -= roughSpeed;
|
||||
|
||||
//metaliczność
|
||||
@ -706,46 +491,8 @@ void processInput(GLFWwindow* window)
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_5) == GLFW_PRESS && planetMetal < 1.f)
|
||||
planetMetal += metalSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_6) == GLFW_PRESS && planetMetal > 0.f)
|
||||
if (glfwGetKey(window, GLFW_KEY_6) == GLFW_PRESS && planetMetal > 0.f)
|
||||
planetMetal -= metalSpeed;
|
||||
|
||||
//natężenie chmur
|
||||
float intensitySpeed = 0.1f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS && cloudIntensity < 100.f)
|
||||
cloudIntensity += intensitySpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS && cloudIntensity > 0.f)
|
||||
cloudIntensity -= intensitySpeed;
|
||||
|
||||
//ruch chmur
|
||||
float motionSpeed = 0.0025f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_V) == GLFW_PRESS && cloudMotion < 0.5f)
|
||||
cloudMotion += motionSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS && cloudMotion > -0.5f)
|
||||
cloudMotion -= motionSpeed;
|
||||
|
||||
//jasność chmur
|
||||
float brightnessSpeed = 0.0005f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_N) == GLFW_PRESS && cloudBrightness < 0.2f)
|
||||
cloudBrightness += brightnessSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS && cloudBrightness > 0.01f)
|
||||
cloudBrightness -= brightnessSpeed;
|
||||
|
||||
//wielkość słońca
|
||||
if (glfwGetKey(window, GLFW_KEY_7) == GLFW_PRESS && sunSize < 0.1f)
|
||||
sunSize += sizeSpeed * 10;
|
||||
else if (glfwGetKey(window, GLFW_KEY_8) == GLFW_PRESS && sunSize > 0.01f)
|
||||
sunSize -= sizeSpeed * 10;
|
||||
|
||||
//obrót skyboxa
|
||||
float rotSpeed = 1.0f;
|
||||
|
||||
if (glfwGetKey(window, GLFW_KEY_9) == GLFW_PRESS && skyBoxRot < 360.0f)
|
||||
skyBoxRot += rotSpeed;
|
||||
else if (glfwGetKey(window, GLFW_KEY_0) == GLFW_PRESS && skyBoxRot > 0.0f)
|
||||
skyBoxRot -= rotSpeed;
|
||||
}
|
||||
|
||||
void init(GLFWwindow* window) {
|
||||
@ -753,13 +500,8 @@ void init(GLFWwindow* window) {
|
||||
initHDR(window);
|
||||
initRBO(window);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
//glDisable(GL_DEPTH_TEST);
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 430");
|
||||
ImGui::StyleColorsDark;
|
||||
|
||||
glfwSetKeyCallback(window, key_callback);
|
||||
|
||||
@ -773,11 +515,10 @@ void init(GLFWwindow* window) {
|
||||
loadModelToContext("./models/sphere.obj", sphereContext);
|
||||
loadModelToContext("./models/cube.obj", cubeContext);
|
||||
|
||||
planetTex = Core::LoadTexture(planetTexPaths[planetTexIndex]);
|
||||
normalTex = Core::LoadTexture(normalTexPaths[planetTexIndex]);
|
||||
sunTex = Core::LoadTexture(sunTexPaths[sunTexIndex]);
|
||||
planetTex = Core::LoadTexture(planetTexPaths[std::abs(planetTexIndex % 20)]);
|
||||
sunTex = Core::LoadTexture(sunTexPaths[std::abs(sunTexIndex % 5)]);
|
||||
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxArrays[skyBoxIndex]);
|
||||
skyBoxTex = Core::LoadSkyBox(skyBoxPaths);
|
||||
|
||||
blurPingPong(window);
|
||||
|
||||
@ -789,11 +530,6 @@ void shutdown(GLFWwindow* window) {
|
||||
shaderLoader.DeleteProgram(programPbr);
|
||||
shaderLoader.DeleteProgram(programSun);
|
||||
shaderLoader.DeleteProgram(programSkyBox);
|
||||
shaderLoader.DeleteProgram(programBlur);
|
||||
shaderLoader.DeleteProgram(programBloomFinal);
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
// funkcja jest glowna petla
|
107
grk/cw 6/style.css
Normal file
@ -0,0 +1,107 @@
|
||||
html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
|
||||
|
||||
body{
|
||||
color:#444;
|
||||
font-family:Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif;
|
||||
font-size:12px;
|
||||
line-height:1.5em;
|
||||
padding:1em;
|
||||
margin:auto;
|
||||
max-width:42em;
|
||||
background:#fefefe;
|
||||
}
|
||||
|
||||
a{ color: #0645ad; text-decoration:none;}
|
||||
a:visited{ color: #0b0080; }
|
||||
a:hover{ color: #06e; }
|
||||
a:active{ color:#faa700; }
|
||||
a:focus{ outline: thin dotted; }
|
||||
a:hover, a:active{ outline: 0; }
|
||||
|
||||
::-moz-selection{background:rgba(255,255,0,0.3);color:#000}
|
||||
::selection{background:rgba(255,255,0,0.3);color:#000}
|
||||
|
||||
a::-moz-selection{background:rgba(255,255,0,0.3);color:#0645ad}
|
||||
a::selection{background:rgba(255,255,0,0.3);color:#0645ad}
|
||||
|
||||
p{
|
||||
margin:1em 0;
|
||||
}
|
||||
|
||||
img{
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6{
|
||||
font-weight:normal;
|
||||
color:#111;
|
||||
line-height:1em;
|
||||
}
|
||||
h4,h5,h6{ font-weight: bold; }
|
||||
h1{ font-size:2.5em; }
|
||||
h2{ font-size:2em; }
|
||||
h3{ font-size:1.5em; }
|
||||
h4{ font-size:1.2em; }
|
||||
h5{ font-size:1em; }
|
||||
h6{ font-size:0.9em; }
|
||||
|
||||
blockquote{
|
||||
color:#666666;
|
||||
margin:0;
|
||||
padding-left: 3em;
|
||||
border-left: 0.5em #EEE solid;
|
||||
}
|
||||
hr { display: block; height: 2px; border: 0; border-top: 1px solid #aaa;border-bottom: 1px solid #eee; margin: 1em 0; padding: 0; }
|
||||
pre, code, kbd, samp { color: #000; font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 0.98em; }
|
||||
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
|
||||
|
||||
b, strong { font-weight: bold; }
|
||||
|
||||
dfn { font-style: italic; }
|
||||
|
||||
ins { background: #ff9; color: #000; text-decoration: none; }
|
||||
|
||||
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
|
||||
|
||||
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
||||
sup { top: -0.5em; }
|
||||
sub { bottom: -0.25em; }
|
||||
|
||||
ul, ol { margin: 1em 0; padding: 0 0 0 2em; }
|
||||
li p:last-child { margin:0 }
|
||||
dd { margin: 0 0 0 2em; }
|
||||
|
||||
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
th { border-bottom: 1px solid black; }
|
||||
td { vertical-align: top; }
|
||||
|
||||
@media only screen and (min-width: 480px) {
|
||||
body{font-size:14px;}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
body{font-size:16px;}
|
||||
}
|
||||
|
||||
@media print {
|
||||
* { background: transparent !important; color: black !important; filter:none !important; -ms-filter: none !important; }
|
||||
body{font-size:12pt; max-width:100%;}
|
||||
a, a:visited { text-decoration: underline; }
|
||||
hr { height: 1px; border:0; border-bottom:1px solid black; }
|
||||
a[href]:after { content: " (" attr(href) ")"; }
|
||||
abbr[title]:after { content: " (" attr(title) ")"; }
|
||||
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
|
||||
pre, blockquote { border: 1px solid #999; padding-right: 1em; page-break-inside: avoid; }
|
||||
tr, img { page-break-inside: avoid; }
|
||||
img { max-width: 100% !important; }
|
||||
@page :left { margin: 15mm 20mm 15mm 10mm; }
|
||||
@page :right { margin: 15mm 10mm 15mm 20mm; }
|
||||
p, h2, h3 { orphans: 3; widows: 3; }
|
||||
h2, h3 { page-break-after: avoid; }
|
||||
}
|
Before ![]() (image error) Size: 9.1 MiB After ![]() (image error) Size: 9.2 MiB ![]() ![]() |
BIN
grk/cw 6/textures/materials/normalne/rust_normal.jpg
Normal file
After ![]() (image error) Size: 3.3 MiB |
BIN
grk/cw 6/textures/materials/normalne/spaceship_normal.jpg
Normal file
After ![]() (image error) Size: 3.3 MiB |
BIN
grk/cw 6/textures/materials/rust.jpg
Normal file
After ![]() (image error) Size: 6.5 MiB |