zmiana nazw plików projektowych i usunięcie zbędnych plików
2
.gitignore
vendored
@ -5,4 +5,4 @@
|
|||||||
/.vs
|
/.vs
|
||||||
/grk/.vs
|
/grk/.vs
|
||||||
/grk/Debug
|
/grk/Debug
|
||||||
/grk/cw 6/Debug
|
/grk/planet-editor/Debug
|
@ -1,144 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,105 +0,0 @@
|
|||||||
## Ł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*.
|
|
||||||
|
|
||||||
![](./img/stride_offest.jpg)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,74 +0,0 @@
|
|||||||
## Ł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`.
|
|
||||||
![](./img/z_depth_graph2.jpg)
|
|
||||||
|
|
||||||
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.
|
|
@ -1,92 +0,0 @@
|
|||||||
## 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
|
|
||||||
![](./img/spotlight.png)
|
|
||||||
## 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**.
|
|
||||||
|
|
||||||
![](./img/sun.png)
|
|
@ -1,196 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,148 +0,0 @@
|
|||||||
# 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.
|
|
||||||
![](./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.
|
|
||||||
|
|
||||||
### Ł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.
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,125 +0,0 @@
|
|||||||
# Physically based rendering
|
|
||||||
|
|
||||||
Physically based rendering jest zbiorem technik renderowania, które bazują na teorii, która ma za zadanie odwzorowywać świat rzeczywisty.
|
|
||||||
|
|
||||||
Podstawową dla obliczenia jest *rendering equation* następującej postaci.
|
|
||||||
|
|
||||||
$$L_o(p,\omega_o)=\int_\Omega f_r(p,\omega_i,\omega_o)L_i(p,\omega_i)n\cdot \omega_i\ d\omega_i$$
|
|
||||||
Całka przechodzi po wszystkich kierunkach na półkuli $\Omega$ oblicza wyjściowe oświetlenie $L_O$ bazując na wejściowym oświetleniu $L_i$ i funkcji $f_r$ znanej jako **BRDF**, czyli *bidirectional reflective distribution function*. W najbardziej ogólnym przypadku należałoby traktować $L_i$ jako dystrybucję i jest nadmiernym formalizmem w naszym przypadku, gdy będziemy wykorzystywać wyłącznie światła punktowe i kierunkowe. Dlatego o powyższej całce możemy myśleć jak o sumie po źródłach światła.
|
|
||||||
|
|
||||||
By uzyskać realistyczny efekt, potrzebujemy realistycznej funkcji $f_r$. Wybierzemy *Cook-Torrance BRDF*, który jest najczęściej wykorzystywaną funkcją przy liczeniu PBR w czasie rzeczywistym.
|
|
||||||
|
|
||||||
*Cook-Torrance BRDF* rozdzielamy na światło rozproszone (diffuse) i odbite kierunkowo (specular)
|
|
||||||
|
|
||||||
$$f_r=k_d f_{lambert}+k_s f_{cook−torrance},$$
|
|
||||||
gdzie $k_d$ i $k_s$ to są współczynniki, rozdzielające ile światła zostało rozproszone a ile odbite, ich suma musi być równa 1. komponent $f_{lambert}$ jest znany jako *Lambertian diffuse* i wynosi
|
|
||||||
$$f_{lambert}=\frac{c}{\pi}$$
|
|
||||||
przy czym $c$ to jest jest kolorem powierzchni a $\pi$ odpowiada za normalizację.
|
|
||||||
|
|
||||||
Część odbicia kierunkowego jest bardziej skomplikowana i wygląda następująco:
|
|
||||||
|
|
||||||
$$f_{cook−torrance}=\frac{DFG}{4(w_o\cdot n)(w_i\cdot n)}$$
|
|
||||||
gdzie $D$, $F$ i $G$ są funkcjami, które zależą od normalnej, wektora wejściowego i wyjściowego oraz parametrów chropowatości $\alpha$ i odbicia $F_0$. Te funkcje to odpowiednio:
|
|
||||||
- $D$ - **Normal distribution function** przybliża ilość powierzchni, która jest ustawiona prostopadle do wektora połówkowego korzystamy z funkcji Trowbridge-Reitz GGX $$D(n,h,\alpha) = \frac{\alpha^2}{π((n⋅h)^2(α^2−1)+1)^2},$$
|
|
||||||
gdzie $h$ to: $$h = \frac{\omega_o+\omega_i}{||\omega_o+\omega_i||}$$
|
|
||||||
- G - **Geometry function** opisuje stopień samo-zacieninienia. Wykorzystujemy Schlick-GGX
|
|
||||||
$$G(n,\omega_o,k)=\frac{n⋅\omega_o}{(n⋅\omega_o)(1−k)+k}$$
|
|
||||||
przy czym $k$ wynosi
|
|
||||||
$$k=\frac{(\alpha+1)^2}{8}$$
|
|
||||||
- F - **Fresnel equation** opisuje stopień odbicia w zależności od kąta padania. Wykorzystujemy aproksymację Fresnela-Schlicka
|
|
||||||
$$F(h,v,F_0)=F_0+(1−F_0)(1−(h⋅v))^5$$
|
|
||||||
Parametr $F_0$ jest własnością materiału dla uproszczenia uznaje się, że niemetale mają wartość (0.04,0.4,0.4) a dla metali jest ona równa kolorowi obiektu. Wprowadza się **parametr metaliczności**, który jest współczynnikiem, jakim miksujemy między (0.04,0.4,0.4) a kolorem, żeby uzyskać $F_0$.
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
W projekcie znajduje się jedna kula, punktowe oświetlenie i cieniowanie Phonga. Zaimplementuj PBR i rozmieść kule w macierzy 10 x 10 zmieniając stopniowo w nich parametr chropowatości $\alpha$ i metaliczności, jak na poniższym obrazku.
|
|
||||||
![](./img/lighting_result.png)
|
|
||||||
|
|
||||||
## Teksturowanie
|
|
||||||
Do uzyskania realistycznego efektu wykorzystuje się szereg tekstur:
|
|
||||||
|
|
||||||
![](./img/textures.png)
|
|
||||||
Tekstury **Albedo** i **Normalnych** odpowiadają za kolor i normalne jak przy cieniowaniu Phonga. **Metalic** i **Roughness** przechowuje wartość metaliczności i chropowatości, które pojawiły się w BRDF. Natomiast **AO** (ambient occlision) odpowiada za stopień samo-zacienienia, który modyfikuje oświetlenie ambientowe w danym punkcie, uwzględnienie jej podkreśla detale obiektów.
|
|
||||||
|
|
||||||
Karty graficzne posiadają ograniczenia, jeżeli chodzi o ilość tekstur, które można przesłać za jednym razem (współcześnie są to 32 tekstury). Może być to znaczące ograniczenie, dlatego **AO** **Roughness** i **Metalic** mogą być przesłane w jednej teksturze kolejno jako kanał r, g i b. Taką teksturę określa się jako arm od pierwszych liter nazw.
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
W `ex_8_2.hpp` znajduje się podstawowa scena. Zaimplementuj w niej PBR z użyciem tekstur.
|
|
||||||
Stwórz nową parę shaderów, które będą obsługiwać PBR![](./img/textures.png)W oparciu o tekstury. Wyświetl statek zestawem tekstur w folderze `textures/spaceshipPBR`. Możesz też wziąć swój układ słoneczny z poprzednich zajęć.
|
|
||||||
Ładowanie tekstur jest męczące, napisz klasę/strukturę `PBRTexturesHandler`, która przechowuje zestaw tekstur *albedo*, *normal* i *arm*. Posiada konstruktor, który ładuje tekstury po ścieżkach oraz funkcję `activateTextures(int[] indices)`, która aktywuje tekstury z indeksami podanymi w tablicy. `int[] indices`.
|
|
||||||
|
|
||||||
## PBR Multitexturing
|
|
||||||
Podobnie jak w przypadku zwykłych tekstur możemy mieszać tekstury przesyłane przez PBR, należy wtedy mieszać wszystkie tekstury z takim samym współczynnikiem. Jednak stopniowe przejście z jednej tekstury do drugiej często prowadzi to do nienaturalnych rezultatów, gdy próbujemy mieszać tekstury o różnej częstotliwości detali, jak na przykład poniżej:
|
|
||||||
![](./img/blending1.webp)
|
|
||||||
W prawdziwym świecie, przy stopniowej zmianie z kamiennego podłoża na piaszczyste, wiedzielibyśmy, jak piasek stopniowo zaczyna wypełniać szpary między kamieniami i przykrywać je coraz bardziej.
|
|
||||||
Istnieje wiele metod mieszania tekstur np [ta](https://onlinelibrary.wiley.com/doi/10.1002/cav.1460). My wykorzystamy technikę opartą na mapach wysokości, opisaną w
|
|
||||||
[https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting](https://www.gamedeveloper.com/programming/advanced-terrain-texture-splatting).
|
|
||||||
|
|
||||||
> Mapa wysokości to dodatkowa tekstura, która wskazuje wysokość obiektów znajdujących się na teksturze względem płaskiej powierzchni. Może być również wykorzystana przy paralax mappingu czy tessalacji. Jest ona nazywana po angielsku *heightmap*, *displacement map* czy *bump map* w zależności od zastosowania. Przykładowe znajdują się w teksutrach.
|
|
||||||
|
|
||||||
Możemy te mapy wykorzystać do mieszania dwóch map tak, że wyświetlana będzie ta, która jest wyżej. Zaprezentować to możemy w poniższym jednowymiarowym schemacie. Niebieska linia reprezentuje mapę piasku a czerwona kamieni.
|
|
||||||
![](./img/2.png)
|
|
||||||
Jako rezultat otrymujemy
|
|
||||||
![](./img/3.webp)
|
|
||||||
|
|
||||||
Kod funkcji mieszającej:
|
|
||||||
```C++
|
|
||||||
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
|
|
||||||
{
|
|
||||||
if (height1>height2){
|
|
||||||
return texture1;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return texture2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To pozwala nam zmieszać dwie tekstury. Jednak samo w sobie nie powoduję przejścia z jednej do drugiej, to możemy osiągnąć modyfikując wysokość parametrem `blend_ratio`, który będzie z zakresu od 0 do 1. Na jednowymiarowym schemacie wartość wysokości wygląda to następująco.
|
|
||||||
![](./img/4.png)
|
|
||||||
Daje nam to to poniższy efekt
|
|
||||||
![](./img/5.webp)
|
|
||||||
Kod nowej funkcji mieszającej:
|
|
||||||
```C++
|
|
||||||
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
|
|
||||||
{
|
|
||||||
float h1=height1+1.0-blend_ratio;
|
|
||||||
float h2=height2+blend_ratio;
|
|
||||||
if (h1>h2){
|
|
||||||
return texture1;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return texture2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
To podejście dało nam bardziej naturalne przejście z piasku do kamieni. Widać jak kamienie stopniowo wyłaniają się spod piasku. Jednak pojawiły się artefakty w miejscach, gdzie wartości mapy wysokości są bardzo zbliżone. Wynika to z ograniczeń kwantyzacji. By zminimalizować te błędy, będziemy mieszać wartości tekstur, ale tylko, gdy wartości będą blisko siebie.
|
|
||||||
![](./img/6.webp)
|
|
||||||
Zdefiniujemy dodatkowy parametr `mix_threshold`, który będzie określał, jak blisko mają być wartości, żeby je mieszać. Jeżeli różnica między jedną wysokością a drugą będzie mniejsza od `mix_threshold` to będziemy je mieszać proporcjonalnie do ich różnicy podzielonej przez `mix_threshold`, w przeciwnym wypadku wybierzemy tą, która jest wyżej. W celu optymalizacji zamiast optymalizacji korzystamy z funkcji clamp
|
|
||||||
|
|
||||||
```C++
|
|
||||||
float3 blend(float3 texture1, float height1, float3 texture2, float height2, float blend_ratio)
|
|
||||||
{
|
|
||||||
float mix_threshold=0.1;
|
|
||||||
float h1=height1+1.0-blend_ratio;
|
|
||||||
float h2=height2+blend_ratio;
|
|
||||||
float havg=(h1+h2)/2.;
|
|
||||||
|
|
||||||
h1 = clamp((h1-hav+0.5*mix_threshold)/(mix_threshold),0.,1.);
|
|
||||||
h2 = clamp((h2-hav+0.5*mix_threshold)/(mix_threshold),0.,1.);
|
|
||||||
|
|
||||||
return (texture1*h1+texture2*h2)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Stosunek w jakim je zmieszamy:
|
|
||||||
![](./img/7.webp)
|
|
||||||
|
|
||||||
Finalna tekstura
|
|
||||||
![](./img/8.webp)
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
W teksturach znajduje plik `heightmap.png` wykorzystaj go jako mapę wysokości przy rysowaniu jednej z planet. Mapa ta zawiera wartości, od 0 do 1. Dla różnych wysokości chcemy wykorzystywać różne tekstury, ustal na przykład, że poniżej 0,3 znajduje się woda, między 0,3 a 0,6 trawa, natomiast powyżej skały.
|
|
||||||
|
|
||||||
### Zadanie*
|
|
||||||
Zmodyfikuj swój układ słoneczny z poprzednich zajęć, zmień w nim model oświetlenia z Phonga na PBR. Tekstury możesz pobrać na przykład z [https://polyhaven.com/textures](https://polyhaven.com/textures)
|
|
||||||
lub [www.texturecan.com](www.texturecan.com).
|
|
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 190 KiB |
Before Width: | Height: | Size: 329 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 978 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 2.0 MiB |
Before Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 72 KiB |
@ -1,107 +0,0 @@
|
|||||||
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 Width: | Height: | Size: 9.2 MiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 6.5 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 598 KiB |
Before Width: | Height: | Size: 424 KiB |
@ -1,164 +0,0 @@
|
|||||||
<!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 3</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="ćwiczenia-3">Ćwiczenia 3</h1>
|
|
||||||
<h2 id="przestrzenie-potoku-graficznego">Przestrzenie potoku graficznego</h2>
|
|
||||||
<p>W trakcie wykładu zostały opisane kolejne przestrzenie potoku graficznego. W tej części zajęć przejdziemy kolejne jego etapy.</p>
|
|
||||||
<p><img src="./img/coordinate_systems.jpg" /></p>
|
|
||||||
<p>Pierwszy krok, czyli przejście do <code>World Space</code> już wykonujemy za pomocą macierzy transformacji. Następnym krokiem będzie stworzenie macierzy projekcji i macierzy widoku. Zaczniemy od macierzy projekcji. Będziemy modyfikować macierz, którą wysyła funkcja <code>createPerspectiveMatrix</code> (macierz jest transponowana dalej, więc zapisuj ją tak, jak tu widzisz). Zanim zaczniemy domnóż macierz perspektywy do macierzy transformacji obiektów.</p>
|
|
||||||
<h2 id="macierz-perspektywy">Macierz perspektywy</h2>
|
|
||||||
<p>Rozważmy mnożenie dowolnej macierzy przez wektor kolumnowy:</p>
|
|
||||||
<p><span class="math display">\[\begin{bmatrix} m_{11} & m_{12} & m_{13}& m_{14}\\m_{21} & m_{22} & m_{23}& m_{24}\\m_{31} & m_{32} & m_{33}& m_{34}\\m_{41} & m_{42} & m_{43}& m_{44}\\\end{bmatrix}\begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}=\begin{bmatrix} x*m_{11}+y*m_{12}+z*m_{13}+m_{14}\\x*m_{21} +y*m_{22} +z*m_{23} +m_{24}\\x*m_{31} +y*m_{32} +z*m_{33} +m_{34}\\x*m_{41} +y*m_{42} +z*m_{43} +m_{44}\\\end{bmatrix}\]</span></p>
|
|
||||||
<p>Pierwszym krokiem jest wykorzystanie homogenizacji do uzyskania efektu perspektywy. W tym celu musimy ustawić współrzędną <span class="math inline">\(w\)</span> na <span class="math inline">\(-z\)</span> za pomocą macierzy. Jeśli przyjrzymy się obliczeniom powyżej. Zobaczymy, że do tego musimy ustawić <span class="math inline">\(m_{43}\)</span> na <span class="math inline">\(-1\)</span> a pozostałe w tym wierszu na <span class="math inline">\(0\)</span>. Przemnożenie daje nam:</p>
|
|
||||||
<p><span class="math display">\[\begin{bmatrix} 1 & 0 & 0& 0\\0 & 1 & 0& 0\\0 & 0 & 1& 0\\0 & 0 & -1& 0 \end{bmatrix}\begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}=\begin{bmatrix} x \\ y \\ z \\ -z\end{bmatrix}\]</span></p>
|
|
||||||
<p>Po homogenizacji otrzymamy wektor:</p>
|
|
||||||
<p><span class="math display">\[\begin{bmatrix} -\frac{x}{z} \\ -\frac{x}{z} \\ -1 \\ 1\end{bmatrix}\]</span></p>
|
|
||||||
<hr />
|
|
||||||
<h3 id="zadanie">Zadanie</h3>
|
|
||||||
<p>zmodyfikuj macierz perspektywy w taki sposób.</p>
|
|
||||||
<hr />
|
|
||||||
<p>Wartość współrzędnej <span class="math inline">\(z\)</span> jest równa -1 dla każdego parametru. Co spowoduje, że nie będzie wiadomo, który wierzchołek bliżej, a który dalej i otrzymam zjawisko znane jako z-fighting. By tego uniknąć. Musimy zmapować współrzędną <span class="math inline">\(z\)</span>. Przypomnijmy, że przy arbitralnej macierzy wartość współrzędnej <span class="math inline">\(z\)</span> będzie następującej postaci:</p>
|
|
||||||
<p><span class="math display">\[ x*m_{31} +y*m_{32} +z*m_{33} +m_{34} \]</span></p>
|
|
||||||
<p>więc możemy pracować tylko z parametrami <span class="math inline">\(m_{33}\)</span> i <span class="math inline">\(m_{34}\)</span>, czyli <span class="math inline">\(z*m_{33} +m_{34}\)</span>, po uwzględnieniu dodatkowo homogenizacji otrzymamy ostateczny wzór:</p>
|
|
||||||
<p><span class="math display">\[ z'=-m_{33} -\frac{m_{34}}{z}.\]</span></p>
|
|
||||||
<p>Przypomnijmy, że w <code>Clipping Space</code> współrzędna <span class="math inline">\(z\)</span> musi się mapować na wartości od <span class="math inline">\(-1\)</span> do <span class="math inline">\(1\)</span>, żeby znalazły się w bryle kanonicznej (obiekty poza bryłą kanoniczną nie będą wyświetlone). Jak na wykładzie określimy sobie parametry <span class="math inline">\(0 < n < f\)</span>, które będą określać pozycję minimalnej i maksymalnej płaszczyzny osi <span class="math inline">\(z\)</span>. Chcemy, żeby dla <span class="math inline">\(z=n\)</span> wartość <span class="math inline">\(z'\)</span> wynosiła <span class="math inline">\(-1\)</span> oraz dla <span class="math inline">\(z=f\)</span> wartość <span class="math inline">\(z'\)</span> wynosiła <span class="math inline">\(1\)</span>, daje nam to układ równań:</p>
|
|
||||||
<p><span class="math display">\[\begin{matrix} -m_{33}& -&\frac{m_{34}}{n}&=&-1\\-m_{33} &-&\frac{m_{34}}{f}&=&1\end{matrix}\]</span></p>
|
|
||||||
<p>co po przekształceniu da nam:</p>
|
|
||||||
<p><span class="math display">\[\begin{matrix} m_{33}&=&\frac{(n + f)}{(n - f)}\\m_{34} &=&\frac{(2 n f)}{(n - f)}\end{matrix}\]</span></p>
|
|
||||||
<p>Ostatecznie otrzymujemy:</p>
|
|
||||||
<p><span class="math display">\[\begin{bmatrix} 1 & 0 & 0& 0\\0 & 1 & 0& 0\\0 & 0 & \frac{(n + f)}{(n - f)}& \frac{(2 n f)}{(n - f)}\\0 & 0 & -1& 0 \end{bmatrix}\]</span></p>
|
|
||||||
<p>Zauważ, że te wartość zmienia się zgodnie ze wzorem <span class="math inline">\(-\left(\frac{(n + f)}{(n - f)}+ \frac{(2 n f)}{z(n - f)}\right)\)</span> czyli zmienia się to asymptotycznie, co można zobaczyć na wykresie.</p>
|
|
||||||
<p><img src="./img/z_depth_graph.jpg" /></p>
|
|
||||||
<h3 id="zadanie-1">Zadanie*</h3>
|
|
||||||
<p>Rozwiąż samodzielnie ten układ równań.</p>
|
|
||||||
<h3 id="zadanie-2">Zadanie</h3>
|
|
||||||
<p>Dodaj zmienne lokalne <code>n</code> i <code>f</code> w funkcji, ustal im jakieś arbitralne wartości i dodaj rzutowanie <span class="math inline">\(z\)</span> zgodnie ze wzorem, który otrzymaliśmy. Spróbuj ustawić taką wartość <code>f</code>, żeby tylna ściana sześcianu zniknęła.</p>
|
|
||||||
<p>Uzyskana macierz daje nam rzutowanie perspektywiczne, ale możemy ją jeszcze rozbudować o zmianę kąta widzenia, a także naprawić problem z nieprawidłowym skalowaniem się ekranu przy zmianie jego proporcji. Obie te czynności sprowadzają się do tego samego, mianowicie chcemy zmienić kształt bryły widzenia w osiach <span class="math inline">\(x\)</span> i <span class="math inline">\(y\)</span>. By tego dokonać, musimy zmienić wartość parametrów <span class="math inline">\(m_{11}\)</span> i <span class="math inline">\(m_{22}\)</span>, to one odpowiadają za skalowanie w tych osiach. Parametry te ściskają lub rozszerzają przestrzeń w tych osiach, więc zmniejszenie wartości zwiększy kąt widzenia w danej osi.</p>
|
|
||||||
<p>Zacznijmy od kąta widzenia. Można go zmienić zwyczajnie ustawiając zamiast <span class="math inline">\(1\)</span> dowolną inną wartość <span class="math inline">\(S\)</span> parametrów <span class="math inline">\(m_{11}\)</span> i <span class="math inline">\(m_{22}\)</span>. Jednak jeśli chcemy uzyskać faktyczne parametry oparte na polu widzenia, musimy skorzystać ze wzoru:</p>
|
|
||||||
<p><span class="math display">\[S=\frac{1}{\tan(\frac{fov}{2}*\frac{\pi}{180})}\]</span></p>
|
|
||||||
<h3 id="zadanie-3">Zadanie</h3>
|
|
||||||
<p>Dodaj do <code>createPerspectiveMatrix</code> argument fov, który będzie ustalał kąt widzenia.</p>
|
|
||||||
<h3 id="zadanie-4">Zadanie</h3>
|
|
||||||
<p>Prawidłowe skalowanie okna uzyskamy poprzez mnożenie <span class="math inline">\(m_{22}\)</span> przez stosunek szerokości do wysokości ekranu. W zadaniu jest zmienna globalna <code>aspectRatio</code>. W funkcji <code>framebuffer_size_callback</code> nadpisz tą zmienną właściwym stosunkiem (pamiętaj, że dzielenie liczb stałoprzecinkowych w c++ nie uwzględnia ułamków). Następnie prześlij ja do <code>createPerspectiveMatrix</code> jako dodatkowy parametr i wykorzystaj przy renderowaniu sceny.</p>
|
|
||||||
<hr />
|
|
||||||
<h3 id="zadanie-5">Zadanie*</h3>
|
|
||||||
<p>Po dodaniu skalowania okna, poszerzanie kwadratowego okna będzie zmniejszać kąt widzenia na osi pionowej. Natomiast wydłużanie go będzie go zwiększać. Zaproponuj rozwiązanie, które sprawi, że poszerzanie kwadratowego okna będzie zwiększać kąt widzenia w osi poziomej i jednocześnie wydłużanie kwadratowego okna będzie zwiększać kąt widzenia w osi pionowej.</p>
|
|
||||||
<h2 id="macierz-widoku">Macierz widoku</h2>
|
|
||||||
<p>Celem macierzy widoku jest wprowadzenie pojęcia kamery, jako obiektu, który możemy ustawić i poruszać nim w przestrzeni. Na taką macierz składa się pozycja kamery kierunek patrzenia oraz jej orientacja: wektory <code>cameraDir</code> oraz <code>cameraUp</code> i <code>cameraSide</code>. W tym celu potrzebujemy jeden wektor, który będzie określał pozycję początku układu współrzędnych (czyli pozycję kamery). Oraz 3 wektory ortonormalne, które będą rozpinać przestrzeń (odpowiedzialne za kierunek i orientację). Ponieważ te wektory są ortogonalne, możemy je rekonstruować za pomocą dwóch wektorów, jeden będzie nam wskazywać kierunek patrzenia (<code>cameraDir</code>), drugi górę (<code>cameraUp</code>).</p>
|
|
||||||
<p><img src="./img/camera.jpg" /></p>
|
|
||||||
<blockquote>
|
|
||||||
<p>Układ współrzędnych kamery</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>W kodzie jest zaimplementowana obsługa klawiatury, klawisze <strong>W</strong> i <strong>S</strong> przesuwają kamerę do przodu i do tyłu, natomiast <strong>A</strong> i <strong>D</strong> obracają ją na boki. Robią to poprzez modyfikacje zmiennych globalnych <code>cameraDir</code> i <code>cameraPos</code>, jednak, żeby kamera faktycznie działała, trzeba uzupełnić funkcję <code>createCameraMatrix</code> i dodać jej wynik do transformacji obiektów.</p>
|
|
||||||
<h3 id="zadanie-6">Zadanie</h3>
|
|
||||||
<p>Uzupełnij funkcję <code>createCameraMatrix</code>. Najpierw oblicz wektor skierowany w bok za pomocą iloczynu wektorowego między <code>cameraDir</code> a wektorem <span class="math inline">\([0,1,0]\)</span>. Wektor może być długości innej niż 1, dlatego znormalizuj go. Zapisz wynik do <code>cameraSide</code>. Podobnie oblicz <code>cameraUp</code> jako znormalizowany iloczyn wektorowy między <code>cameraSide</code> i <code>cameraDir</code>.</p>
|
|
||||||
<blockquote>
|
|
||||||
<p>Wektor normalizuje się za pomocą funkcji: <code>glm::normalize</code></p>
|
|
||||||
</blockquote>
|
|
||||||
<p>Macierz kamery złożona jest z iloczynu macierzy obrotu i macierzy translacji. By otrzymać pierwszą z nich, korzystamy z ortonormalności bazy, dzięki temu wystarczy zapisać wektory <code>cameraSide</code>, <code>cameraUp</code> i <code>-cameraDir</code> wierszami. >Zauważ, że <code>cameraDir</code> musi być odwrócony tak jak na obrazku, ma być zwrócony do kamery, nie od niej. Macierz wygląda następująco:</p>
|
|
||||||
<p><span class="math display">\[M_{VR}=\begin{bmatrix} cameraSide_x & cameraSide_y & cameraSide_z & 0\\cameraUp_x & cameraUp_y & cameraUp_z & 0\\-cameraDir_x & -cameraDir_y & -cameraDir_z & 0\\0 & 0 & 0 & 1 \end{bmatrix}\]</span></p>
|
|
||||||
<p>Macierz translacji <span class="math inline">\(M_{VT}\)</span> otrzymujemy przez translacje o <code>-cameraPos</code>. Ostatecznie macierz widoku jest postaci <span class="math display">\[M_V=M_{VR}*M_{VT}\]</span></p>
|
|
||||||
<p>W macierzy <code>transformation</code> umieść pomnożone przez siebie macierze perspektywy (wynik funkcji <code>createPerspectiveMatrix</code>) i kamery (wynik funkcji <code>createCameraMatrix</code>) w odpowiedniej kolejności. Jako efektem otrzymamy pełen potok graficzny, czyli kamerę, którą możemy poruszać klawiszami, z poprawnym rzutowaniem perspektywicznym.</p>
|
|
||||||
<h3 id="zadanie-7">Zadanie</h3>
|
|
||||||
<p>Wyświetl dwa dodatkowe prostopadłościany w różnych pozycjach i orientacjach.</p>
|
|
||||||
<h3 id="zadanie-8">Zadanie*</h3>
|
|
||||||
<p>Zmodyfikuj ustawienia tak, żeby kamera zawsze była zwrócona w punkt wybrany punkt <span class="math inline">\(p\)</span> <span class="math inline">\((0,0,0)\)</span>. W funkcji <code>processInput</code> zakomentuj obsługę klawiszy <strong>A</strong> i <strong>D</strong>. Zamiast tego na końcu tej funkcji ustaw <code>cameraDir</code> jako znormalizowana różnicę między punktem <span class="math inline">\(p\)</span> a <code>cameraPos</code>. Jako obsługę klawiszy <strong>R</strong> i <strong>F</strong> dodaj przesuwanie kamery w górę i w dół.</p>
|
|
||||||
<h1 id="ładowanie-modeli-z-użyciem-assimp">Ładowanie modeli z użyciem assimp**</h1>
|
|
||||||
<p>W tym zadaniu przećwiczymy ładowanie modeli z plików, wykorzystamy do tego bibliotekę assimp (The Open Asset Import Library ), która zapewnia wspólny interfejs dla różnych typów plików.</p>
|
|
||||||
<p>Funkcja <code>loadModelToContext</code> pobiera ścieżkę do pliku z modelem i wczytuje go przy użyciu importera assimp.</p>
|
|
||||||
<div class="sourceCode" id="cb1"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="at">const</span> aiScene* scene = <span class="kw">import</span>.ReadFile(path, aiProcess_TriangulateaiProcess_Triangulate | aiProcess_CalcTangentSpace);</span></code></pre></div>
|
|
||||||
<p>Importer przyjmuje ścieżkę i flagi preprocesingu, które mówią, jakie operacje ma wykonać importer przed przekazaniem nam pliku. W naszym przypadku dokonuje triangularyzacji (zamienia wszystkie wielokąty na trójkąty) i oblicza przestrzeń styczną (o której będzie mowa później).</p>
|
|
||||||
<blockquote>
|
|
||||||
<p>Wywołaj funkcję dla ścieżki do statku <strong>./models/spaceship.obj</strong> i zmiennej globalnej <code>Core::RenderContext sphereContext</code>. Dodaj breakpoint po załadowaniu sceny i obejrzyj jak wygląda struktura załadowanego obiektu.</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>Załadowany obiekt posiada szereg pól jak na przykład tekstury, oświetlenia, materiały, węzły (<em>Node</em>) czy modele. Węzły odpowiadają za hierarchię elementów w modelu, co ułatwia jego animację, wykorzystamy to w późniejszych zajęciach. Nasze pliki składają się z tylko jednego modelu, dlatego nie musimy się na tym skupiać i wywołujemy tylko pierwszy model, do którego odwołujemy się za pomocą <code>scene->mMeshes[0]</code>. Wywołaj <code>context.initFromAiMesh</code> z nim jako argumentem.</p>
|
|
||||||
<h3 id="zadanie-9">Zadanie**</h3>
|
|
||||||
<p>Jeśli tego nie zrobiłeś wywołaj metodę <code>context.initFromAiMesh</code> z argumentem<code>scene->mMeshes[0]</code> w funkcji <code>init</code>, po wczytaniu modelu. Metoda nie jest kompletna, uzupełnij ją o ładowanie indeksów, wierzchołków, normalnych i współrzędnych tekstur do bufora. Współrzędne tekstur i indeksy zostały przekonwertowane do odpowiedniego formatu i znajdują się w zmiennych <code>std::vector<aiVector2D> textureCoord</code> i <code>std::vector<unsigned int> indices</code> odpowiednio. Pozostałe są dostępne jako atrybuty <code>aiMesh</code>, mianowicie <code>mesh->mVertices</code> zawiera wierzchołki a <code>mesh->mNormals</code> normalne.</p>
|
|
||||||
<p>Dodatkowo <code>mesh->mNumVertices</code> zawiera liczbę wierzchołków.</p>
|
|
||||||
<p>zawierają rozmiary buforów.</p>
|
|
||||||
<p>Utwórz jedną duża tablicę/vector, która zawiera informacje o wierzchołkach, normalnych i współrzędnych tekstur. Powinna mieć ona format jak na poniższym obrazku:</p>
|
|
||||||
<p><img src="https://i.imgur.com/WLAQtGH.jpg" /></p>
|
|
||||||
<p>Gdy załadujesz kontekst, wykorzystaj w <code>renderScene</code> funkcję <code>Core::DrawContext(Core::RenderContext& context)</code> do narysowania obiektów. Rozmieść statek i kulę w przestrzeni za pomocą macierzy transformacji i obrotu.</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,162 +0,0 @@
|
|||||||
# Ćwiczenia 3
|
|
||||||
## Przestrzenie potoku graficznego
|
|
||||||
W trakcie wykładu zostały opisane kolejne przestrzenie potoku graficznego.
|
|
||||||
W tej części zajęć przejdziemy kolejne jego etapy.
|
|
||||||
|
|
||||||
![](./img/coordinate_systems.jpg)
|
|
||||||
|
|
||||||
|
|
||||||
Pierwszy krok, czyli przejście do `World Space` już wykonujemy za pomocą macierzy transformacji. Następnym krokiem będzie stworzenie macierzy projekcji i macierzy widoku. Zaczniemy od macierzy projekcji. Będziemy modyfikować macierz, którą wysyła funkcja `createPerspectiveMatrix` (macierz jest transponowana dalej, więc zapisuj ją tak, jak tu widzisz). Zanim zaczniemy domnóż macierz perspektywy do macierzy transformacji obiektów.
|
|
||||||
|
|
||||||
**Nim przejdziemy dalej odkomentuj rysowanie prostopadłościanu.**
|
|
||||||
|
|
||||||
## Macierz perspektywy
|
|
||||||
|
|
||||||
Rozważmy mnożenie dowolnej macierzy przez wektor kolumnowy:
|
|
||||||
|
|
||||||
$$\begin{bmatrix} m_{11} & m_{12} & m_{13}& m_{14}\\m_{21} & m_{22} & m_{23}& m_{24}\\m_{31} & m_{32} & m_{33}& m_{34}\\m_{41} & m_{42} & m_{43}& m_{44}\\\end{bmatrix}\begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}=\begin{bmatrix} x*m_{11}+y*m_{12}+z*m_{13}+m_{14}\\x*m_{21} +y*m_{22} +z*m_{23} +m_{24}\\x*m_{31} +y*m_{32} +z*m_{33} +m_{34}\\x*m_{41} +y*m_{42} +z*m_{43} +m_{44}\\\end{bmatrix}$$
|
|
||||||
|
|
||||||
Pierwszym krokiem jest wykorzystanie homogenizacji do uzyskania efektu perspektywy. W tym celu musimy ustawić współrzędną $w$ na $-z$ za pomocą macierzy. Jeśli przyjrzymy się obliczeniom powyżej. Zobaczymy, że do tego musimy ustawić $m_{43}$ na $-1$ a pozostałe w tym wierszu na $0$. Przemnożenie daje nam:
|
|
||||||
|
|
||||||
|
|
||||||
$$\begin{bmatrix} 1 & 0 & 0& 0\\0 & 1 & 0& 0\\0 & 0 & 1& 0\\0 & 0 & -1& 0 \end{bmatrix}\begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}=\begin{bmatrix} x \\ y \\ z \\ -z\end{bmatrix}$$
|
|
||||||
|
|
||||||
|
|
||||||
Po homogenizacji otrzymamy wektor:
|
|
||||||
|
|
||||||
|
|
||||||
$$\begin{bmatrix} -\frac{x}{z} \\ -\frac{x}{z} \\ -1 \\ 1\end{bmatrix}$$
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
zmodyfikuj macierz perspektywy w taki sposób.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Wartość współrzędnej $z$ jest równa -1 dla każdego parametru. Co spowoduje, że nie będzie wiadomo, który wierzchołek bliżej, a który dalej i otrzymam zjawisko znane jako z-fighting. By tego uniknąć. Musimy zmapować współrzędną $z$.
|
|
||||||
Przypomnijmy, że przy arbitralnej macierzy wartość współrzędnej $z$ będzie następującej postaci:
|
|
||||||
|
|
||||||
$$ x*m_{31} +y*m_{32} +z*m_{33} +m_{34} $$
|
|
||||||
|
|
||||||
więc możemy pracować tylko z parametrami $m_{33}$ i $m_{34}$, czyli $z*m_{33} +m_{34}$, po uwzględnieniu dodatkowo homogenizacji otrzymamy ostateczny wzór:
|
|
||||||
|
|
||||||
$$ z'=-m_{33} -\frac{m_{34}}{z}.$$
|
|
||||||
|
|
||||||
Przypomnijmy, że w `Clipping Space` współrzędna $z$ musi się mapować na wartości od $-1$ do $1$, żeby znalazły się w bryle kanonicznej (obiekty poza bryłą kanoniczną nie będą wyświetlone). Jak na wykładzie określimy sobie parametry $0 < n < f$, które będą określać pozycję minimalnej i maksymalnej płaszczyzny osi $z$. Chcemy, żeby dla $z=n$ wartość $z'$ wynosiła $-1$ oraz dla $z=f$ wartość $z'$ wynosiła $1$, daje nam to układ równań:
|
|
||||||
|
|
||||||
|
|
||||||
$$\begin{matrix} -m_{33}& -&\frac{m_{34}}{n}&=&-1\\-m_{33} &-&\frac{m_{34}}{f}&=&1\end{matrix}$$
|
|
||||||
|
|
||||||
co po przekształceniu da nam:
|
|
||||||
|
|
||||||
|
|
||||||
$$\begin{matrix} m_{33}&=&\frac{(n + f)}{(n - f)}\\m_{34} &=&\frac{(2 n f)}{(n - f)}\end{matrix}$$
|
|
||||||
|
|
||||||
Ostatecznie otrzymujemy:
|
|
||||||
|
|
||||||
$$\begin{bmatrix} 1 & 0 & 0& 0\\0 & 1 & 0& 0\\0 & 0 & \frac{(n + f)}{(n - f)}& \frac{(2 n f)}{(n - f)}\\0 & 0 & -1& 0 \end{bmatrix}$$
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Zauważ, że te wartość zmienia się zgodnie ze wzorem $-\left(\frac{(n + f)}{(n - f)}+ \frac{(2 n f)}{z(n - f)}\right)$ czyli zmienia się to asymptotycznie, co można zobaczyć na wykresie.
|
|
||||||
|
|
||||||
![](./img/z_depth_graph.jpg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Zadanie*
|
|
||||||
Rozwiąż samodzielnie ten układ równań.
|
|
||||||
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
|
|
||||||
Dodaj zmienne lokalne `n` i `f` w funkcji, ustal im jakieś arbitralne wartości i dodaj rzutowanie $z$ zgodnie ze wzorem, który otrzymaliśmy. Spróbuj ustawić taką wartość `f`, żeby tylna ściana sześcianu zniknęła.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Uzyskana macierz daje nam rzutowanie perspektywiczne, ale możemy ją jeszcze rozbudować o zmianę kąta widzenia, a także naprawić problem z nieprawidłowym skalowaniem się ekranu przy zmianie jego proporcji. Obie te czynności sprowadzają się do tego samego, mianowicie chcemy zmienić kształt bryły widzenia w osiach $x$ i $y$. By tego dokonać, musimy zmienić wartość parametrów $m_{11}$ i $m_{22}$, to one odpowiadają za skalowanie w tych osiach. Parametry te ściskają lub rozszerzają przestrzeń w tych osiach, więc zmniejszenie wartości zwiększy kąt widzenia w danej osi.
|
|
||||||
|
|
||||||
Zacznijmy od kąta widzenia. Można go zmienić zwyczajnie ustawiając zamiast $1$ dowolną inną wartość $S$ parametrów $m_{11}$ i $m_{22}$. Jednak jeśli chcemy uzyskać faktyczne parametry oparte na polu widzenia, musimy skorzystać ze wzoru:
|
|
||||||
|
|
||||||
$$S=\frac{1}{\tan(\frac{fov}{2}*\frac{\pi}{180})}$$
|
|
||||||
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
Dodaj do `createPerspectiveMatrix` argument fov, który będzie ustalał kąt widzenia.
|
|
||||||
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
Prawidłowe skalowanie okna uzyskamy poprzez mnożenie $m_{22}$ przez stosunek szerokości do wysokości ekranu. W zadaniu jest zmienna globalna `aspectRatio`. W funkcji `framebuffer_size_callback` nadpisz tą zmienną właściwym stosunkiem (pamiętaj, że dzielenie liczb stałoprzecinkowych w c++ nie uwzględnia ułamków). Następnie prześlij ja do `createPerspectiveMatrix` jako dodatkowy parametr i wykorzystaj przy renderowaniu sceny.
|
|
||||||
|
|
||||||
----
|
|
||||||
### Zadanie*
|
|
||||||
Po dodaniu skalowania okna, poszerzanie kwadratowego okna będzie zmniejszać kąt widzenia na osi pionowej. Natomiast wydłużanie go będzie go zwiększać. Zaproponuj rozwiązanie, które sprawi, że poszerzanie kwadratowego okna będzie zwiększać kąt widzenia w osi poziomej i jednocześnie wydłużanie kwadratowego okna będzie zwiększać kąt widzenia w osi pionowej.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Macierz widoku
|
|
||||||
Celem macierzy widoku jest wprowadzenie pojęcia kamery, jako obiektu, który możemy ustawić i poruszać nim w przestrzeni. Na taką macierz składa się pozycja kamery kierunek patrzenia oraz jej orientacja: wektory `cameraDir` oraz `cameraUp` i `cameraSide`. W tym celu potrzebujemy jeden wektor, który będzie określał pozycję początku układu współrzędnych (czyli pozycję kamery). Oraz 3 wektory ortonormalne, które będą rozpinać przestrzeń (odpowiedzialne za kierunek i orientację). Ponieważ te wektory są ortogonalne, możemy je rekonstruować za pomocą dwóch wektorów, jeden będzie nam wskazywać kierunek patrzenia (`cameraDir`), drugi górę (`cameraUp`).
|
|
||||||
|
|
||||||
![](./img/camera.jpg)
|
|
||||||
|
|
||||||
> Układ współrzędnych kamery
|
|
||||||
|
|
||||||
W kodzie jest zaimplementowana obsługa klawiatury, klawisze **W** i **S** przesuwają kamerę do przodu i do tyłu, natomiast **A** i **D** obracają ją na boki. Robią to poprzez modyfikacje zmiennych globalnych `cameraDir` i `cameraPos`, jednak, żeby kamera faktycznie działała, trzeba uzupełnić funkcję `createCameraMatrix` i dodać jej wynik do transformacji obiektów.
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
Uzupełnij funkcję `createCameraMatrix`. Najpierw oblicz wektor skierowany w bok za pomocą iloczynu wektorowego między `cameraDir` a wektorem $[0,1,0]$. Wektor może być długości innej niż 1, dlatego znormalizuj go. Zapisz wynik do `cameraSide`. Podobnie oblicz `cameraUp` jako znormalizowany iloczyn wektorowy między `cameraSide` i `cameraDir`.
|
|
||||||
|
|
||||||
> Wektor normalizuje się za pomocą funkcji: `glm::normalize`
|
|
||||||
|
|
||||||
Macierz kamery złożona jest z iloczynu macierzy obrotu i macierzy translacji. By otrzymać pierwszą z nich, korzystamy z ortonormalności bazy, dzięki temu wystarczy zapisać wektory `cameraSide`, `cameraUp` i `-cameraDir` wierszami.
|
|
||||||
>Zauważ, że `cameraDir` musi być odwrócony tak jak na obrazku, ma być zwrócony do kamery, nie od niej. Macierz wygląda następująco:
|
|
||||||
|
|
||||||
|
|
||||||
$$M_{VR}=\begin{bmatrix} cameraSide_x & cameraSide_y & cameraSide_z & 0\\cameraUp_x & cameraUp_y & cameraUp_z & 0\\-cameraDir_x & -cameraDir_y & -cameraDir_z & 0\\0 & 0 & 0 & 1 \end{bmatrix}$$
|
|
||||||
|
|
||||||
Macierz translacji $M_{VT}$ otrzymujemy przez translacje o `-cameraPos`. Ostatecznie macierz widoku jest postaci
|
|
||||||
$$M_V=M_{VR}*M_{VT}$$
|
|
||||||
|
|
||||||
W macierzy `transformation` umieść pomnożone przez siebie macierze perspektywy (wynik funkcji `createPerspectiveMatrix`) i kamery (wynik funkcji `createCameraMatrix`) w odpowiedniej kolejności. Jako efektem otrzymamy pełen potok graficzny, czyli kamerę, którą możemy poruszać klawiszami, z poprawnym rzutowaniem perspektywicznym.
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
|
|
||||||
Wyświetl dwa dodatkowe prostopadłościany w różnych pozycjach i orientacjach.
|
|
||||||
|
|
||||||
### Zadanie*
|
|
||||||
Zmodyfikuj ustawienia tak, żeby kamera zawsze była zwrócona w punkt wybrany punkt $p$ $(0,0,0)$. W funkcji `processInput` zakomentuj obsługę klawiszy **A** i **D**. Zamiast tego na końcu tej funkcji ustaw `cameraDir` jako znormalizowana różnicę między punktem $p$ a `cameraPos`. Jako obsługę klawiszy **R** i **F** dodaj przesuwanie kamery w górę i w dół.
|
|
||||||
|
|
||||||
# Ładowanie modeli z użyciem assimp**
|
|
||||||
|
|
||||||
W tym zadaniu przećwiczymy ładowanie modeli z plików, wykorzystamy do tego bibliotekę assimp (The Open Asset Import Library ), która zapewnia wspólny interfejs dla różnych typów plików.
|
|
||||||
|
|
||||||
Funkcja `loadModelToContext` pobiera ścieżkę do pliku z modelem i wczytuje go przy użyciu importera assimp.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
const aiScene* scene = import.ReadFile(path, aiProcess_TriangulateaiProcess_Triangulate | aiProcess_CalcTangentSpace);
|
|
||||||
```
|
|
||||||
|
|
||||||
Importer przyjmuje ścieżkę i flagi preprocesingu, które mówią, jakie operacje ma wykonać importer przed przekazaniem nam pliku. W naszym przypadku dokonuje triangularyzacji (zamienia wszystkie wielokąty na trójkąty) i oblicza przestrzeń styczną (o której będzie mowa później).
|
|
||||||
|
|
||||||
> Wywołaj funkcję dla ścieżki do statku **./models/spaceship.obj** i zmiennej globalnej `Core::RenderContext sphereContext`. Dodaj breakpoint po załadowaniu sceny i obejrzyj jak wygląda struktura załadowanego obiektu.
|
|
||||||
|
|
||||||
Załadowany obiekt posiada szereg pól jak na przykład tekstury, oświetlenia, materiały, węzły (*Node*) czy modele. Węzły odpowiadają za hierarchię elementów w modelu, co ułatwia jego animację, wykorzystamy to w późniejszych zajęciach. Nasze pliki składają się z tylko jednego modelu, dlatego nie musimy się na tym skupiać i wywołujemy tylko pierwszy model, do którego odwołujemy się za pomocą `scene->mMeshes[0]`. Wywołaj `context.initFromAiMesh` z nim jako argumentem.
|
|
||||||
|
|
||||||
### Zadanie**
|
|
||||||
|
|
||||||
Jeśli tego nie zrobiłeś wywołaj metodę `context.initFromAiMesh` z argumentem`scene->mMeshes[0]` w funkcji `init`, po wczytaniu modelu. Metoda nie jest kompletna, uzupełnij ją o ładowanie indeksów, wierzchołków, normalnych i współrzędnych tekstur do bufora. Współrzędne tekstur i indeksy zostały przekonwertowane do odpowiedniego formatu i znajdują się w zmiennych `std::vector<aiVector2D> textureCoord` i ` std::vector<unsigned int> indices` odpowiednio. Pozostałe są dostępne jako atrybuty `aiMesh`, mianowicie `mesh->mVertices` zawiera wierzchołki a `mesh->mNormals` normalne.
|
|
||||||
|
|
||||||
Dodatkowo `mesh->mNumVertices` zawiera liczbę wierzchołków.
|
|
||||||
|
|
||||||
|
|
||||||
zawierają rozmiary buforów.
|
|
||||||
|
|
||||||
Utwórz jedną duża tablicę/vector, która zawiera informacje o wierzchołkach, normalnych i współrzędnych tekstur. Powinna mieć ona format jak na poniższym obrazku:
|
|
||||||
|
|
||||||
![](https://i.imgur.com/WLAQtGH.jpg)
|
|
||||||
|
|
||||||
|
|
||||||
Gdy załadujesz kontekst, wykorzystaj w `renderScene` funkcję `Core::DrawContext(Core::RenderContext& context)` do narysowania obiektów. Rozmieść statek i kulę w przestrzeni za pomocą macierzy transformacji i obrotu.
|
|
@ -1,116 +0,0 @@
|
|||||||
<!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 5</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>
|
|
||||||
<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="oświetlenie-phonga">Oświetlenie Phonga</h2>
|
|
||||||
<p>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 <code>ex_5_1.hpp</code> kod z poprzednich zajęć. W przeciwnym wypadku wykorzystaj ten, który jest zaimplementowany w <code>ex_5_1.hpp</code>. W zadaniu będzie nam potrzebny statek latający przed kamerą, jak nie ma tego w Twojej scenie to skopiuj to z <code>ex_5_1.hpp</code>.</p>
|
|
||||||
<h2 id="zadanie---diffuse">Zadanie - <em>diffuse</em></h2>
|
|
||||||
<p>Oblicz we fragment shaderze oświetlenie obiektu przy użyciu modelu Phonga dla odbicia rozproszonego (*diffuse)</p>
|
|
||||||
<p> 1. Przekaż źródło światła. Na razie przyjmiemy kierunkowy model oświetlenia, dlatego źródło będzie opisane jako wektor kierunkowy:</p>
|
|
||||||
<ul>
|
|
||||||
<li><p>Prześlij do shadera fragmentów zmienna typu <code>uniform vec3</code> (nazwij ją np. <code>lightDir</code>), w której będzie się znajdować wektor kierunkowy.</p></li>
|
|
||||||
<li><p>Należy to zrobić podobnie do tego, jak przesyłany jest kolor.</p></li>
|
|
||||||
<li><p>Jako kierunek światła wybierz dowolny wektor jednostkowy. (Możesz wziąć dowolny niezerowy wektor następnie go znormalizować).</p></li>
|
|
||||||
<li><p>Dodatkowo prześlij drugą zmienną <code>uniform vec3 lightColor</code>, w której umieścimy kolor światła. Prześlij tam wartości ze zmiennej <code>glm::vec3 lightColor</code>.</p></li>
|
|
||||||
</ul>
|
|
||||||
<ol start="2" type="1">
|
|
||||||
<li>Oblicz natężenie w shaderze fragmentów:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li><p>prześlij normalne z shadera fragmentów do shadera wierzchołków</p></li>
|
|
||||||
<li><p>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).</p></li>
|
|
||||||
<li><p>Natężenie to iloczyn skalarny wektora normalnego powierzchni i odwrotnego wektora kierunku padania światła. Skorzystaj z funkcji <code>dot</code>.</p></li>
|
|
||||||
<li><p>Natężenie nie może być ujemne. Przytnij natężenie do zera przy użyciu: <code>x = max(x, 0.0)</code></p></li>
|
|
||||||
</ul>
|
|
||||||
<ol start="3" type="1">
|
|
||||||
<li>Zastosuj obliczone natężenie, aby zmodyfikować kolor obiektu:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Przemnóż kolor RGB fragmentu przez obliczone natężenie i przez kolor światła z <code>lightColor</code>.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="zadanie---obsługa-obrotów">Zadanie - obsługa obrotów</h2>
|
|
||||||
<p>Dlaczego oświetlenie statku nie zmienia się podczas jego obracania?</p>
|
|
||||||
<p>(Wektory normalne są w układzie lokalnym modelu, a wektor padania światła w układzie świata)</p>
|
|
||||||
<p>Należy wykonać transformacje wektorów normalnych do przestrzeni świata: - Prześlij macierz modelu rysowanego obiektu (<em>model Matrix</em>) jako osobna zmienna do vertex shadera (<code>uniform mat4</code>).<br />
|
|
||||||
- Przemnóż przez te macierz wektor normalny wierzchołka przed przesłaniem go do shadera fragmentów. - Współrzędna <strong>w</strong> 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.</p>
|
|
||||||
<h2 id="zadanie---specular">Zadanie - <em>specular</em></h2>
|
|
||||||
<p>Uzupełnił model o czynnik odbicia zwierciadlanego (<em>specular</em>). W tym celu:</p>
|
|
||||||
<ol type="1">
|
|
||||||
<li>Potrzebny będzie wektor od rysowanego fragmentu do pozycji kamery:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Wyślij pozycje kamery (<code>cameraPos</code>) jako kolejna zmienna do fragment shadera.<br />
|
|
||||||
</li>
|
|
||||||
<li>Podobnie jak wektory normalne prześlij z vertex do fragment shadera pozycje wierzchołków (<code>vertexPosition</code>) w przestrzeni świata (czyli pomnożone przez macierz <strong>modelMatrix</strong>). Pamiętaj, że tym razem wektory reprezentują punkty, a nie kierunki - współrzędna <strong>w</strong> przed mnożeniem musi być ustawiona na 1. W wyniku rasteryzacji otrzymamy w shaderze fragmentu jego pozycję (nazywaną pozycją fragmentu)</li>
|
|
||||||
<li>Oblicz wektor <strong>V</strong> (<em>view direction</em>) jako znormalizowaną różnice pozycji kamery i pozycji fragmentu.</li>
|
|
||||||
</ul>
|
|
||||||
<ol start="2" type="1">
|
|
||||||
<li>Oblicz natężenie światła odbitego we <em>fragment shaderze</em>:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Oblicz wektor kierunku odbicia światła <strong>R</strong> przy użyciu funkcji <a href="https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/reflect.xhtml"><code>reflect</code></a>. Pamiętaj, żeby przesłać do funkcji odwrócony wektor kierunku światła.</li>
|
|
||||||
<li>Oblicz natężenie: iloczyn skalarny <strong>V</strong> i <strong>R</strong>, przycięty do zera ( <code>max(...,0.0)</code> ), a następnie podniesiony do wysokiej potęgi (np. 8, 50, 1000), która jest miara połyskliwości powierzchni.</li>
|
|
||||||
</ul>
|
|
||||||
<ol start="3" type="1">
|
|
||||||
<li>Ustal ostateczny kolor piksela na <code>objectColor * diffuse + lightColor * specular</code>. Oznacza to najprostszy przypadek, gdy kolor światła odbitego jest biały.</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="zadanie---oświetlenie-punktowe">Zadanie - oświetlenie punktowe</h2>
|
|
||||||
<p>W układzie planetarnym obiektem oświetlającym powinno być słońce, dlatego zamień oświetlenie kierunkowe na punktowe:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Zamiast przesyłać (w <code>lightDir</code>) 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 <code>lightPos</code>).</li>
|
|
||||||
<li>Oblicz kierunek światła odejmując od pozycji światła pozycję fragmentu, znormalizuj wynik. Zapisz wynik w zmiennej <code>lightDir</code>.<br />
|
|
||||||
</li>
|
|
||||||
<li>Słońce będzie czarne, nie martw się tym, tylko spójrz na punkt następny.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="zadanie---shader-słońca">Zadanie - shader słońca</h2>
|
|
||||||
<p>Ź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.</p>
|
|
||||||
<p>Celem tego zadania jest stworzenie shadera (<strong>shader_5_sun.vert</strong>_ i <strong>shader_5_sun.frag</strong>), który będzie odpowiadał wyłącznie za rysowanie słońca. Poprzednie shadery (<strong>shader_4_1.vert</strong>_ i <strong>shader_5_1.frag</strong>) nadal mają rysować pozostałe obiekty. a) zainicjalizuj <em>program</em> (shadery): - Pliki <strong>shader_5_sun.vert</strong> i <strong>shader_5_sun.frag</strong> są identyczne z 5_1** przed zmianami, będą punktem wyjścia dla <em>shadera</em> słońca.</p>
|
|
||||||
<ol type="1">
|
|
||||||
<li><p>Utwórz zmienną globalną <code>GLuint programSun</code> na adres shadera słońca. Stwórz <em>program</em> za pomocą <code>shaderLoader.CreateProgram</code> analogicznie jak tworzy się <code>program</code> z <strong>shader_5_1.vert</strong> i <strong>shader_5_1.frag</strong> (parametry wejściowe to ścieżka do shadera wierzchołków i fragmentów <strong>shader_5_sun.vert</strong> i <strong>shader_5_sun.frag</strong>).</p></li>
|
|
||||||
<li><p>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 <em>program</em> (shadery) do rysowania słońca:</p></li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>funkcja <code>drawObject</code> korzysta z globalnej zmiennej <code>program</code>, żeby wskazywać shadery do rysowania. Dodaj argument do funkcji, który będziesz przekazywać adres <em>programu</em>, który ma być wykorzystany do rysowania.</li>
|
|
||||||
<li>dodaj odpowiedni <em>program</em> w wywołaniach <code>drawObject</code>.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id="zadanie---osłabienie-światła-attenuation"> Zadanie - Zmień shader słońca na bardziej realistyczny </h2>
|
|
||||||
|
|
||||||
<p>Na poniższym obrazku jest zdjęcie słońca. Jest ono ciemniejsze na brzegach spróbuj uzyskać podobny efekt. Przydadzą się wektory normalnych i wektor <strong>V</strong> jak i funkcja mix.</p>
|
|
||||||
<p><img src="./img/sun.png" /></p>
|
|
||||||
|
|
||||||
<h2 id="osłabienie-światła-tone-mapping">Osłabienie światła, tone mapping</h2>
|
|
||||||
<h3 id="zadanie---osłabienie-światła-attenuation">Zadanie - Osłabienie światła (attenuation)</h3>
|
|
||||||
<p>Ś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. Przed kwadratowaniem przemnoz diystans przez 10.0.</p>
|
|
||||||
|
|
||||||
<h4 id="tone-mapping">Tone mapping</h4>
|
|
||||||
<p>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 <span class="math inline">\([0,1]\)</span> (obsługą wyższych wartości nazywamy HDR). Rozwiązaniem jest pracowanie na wartościach powyżej 1 wykorzystanie <em>tone mappingu</em> do przeniesienia ich w zakres <span class="math inline">\([0,1]\)</span>. Istnieje wiele wzorów, które są wykorzystywane do tego, jeden z nich to:</p>
|
|
||||||
<p><span class="math display">\[C_{mapped} = 1-e^{-C * E},\]</span> gdzie C to kolor sceny a E to parametr ekspozycji (z zakresu <span class="math inline">\((0,\infty)\)</span>, który może być dostosowany w zależności od jasności.
|
|
||||||
|
|
||||||
<h3 id="zadanie">Zadanie - Tone mapping</h3>
|
|
||||||
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.</p>
|
|
||||||
<h3 id="zadanie">Zadanie*</h3>
|
|
||||||
<p>Dodaj drugie źródło oświetlenia w postaci reflektora statku. Reflektor świeci tylko w określonym stożku,dlatego oprócz pozycji <code>spotPos</code> posiada również kierunek <code>spotDir</code> i kąt świecenia <span class="math inline">\(\phi\)</span>. Po obliczeniu dla niego <code>lightDir</code> należy sprawdzić, czy iloczyn skalarny pomiędzy <code>lightDir</code> a <code>spodDir</code> jest większy niż <span class="math inline">\(\cos\phi\)</span> . 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.</p>
|
|
||||||
Zwróć uwagę, że SpotDir to co innego niż light Dir w poprzednich zadaniach.
|
|
||||||
<p><img src="./img/spotlight.png" /> </p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,88 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="pl" xml:lang="pl">
|
|
||||||
<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 6</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>
|
|
||||||
<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>
|
|
||||||
<h1 id="teksturowanie">Teksturowanie</h1>
|
|
||||||
<p>Te zajęcia są kontynuacją poprzednich. Zalecam wykorzystać ukończony kod z poprzednich zajęć. Projekt startowy to tylko częściowo ukończone zadania.</p>
|
|
||||||
<h3 id="zadanie">Zadanie</h3>
|
|
||||||
<p>Obejrzyj plik <strong>Texture.h</strong>, aby zapoznać się z interfejsem do obsługi tekstur.</p>
|
|
||||||
<h3 id="przygotuj-shadery-i-załaduj-teksturę">Przygotuj shadery i załaduj teksturę:</h3>
|
|
||||||
<p>W kolejnych zadaniach będziemy potrzebować trzeciej pary shaderów: <strong>shader_5_tex.vert</strong> i <strong>shader_5_tex.frag</strong>. Są to gotowe shadery z poprzednich zadań. Wczytaj shadery i przechowuj adres programu pod zmienną globalną <code>programTex</code>.</p>
|
|
||||||
<p>Pod <code>namespace texture</code> znajdują się zmienne globalne typu <code>GLuint</code>, które będą przechowywać adresy tekstur. W funkcji <code>init()</code> załaduj tekstury z folderu <strong>textures</strong> przy pomocy funkcji <code>Core::LoadTexture</code>. Ta funkcja zwraca adres wczytanej tekstury, przypisz je do zmiennych w <code>namespace texture</code>. (Aby odwołać się do <code>grid</code> w <code>namespace</code> <code>texture</code>, użyj <code>texture::grid</code>.)</p>
|
|
||||||
<p>Stwórz funkcję odpowiedzialną za rysowanie teksturowanych obiektów. Skopiuj funkcję <code>drawObjectColor</code>. Nazwij kopię <code>drawObjectTexture()</code> i zmodyfikuj ją tak, aby przyjmowała jako parametr identyfikator tekstury, a nie wektor koloru. (Usuń w niej linijkę odpowiadającą za przesyłanie koloru, aby uniknąć błędu komunikacji, później dodamy ładowanie tekstury na jej miejsce.)</p>
|
|
||||||
<h3 id="zmodyfikuj-shader-tak-aby-nakładał-teksturę-na-obiekt">Zmodyfikuj shader, aby nakładał teksturę na obiekt:</h3>
|
|
||||||
<p>Narysuj jedną z planet za pomocą <code>drawObjectTexture()</code>, użyj <code>programTex</code> jako programu.</p>
|
|
||||||
<ol start="2" type="a">
|
|
||||||
<li>Prześlij współrzędne mapowania tekstur z <em>vertex shadera</em> do <em>fragment shadera</em>.</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Współrzędne tekstur to kolejny (po pozycjach i wektorach normalnych) atrybut wierzchołków - są dostępne w <em>vertex shaderze</em> pod nazwą <code>vertexTexCoord</code>.</li>
|
|
||||||
<li>Prześlij je znanym już sposobem do <em>fragment shadera</em> (zmienna <code>out</code> w <em>vertex shaderze</em> i odpowiadająca jej zmienna <code>in</code> we <em>fragment shaderze</em>).</li>
|
|
||||||
</ul>
|
|
||||||
<ol start="3" type="a">
|
|
||||||
<li>Prześlij teksturę do fragment shadera:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Stwórz zmienną typu <code>uniform sampler2D</code> (nazwij ją na przykład <code>colorTexture</code>) we fragment shaderze - analogicznie do innych zmiennych typu uniform, służy ona do przesyłania informacji bezpośrednio z kodu C++ do shadera.</li>
|
|
||||||
<li>W kodzie C++ użyj funkcji <code>Core::SetActiveTexture</code> wewnątrz funkcji <code>drawObjectTexture()</code>, aby ustawić zmienną <code>sampler2D</code> na wczytaną wcześniej teksturę.</li>
|
|
||||||
</ul>
|
|
||||||
<ol start="4" type="a">
|
|
||||||
<li>Użyj wartości uzyskanej z tekstury zamiast koloru (<code>objectColor</code>) do pokolorowania obiektu:</li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Wykonaj próbkowanie tekstury we współrzędnych otrzymanych przez fragment shader: <code>vec4 textureColor = texture2D(~nazwaZmiennejSampler2D, ~nazwaZmiennejWspolrzedneTekstury)</code> (gdzie <code>vec4</code> zawiera kolor RGBA).</li>
|
|
||||||
<li>Użyj pierwszych trzech współrzędnych (RGB) uzyskanego wektora jako nowego koloru bazowego piksela.</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="pobaw-się-mechanizmem-teksturowania">Pobaw się mechanizmem teksturowania:</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Przemnóż jedną lub obie ze współrzędnych mapowania przez 5 i sprawdź, co się stanie.</li>
|
|
||||||
<li>Wypróbuj pozostałe tekstury: <strong>grid_color.png</strong>, <strong>earth.png</strong>.</li>
|
|
||||||
<li>Tekstury Ziemi wyświetlają się "do góry nogami". Napraw to.</li>
|
|
||||||
<li>Jeśli chcesz mieć kilka planet o różnych teksturach, możesz skorzystać z <a href="https://www.solarsystemscope.com/textures/">tego linku</a> lub <a href="https://stevealbers.net/albers/sos/sos.html">tego linku</a>.</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="zadanie-1">Zadanie *</h3>
|
|
||||||
<p>Nadaj jakieś tekstury wszystkim obiektom, które znajdują się w scenie.</p>
|
|
||||||
<h2 id="multitexturing">Multitexturing</h2>
|
|
||||||
<p>W tej części pokażemy, jak wykorzystać więcej niż jedną teksturę przy rysowaniu obiektu na przykładzie zachmurzonej Ziemi.</p>
|
|
||||||
<h3 id="zadanie-2">Zadanie</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Utwórz kolejną parę shaderów, nazwij je <code>shader_earth</code>, skopiuj poprzednie shadery, jakie wykorzystywaliśmy,</li>
|
|
||||||
<li>dodaj w nich nowy <code>uniform sampler2D</code> nazwij go <code>clouds</code>,</li>
|
|
||||||
<li>użyj do rysowania ziemi nowo stworzonego shadera,</li>
|
|
||||||
<li>wyślij dodatkowo teksturę chmur, która znajduje się pod plikiem <code>clouds.png</code>, ustaw indeks tekstury na <strong>2</strong>,</li>
|
|
||||||
<li>by sprawdzić, czy całość się udała, użyj chmury jako kolory.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Wykorzystamy teksturę chmur jako maskę. Ustaw kolor Ziemi jako mix między białym a kolorem tekstury Ziemi, a za współczynnik mieszania weź kanał <code>r</code> tekstury chmur.</p>
|
|
||||||
<h3 id="zadanie-3">Zadanie*</h3>
|
|
||||||
<p>W podobny sposób wykorzystaj tekstury <code>ship.jpg</code>, <code>scratches.jpg</code> i <code>rust.jpg</code>, żeby dodać zarysowania do statku. Załaduj wszystkie trzy tekstury, użyj <code>scratches.jpg</code> jako maski, która będzie wskazywała współczynnik, którym będziemy mieszać pomiędzy dwoma pozostałymi teksturami.</p>
|
|
||||||
<h2 id="teksturowanie-proceduralne">Teksturowanie proceduralne</h2>
|
|
||||||
<ol type="a">
|
|
||||||
<li><p>Stwórz czwartą parę plików z shaderami (np. <strong>shader_proc_tex.vert</strong> i <strong>shader_proc_tex.frag</strong>). Następnie zainicjalizuj program jak w I.6.a) i II.1.a) (nazwij go np <code>programProcTex</code>). Do wywołania rysowania wykorzystuj funkcję <code>drawObject</code> (Żeby nie tracić oteksturowanych planet, możesz rysować za ich pomocą wyłącznie statek).</p></li>
|
|
||||||
<li><p>Prostym sposobem proceduralnego teksturowania, jest uzależnienie koloru od pozycji piksela w przestrzeni lokalnej (użycie przestrzeni świata spowodowałoby, że wzór na obiekcie zmieniałby się przy poruszaniu go).</p></li>
|
|
||||||
</ol>
|
|
||||||
<ul>
|
|
||||||
<li>Prześlij z vertex shadera do fragment shadera pozycję wierzchołka w przestrzeni lokalnej (czyli tej, w której wyrażone są atrybuty wierzchołka - nie trzeba więc wykonywać żadnej transformacji macierzowej).</li>
|
|
||||||
<li>We fragment shaderze oblicz sinus współrzędnej y pozycji piksela.</li>
|
|
||||||
<li>Jeżeli sinus jest większy od zera, to ustaw bazowy kolor obiektu na wybrany kolor, a jeśli jest mniejszy od zera, to na inny kolor.</li>
|
|
||||||
<li>Możesz przesłać te kolory przy użyciu zmiennych uniform z kodu C++ - pozwoli to rysować różne obiekty z różnymi parami kolorów.</li>
|
|
||||||
<li>Poeksperymentuj z innymi metodami teksturowania proceduralnego podanymi na wykładzie.</li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,67 +0,0 @@
|
|||||||
# Teksturowanie
|
|
||||||
Te zajęcia są kontynuacją poprzednich Zalecam wykorzystać ukończony kod z poprzednich zajęć. Projekt startowy jest tylko częściowo ukończonymi zadaniami
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
Obejrzyj plik **Texture.h**, aby zapoznać się z interfejsem do obsługi tekstur.
|
|
||||||
|
|
||||||
|
|
||||||
### Przygotuj shadery i załaduj teksturę:
|
|
||||||
W kolejnych zadaniach będziemy potrzebować trzecią parę shaderów: **shader_5_tex.vert** i **shader_5_tex.frag** są one gotowymi shaderami z poprzednich zadań. Wczytaj shadery i przechowaj adres programu pod zmienną globalną `programTex`.
|
|
||||||
|
|
||||||
Pod `namespace texture` znajdują się zmienne globalne typu `Gluint`, które będą przechowywać adresy tekstur. W funkcji `init()` załaduj tekstury z folderu **textures** przy pomocy `Core::LoadTexture`. Ta funkcja zwraca adres do wczytanej tekstury, przypisz je do zmiennych z `namespace texture`. (odwołanie się do `namespace` wykonuje się za pomocą ::, czyli by odwołać się do `grid` w `namespace` `texture` należy napisać `texture::grid`)
|
|
||||||
|
|
||||||
Stwórz funkcje odpowiadająca za rysowanie teksturowanych obiektów. Skopiuj funkcję `drawObjectColor`. Nazwij kopię `drawObjectTexture()` i zmodyfikują ją tak, aby przyjmowała jako parametr identyfikator tekstury, a nie wektor koloru. (Usuń w niej linijkę odpowiadającą za przesyłanie koloru, żeby uniknąć błędu komunikacji, później dodamy na jej miejsce ładowanie tekstury).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Zmodyfikuj shader tak, aby nakładał teksturę na obiekt:
|
|
||||||
Narysuj jedną z planet za pomocą `drawObjectTexture()`, użyj `programTex` jako programu.
|
|
||||||
|
|
||||||
b) Prześlij współrzędne mapowania tekstur z *vertex shadera* do *fragment shadera*
|
|
||||||
- Współrzędne tekstur to kolejny (po pozycjach i wektorach normalnych) atrybut wierzchołków - są dostępne w *vertex shaderze* pod nazwą `vertexTexCoord`
|
|
||||||
- Prześlij je znanym już sposobem do *fragment shadera* (zmienna `out` w *vertex shaderze* i odpowiadająca jej zmienna `in` we *fragment shaderze*)
|
|
||||||
|
|
||||||
c) Prześlij teksturę do fragment shadera:
|
|
||||||
- Stwórz zmienną typu `uniform sampler2D` (nazwij ją na przykład `colorTexture`) we fragment shaderze - analogicznie do innych zmiennych typu uniform, służy ona do przesyłania informacji bezpośrednio z kodu C++ do shadera
|
|
||||||
- Po stronie kodu C++ użyj funkcji `Core::SetActiveTexture` wewnątrz `drawObjectTexture()` aby ustawić zmienną `sampler2D` na wczytaną wcześniej teksturę.
|
|
||||||
|
|
||||||
d) Użyj wartości uzyskanej z tekstury zamiast koloru (`objectColor`) do pokolorowania obiektu:
|
|
||||||
- Wykonaj próbkowanie tekstury we współrzędnych otrzymanych przez fragment shader: `vec4 textureColor = texture2D(~nazwaZmiennejSampler2D, ~nazwaZmiennejWspolrzedneTekstury)`" (`vec4` zawiera kolor RGBA)
|
|
||||||
- Użyj pierwszych trzech współrzędnych (RGB) uzyskanego wektora jako nowego koloru bazowego piksela.
|
|
||||||
|
|
||||||
|
|
||||||
### Pobaw się mechanizmem teksturowania:
|
|
||||||
- Przemnóż jedną lub obie ze współrzędnych mapowania przez 5 i sprawdź, co się stanie.
|
|
||||||
- Wypróbuj pozostałe tekstury: **grid_color.png**, **earth.png**.
|
|
||||||
- Tekstury Ziemi wyświetlają się "do góry nogami". Napraw to.
|
|
||||||
- Jeśli chcesz mieć kilka planet o różnych teksturach, możesz skorzystać z [link](https://www.solarsystemscope.com/textures/) lub [link2](https://stevealbers.net/albers/sos/sos.html).
|
|
||||||
|
|
||||||
### Zadanie *
|
|
||||||
Nadaj jakieś tekstury wszystkim obiektom, które znajdują się w scenie.
|
|
||||||
|
|
||||||
## Multitexturing
|
|
||||||
W tej części pokażemy jak wykorzystać więcej niż jedną teksturę przy rysowaniu obiektu na przykładzie zachmurzonej Ziemi.
|
|
||||||
|
|
||||||
### Zadanie
|
|
||||||
- Utwórz kolejną parę shaderów, nazwij je `shader_earth`, skopiuj poprzednie shadery, jakie wykorzystywaliśmy,
|
|
||||||
- dodaj w nich nowy `uniform sampler2D` nazwij go `clouds`,
|
|
||||||
- użyj do rysowania ziemi nowo stworzonego shadera,
|
|
||||||
- wyślij dodatkowo teksturę chmur, która znajduje się pod plikiem `clouds.png`, ustaw indeks tekstury na **2**,
|
|
||||||
* by sprawdzić, czy całość się udała, użyj chmury jako kolory.
|
|
||||||
|
|
||||||
Wykorzystamy teksturę chmur jako maskę. Ustaw kolor Ziemi jako mix między białym a kolorem tekstury Ziemi a za współczynnik mieszania weź kanał `r` tekstury chmur.
|
|
||||||
|
|
||||||
### Zadanie*
|
|
||||||
W podobny sposób wykorzystaj tekstury `ship.jpg`, `scratches.jpg` i `rust.jpg`, żeby dodać zarysowania do statku. Załaduj wszystkie trzy tekstury, użyj `scratches.jpg` jako maskę, która będzie wskazywała współczynnik, którym będziemy mieszać pomiędzy dwoma pozostałymi teksturami.
|
|
||||||
|
|
||||||
## Teksturowanie proceduralne
|
|
||||||
|
|
||||||
a) Stwórz czwartą parę plików z shaderami (np. **shader_proc_tex.vert** i **shader_proc_tex.frag**). Następnie zainicjalizuj program jak w I.6.a) i II.1.a) (nazwij go np `programProcTex`). Do wywołania rysowania wykorzystuj funkcję `drawObject` (Żeby nie tracić oteksturowanych planet, możesz rysować za ich pomocą wyłącznie statek).
|
|
||||||
|
|
||||||
b) Prostym sposobem proceduralnego teksturowania, jest uzależnienie koloru od pozycji piksela w przestrzeni lokalnej (użycie przestrzeni świata spowodowałoby, że wzór na obiekcie zmieniałby się przy poruszaniu go).
|
|
||||||
- Prześlij z vertex shadera do fragment shadera pozycję wierzchołka w przestrzeni lokalnej (czyli tej, w której wyrażone są atrybuty wierzchołka - nie trzeba więc wykonywać żadnej transformacji macierzowej).
|
|
||||||
- We fragment shaderze oblicz sinus współrzędnej y pozycji piksela.
|
|
||||||
- Jeżeli sinus jest większy od zera, to ustaw bazowy kolor obiektu na wybrany kolor, a jeśli jest mniejszy od zera, to na inny kolor.
|
|
||||||
- Możesz przesłać te kolory przy użyciu zmiennych uniform z kodu C++ - pozwoli to rysować różne obiekty z różnymi parami kolorów.
|
|
||||||
- Poeksperymentuj z innymi metodami teksturowania proceduralnego podanymi na wykładzie.
|
|
||||||
|
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.4.33213.308
|
VisualStudioVersion = 17.4.33213.308
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Planeta", "cw 6\grk-cw6.vcxproj", "{3952C396-B1C6-44CD-96DD-C1AC15D32978}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Planeta", "planet-editor\grk-planet-editor.vcxproj", "{3952C396-B1C6-44CD-96DD-C1AC15D32978}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<ProjectGuid>{3952C396-B1C6-44CD-96DD-C1AC15D32978}</ProjectGuid>
|
<ProjectGuid>{3952C396-B1C6-44CD-96DD-C1AC15D32978}</ProjectGuid>
|
||||||
<Keyword>Win32Proj</Keyword>
|
<Keyword>Win32Proj</Keyword>
|
||||||
<RootNamespace>grk-cw6</RootNamespace>
|
<RootNamespace>grk-planet-editor</RootNamespace>
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
<ProjectName>Planeta</ProjectName>
|
<ProjectName>Planeta</ProjectName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -136,7 +136,6 @@
|
|||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<LanguageStandard>stdcpp14</LanguageStandard>
|
<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>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|