---
title: Modelování a projekce
description: A guide in my new Starlight docs site.
---

> [!NOTE]
> Homogenní souřadnice, modelovací, pohledová a projekční matice, perspektivní a ortografická projekce. Základní afinní transformace.
>
> _PV189, PV112_

## Souřadnicové systémy

- **Right-hand rule**
  Mnemotechnická pomůcka pro určení orientace os v kartézské soustavě souřadnic. Taky se používá pro určení směru vektorového součinu.

  ![Right-hand rule](./img/szp08_right_hand_rule.svg)

  > [!TIP]
  > Osa X je dána ukazováčkem, osa Y prostředníčkem, osa Z palcem. Pokud Y míří nahoru, pak ano, člověk si u toho může vykroutit ruku, ale alespoň si to zapamatuje.

- **Kartézská soustava souřadnic**
  Right-handed systém definován třemi kolmými osami. Ve 2D jsou to $x$ a $y$. Ve 3D jsou to $x$, $y$ a $z$. Jsou na sebe v zájemně kolmé. Počátek je v bodě, kde se protínají všechny osy, označovaném jako $0$.
- **Homogenní souřadnice**
  Hack, kdy reprezentujeme souřadnici v 3D prostoru pomocí 4 čísel, abychom mohli zapsat translaci pomocí matice. Využívá se v projektivní geometrii, pro projekci 3D scén na 2D plochu.

  Převod z kartézských na homogenní souřadnice: $(x, y, z) \to (x, y, z, 1)$.

  Převod z homogenních na kartézské souřadnice: $(x, y, z, w) \to (\frac{x}{w}, \frac{y}{w}, \frac{z}{w})$.

  Body, kde $w = 0$ jsou body v nekonečnu. Využívá se pro popis pohybu k nekonečnu, který se v kartézských souřadnicích nedá popsat.

## MVP matice

> [!IMPORTANT]
> Pro implementaci v OpenGL viz [Renderování s využitím GPU](../vph07_gpu_rendering/).

> [!WARNING]
> Při zápisu matic bacha na to, jestli jsou row-major nebo column-major. Třeba v OpenGL to znamená, že se všechny matice píší v transponované podobě, jelikož OpenGL je column-major a v takovém pořádí jsou i parametery `mat2`, `mat3` a `mat4` V GLSL.

- **Modelovací matice $M$**
  Převádí souřadnice z prostoru objektu (local space) do prostoru světa (world space). Využívá se pro rotaci ($R$), škálování ($S$) a translaci ($T$) objektu.

  ```math
  M = T \cdot R \cdot S
  ```

- **Pohledová matice / view matrix $V$**\
  Převádí souřadnice z prostoru světa (world space) do prostoru před kamerou (camera space). Otáčí světem, aby kamera byla jeho středem.[^camera]

  ```math
  V = \begin{bmatrix}
      \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \\
      \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \\
      \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \\
      0 & 0 & 0  & 1
  \end{bmatrix}
  \cdot
  \begin{bmatrix}
      1 & 0 & 0 & -\color{purple}{P_x} \\
      0 & 1 & 0 & -\color{purple}{P_y} \\
      0 & 0 & 1 & -\color{purple}{P_z} \\
      0 & 0 & 0  & 1
  \end{bmatrix}
  ```

  kde:

  - $\color{red}{R}$ je vektor, který ukazuje doprava od kamery.
  - $\color{green}{U}$ je vektor, který ukazuje nahoru od kamery.
  - $\color{blue}{D}$ je vektor, který ukazuje dopředu od kamery.
  - $\color{purple}{P}$ je pozice kamery.

    > [!NOTE]
    > Všimni si, že levá matice je transponovaná a poziční vektor v pravé matici je negovaný. Je to proto, že otáčíme a posouváme celým světem tak, aby kamera byla v počátku, musíme proto provést inverzní operace vůči těm, které chceme provést s kamerou. [^camera]

- **Frustum**
  Část 3D tělesa (nejčastěji pyramidy nebo jehlanu) mezi dvěma rovnoběžnými rovinami. Doslovný překlad je _"komolý jehlan"_
- **Projekční matice / projection matrix $P$**
  Převádí souřadnice z prostoru před kamerou (camera space) do clip space.

  Používá se zejména ortografická projekce ($P_\text{ortho}$) a perspektivní projekce ($P_\text{persp}$).

- **MVP matice**
  Pro převod modelu z jeho lokálního prostoru do clip space použijeme:

  ```math
  \vec{v}_\text{clip} = P \cdot V \cdot M \cdot \vec{v}_\text{local}
  ```

## Projekce

### Ortografická projekce

Používáme především k vykreslení 2D scén. Osu Z můžeme využít, abychom jeden sprite schovali za jiný. Nicméně objekty dál od kamery jsou stejně velké jako ty blízko kamery. [^camera]

![width=500](./img/szp08_orthographic_projection.png)

Je dána 6 parametry:

- $\text{left}$ - levá hranice (X),
- $\text{right}$ - pravá hranice (X),
- $\text{bottom}$ - spodní hranice (Y),
- $\text{top}$ - horní hranice (Y),
- $\text{near}$ - blízká hranice (Z),
- $\text{far}$ - daleká hranice (Z).

Společně definují boxík, kde je $(\text{left}, \text{bottom}, -\text{near})$ levý spodní roh a $(\text{right}, \text{top}, -\text{far})$ pravý horní roh. Úkolem matice $P_\text{ortho}$ je nasoukat tento boxík do krychle $(-1, -1, -1) \to (1, 1, 1)$ (a navíc flipnou Z, protože OpenGL mění handedness).

```math
\begin{aligned}

P_\text{ortho} &= C \cdot S \cdot T \\

&=

\begin{bmatrix}
    1 & 0 & 0 & 0 \\
    0 & 1 & 0 & 0 \\
    0 & 0 & -1 & 0 \\
    0 & 0 & 0 & 1 \\
\end{bmatrix}
\cdot
\begin{bmatrix}
    \frac{2}{\text{right-left}} & 0 & 0 & 0 \\
    0 & \frac{2}{\text{top-bottom}} & 0 & 0 \\
    0 & 0 & \frac{2}{\text{far-near}} & 0 \\
    0 & 0 & 0 & 1 \\
\end{bmatrix}
\cdot
\begin{bmatrix}
    1 & 0 & 0 & -\frac{\text{right+left}}{2} \\
    0 & 1 & 0 & -\frac{\text{top+bottom}}{2} \\
    0 & 0 & 1 & -\frac{\text{far+near}}{2} \\
    0 & 0 & 0 & 1 \\
\end{bmatrix} \\
&=

\begin{bmatrix}
    \frac{2}{\text{right}-\text{left}} & 0 & 0 & -\frac{\text{right}+\text{left}}{\text{right}-\text{left}} \\
    0 & \frac{2}{\text{top}-\text{bottom}} & 0 & -\frac{\text{top}+\text{bottom}}{\text{top}-\text{bottom}} \\
    0 & 0 & -\frac{2}{\text{far}-\text{near}} & -\frac{\text{far}+\text{near}}{\text{far}-\text{near}} \\
    0 & 0 & 0 & 1 \\
\end{bmatrix}

\end{aligned}
```

kde:

- $C$ obrací osu Z, kvůli přechodu z right-handed do left-handed (týká se OpenGL [^ortho]),
- $S$ definuje velikost kvádru, který se vleze do clip space,
- $T$ posouvá počátek doprostřed kvádru.

### Perspektivní projekce

Zmenšuje objekty, které jsou dále od kamery. [^camera]

![width=500](./img/szp08_perspective_projection.png)

Je definována 4 parametry:

- $\text{FOV}_y$ - field of view (úhel zorného pole) v ose Y,
- $\text{aspect}$ - poměr šířky a výšky okna,
- $\text{near}$ - blízká hranice,
- $\text{far}$ - daleká hranice.

V matici $P_\text{persp}$ se vyskytují následující mezihodnoty:

- $\text{top} = \text{near} \cdot \tan \left( \frac{\text{FOV}_y}{2} \right)$,
- $\text{bottom} = -\text{top}$,
- $\text{right} = \text{top} \cdot \text{aspect}$,
- $\text{left} = -\text{right}$.

- **Translace frustumu**
  Posouváme špičku frustumu do počátku souřadného systému. [^perspective]

  ```math
  T =
  \begin{bmatrix}
      1 & 0 & 0 & -\frac{\text{left} + \text{right}}{2} \\
      0 & 1 & 0 & -\frac{\text{bottom} + \text{top}}{2} \\
      0 & 0 & 1 & 0 \\
      0 & 0 & 0 & 1 \\
  \end{bmatrix}
  ```

  > [!NOTE]
  > Všimni si, že. Pokud používáme 4-parametrickou verzi, tak je to matice identity a tím pádem není potřeba.

- **Perspective divide**
  Objekty blíže k rovině $\text{near}$ budou větší než objekty dále. Rovina $\text{near}$ reprezentuje plochu obrazovky, na kterou jsou všechny body promítány.

  **Perspective divide** [^perspective]

  ![width=500](./img/szp08_perspective_divide.png)

  V obrázku výše je bod $(x, y, z)$ promítnut na rovinu $\text{near}$ jako $(x', y', near)$. Vznikají tak dva trojúhelníky, které jsou si sobě podobné a proto mají stejné poměry stran. Platí tedy $\frac{y'}{\text{near}} = \frac{y}{z}$. Pak $y' = \frac{y \cdot \text{near}}{z}$. Chceme tedy, aby platilo:

  ```math
  \begin{aligned}
      x' = \frac{x \cdot \text{near}}{z} \\
      y' = \frac{y \cdot \text{near}}{z}
  \end{aligned}
  ```

  Což můžeme vyjádřit v homogenních souřadnicích vyjadřít dělením $w$ jako:

  ```math
  D = \begin{bmatrix}
      \text{near} & 0 & 0 & 0 \\
      0 & \text{near} & 0 & 0 \\
      0 & 0 & 1 & 0 \\
      0 & 0 & \color{red}{-1} & \color{red}{0} \\
  \end{bmatrix}
  ```

- **Velikost okna**
  Šířka a výška okna dána pomocí $\text{left}, \text{right}, \text{bottom}, \text{top}$ se musí vlézt do intervalu $(-1.0, 1.0)$, proto je potřeba provést škálování:

  ```math
  S = \begin{bmatrix}
      \frac{2}{\text{right} - \text{left}} & 0 & 0 & 0 \\
      0 & \frac{2}{\text{top} - \text{bottom}} & 0 & 0 \\
      0 & 0 & 1 & 0 \\
      0 & 0 & 0 & 1 \\
  \end{bmatrix}
  ```

- **Přemapování hloubky**
  Chceme zachovat tu schopnost souřadnice $z$ nám říct, že něco je před něčím jiným. Potřebujeme proto přemapovat interval $(-\text{near}, -\text{far})$ na $(-1.0, 1.0)$. Jelikož desetinná čísla mají tendenci vytvářet artefakty, chceme aby toto mapování bylo nelineární tak, aby bylo přesnější blíže $\text{near}$. Použijeme $\frac{c_1}{-z} + c_2$, kde $c_1$ a $c_2$ jsou konstanty zvoleny pomocí:

  > [!NOTE]
  > Interval $(-\text{near}, -\text{far})$ obsahuje negace, neboť kamera se dívá do -Z osy, ale tyto hodnoty zadáváme jako kladná čísla.

  > [!NOTE]
  > $-z$ v rovnici výše je zodpovědné za přepnutí mezi right-handed a left-handed systémem souřadnic v OpenGL.

  **Depth mapping [^perspective]**

  ![width=500](./img/szp08_depth_mapping.png)

  ```math
  \begin{aligned}
      -1.0 &= \frac{c_1}{-(-\text{near})} + c_2 \\
      1.0 &= \frac{c_1}{-(-\text{far})} + c_2
  \end{aligned}
  ```

  Tedy:

  ```math
  \begin{aligned}
      c_1 &= \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}} \\
      c_2 &= \frac{\text{far} + \text{near}}{\text{far} - \text{near}}
  \end{aligned}
  ```

  Pokud $\frac{c_1}{-z} + c_2$ přepíšeme jako $\frac{(c_1 + c_2 \cdot (-z))}{-z} = \frac{(-c_2 \cdot z + c_1)}{-z}$, můžeme opět použít homogenní souřadnice a dělení $w$ a získáme:

  ```math
  M_\text{depth} = \begin{bmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & -c_2 & c_1 \\
      0 & 0 & -1 & 0 \\
  \end{bmatrix}
  = \begin{bmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & -\frac{\text{far} + \text{near}}{\text{far} - \text{near}} & \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}} \\
      0 & 0 & -1 & 0 \\
  \end{bmatrix}
  ```

---

Výsledná matice je:

```math
P_\text{persp} =  M_\text{depth} \cdot S \cdot D \cdot T =

\begin{bmatrix}
    \textcolor{green}{\frac{2 \cdot \text{near}}{\text{right} - \text{left}}}
        & 0
        & 0
        & \textcolor{purple}{-\text{near} \cdot \frac{\text{right} + \text{left}}{\text{right} - \text{left}}} \\

        0
        & \textcolor{blue}{\frac{2 \cdot \text{near}}{\text{top} - \text{bottom}}}
        & 0
        & \textcolor{#F56FA1}{-\text{near} \cdot \frac{\text{top} + \text{bottom}}{\text{top} - \text{bottom}}} \\
        % Nika: použij cyklamenovou farbu

        0
        & 0
        & -\frac{\text{far} + \text{near}}{\text{far} - \text{near}}
        & \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}} \\

    0 & 0 & -1 & 0 \\
\end{bmatrix}
```

Tahle matice funguje pro obecné frustum dané parametry $\text{left}, \text{right}, \text{bottom}, \text{top}, \text{near}, \text{far}$. Pokud dosadíme původní parametry za mezihodnoty:

```math
\begin{aligned}

\textcolor{green}{\frac{2 \cdot \text{near}}{\text{right} - \text{left}}}
&= \frac{\textcolor{red}{2} \cdot \text{near}}{\textcolor{red}{2} \cdot \text{right}}
= \frac{\text{near}}{\text{top} \cdot \text{aspect}}
= \frac{\textcolor{red}{\text{near}}}{\textcolor{red}{\text{near}} \cdot \tan \left( \frac{\text{FOV}_y}{2} \right) \cdot \text{aspect}}
= \textcolor{green}{\frac{\cot \left( \frac{\text{FOV}_y}{2} \right)}{\text{aspect}}} \\

\textcolor{blue}{\frac{2 \cdot \text{near}}{\text{top} - \text{bottom}}}
&= \frac{\textcolor{red}{2} \cdot \text{near}}{\textcolor{red}{2} \cdot \text{top}}
= \frac{\textcolor{red}{\text{near}}}{\textcolor{red}{\text{near}} \cdot \tan \left( \frac{\text{FOV}_y}{2} \right)}
= \textcolor{blue}{\cot \left( \frac{\text{FOV}_y}{2} \right)} \\

\textcolor{purple}{-\text{near} \cdot \frac{\text{right} + \text{left}}{\text{right} - \text{left}}}
&= -\text{near} \cdot \frac{\color{red} \text{right} - \text{right}}{2 \cdot \text{right}}
= \textcolor{purple}{0} \\

\textcolor{#F56FA1}{-\text{near} \cdot \frac{\text{top} + \text{bottom}}{\text{top} - \text{bottom}}}
&= -\text{near} \cdot \frac{\color{red} \text{top} - \text{top}}{2 \cdot \text{top}}
= \textcolor{#F56FA1}{0}

\end{aligned}
```

dostaneme matici, na kterou se trochu lépe kouká: [^gluperspective]

```math
P_\text{persp} = \begin{bmatrix}
    \textcolor{green}{\frac{\cot \left( \frac{\text{FOV}_y}{2} \right)}{\text{aspect}}}
        & 0
        & 0
        & \textcolor{purple}{0} \\

    0
        & \textcolor{blue}{\cot \left( \frac{\text{FOV}_y}{2} \right)}
        & 0
        & \textcolor{#F56FA1}{0} \\

    0
        & 0
        & -\frac{\text{far} + \text{near}}{\text{far} - \text{near}}
        & \frac{2 \cdot \text{far} \cdot \text{near}}{\text{near} - \text{far}} \\

    0 & 0 & -1 & 0 \\
\end{bmatrix}
```

## Základní afinní transformace

Opakování z [Lineární algebry II](../../szb/linearni-algebra-ii/):

- **Afinní prostor $\mathcal{A}$**\
  Trojice $(A, V, \oplus)$, kde $A$ je množina bodů, $V$ je vektorový prostor (zaměření) a $\oplus$ je binární funkce $\oplus : A \times V \to A$.
- **Standardní afinní prostor $\mathcal{A}_n$**\
  Je afinní prostor $(\mathbb{R}^n, \mathbb{R}^n, +)$.
- **Afinní kombinace bodů**\
  Výrazy tvaru $t_0 \cdot a_0 + t_1 \cdot a_1 + ... + t_n \cdot a_n$, kde $t_i \in \mathbb{R}, a_i \in \mathcal{A}$ a $\sum_{i=0}^n t_i = 1$.
- **Afinní zobrazení**\
  Zobrazení $f : \mathcal{A} \to \mathcal{A}$, které zachovává afinní kombinace bodů.

  Složení nějakého lineárního zobrazení a translace.

- **Translace $T$**\
  Pro posun v 2D prostoru podél osy $(x, y)$:

  ```math
  T = \begin{bmatrix}
      1 & 0 & x \\
      0 & 1 & y \\
      0 & 0 & 1
  \end{bmatrix}
  ```

  Pro posun ve 3D prostoru podél osy $(x, y, z)$:

  ```math
  T = \begin{bmatrix}
      1 & 0 & 0 & x \\
      0 & 1 & 0 & y \\
      0 & 0 & 1 & z \\
      0 & 0 & 0 & 1
  \end{bmatrix}
  ```

- **Rotace $R$**\
  Pro rotaci v 2D prostoru o úhel $\theta$:

  ```math
  R = \begin{bmatrix}
      \cos \theta & -\sin \theta & 0 \\
      \sin \theta & \cos \theta & 0 \\
      0 & 0 & 1 \\
  \end{bmatrix}
  ```

  Pro rotaci ve 3D prostoru o úhel $\theta$:

  - Kolem osy $X$:

    ```math
    R = \begin{bmatrix}
        1 & 0 & 0 & 0 \\
        0 & \cos \theta & -\sin \theta & 0 \\
        0 & \sin \theta & \cos \theta & 0 \\
        0 & 0 & 0 & 1
    \end{bmatrix}
    ```

  - Kolem osy $Y$:

    ```math
    R = \begin{bmatrix}
        \cos \theta & 0 & \sin \theta & 0 \\
        0 & 1 & 0 & 0 \\
        -\sin \theta & 0 & \cos \theta & 0 \\
        0 & 0 & 0 & 1
    \end{bmatrix}
    ```

  - Kolem osy $Z$:

    ```math
    R = \begin{bmatrix}
        \cos \theta & -\sin \theta & 0 & 0 \\
        \sin \theta & \cos \theta & 0 & 0 \\
        0 & 0 & 1 & 0 \\
        0 & 0 & 0 & 1
    \end{bmatrix}
    ```

- **Škálování / scale $S$**
  Pro škálování v 2D prostoru:

  ```math
  S = \begin{bmatrix}
      x & 0 & 0 \\
      0 & y & 0 \\
      0 & 0 & 1 \\
  \end{bmatrix}
  ```

  Pro škálování ve 3D prostoru:

  ```math
  S = \begin{bmatrix}
      x & 0 & 0 & 0 \\
      0 & y & 0 & 0 \\
      0 & 0 & z & 0 \\
      0 & 0 & 0 & 1
  \end{bmatrix}
  ```

- **Zkosení / shear (asi taky $S$)**
  Pro zkosení v 2D prostoru vektorem $(x, y)$:

  ```math
  S = \begin{bmatrix}
      1 & x & 0 \\
      y & 1 & 0 \\
      0 & 0 & 1 \\
  \end{bmatrix}
  ```

  Pro zkosení ve 3D prostoru podél plochy $YZ$:

  ```math
  S = \begin{bmatrix}
      1 & Y & Z & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0 \\
      0 & 0 & 0 & 1
  \end{bmatrix}
  ```

  Podél plochy $XZ$:

  ```math
  S = \begin{bmatrix}
      1 & 0 & 0 & 0 \\
      X & 1 & Z & 0 \\
      0 & 0 & 1 & 0 \\
      0 & 0 & 0 & 1
  \end{bmatrix}
  ```

  Podél plochy $XY$:

  ```math
  S = \begin{bmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      X & Y & 1 & 0 \\
      0 & 0 & 0 & 1
  \end{bmatrix}
  ```

## Další zdroje

- [`glm::ortho`](https://github.com/g-truc/glm/blob/5c46b9c07008ae65cb81ab79cd677ecc1934b903/glm/ext/matrix_clip_space.inl#L55)
- [`glm::perspective`](https://github.com/g-truc/glm/blob/5c46b9c07008ae65cb81ab79cd677ecc1934b903/glm/ext/matrix_clip_space.inl#L249)
- [`glm::lookAt`](https://github.com/g-truc/glm/blob/5c46b9c07008ae65cb81ab79cd677ecc1934b903/glm/ext/matrix_transform.inl#L153)

---

[^camera]: [LearnOpenGL: Camera](https://learnopengl.com/Getting-started/Camera)
[^ortho]: [LearnWebGL: Orthographic Projection](http://learnwebgl.brown37.net/08_projections/projections_ortho.html)
[^perspective]: [LearnWebGL: Perspective Projection](http://learnwebgl.brown37.net/08_projections/projections_perspective.html)
[^gluperspective]: [`gluPerspective`](https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml)
