# OM3D - TP2&3: Deferred Shading
[toc]
---
Dans ce TP vous allez implémenter un deferred shading simple, comme expliqué pendant la séance 2, pour remplacer l'approche forward naïve utilisée jusqu'ici.
Votre pipeline devrait ressembler à ça :

Ce TP n'aborde pas les calculs d'éclairage, seulement la mise en place d'un pipeline deferred. Vous êtes libre d'utiliser les équations que vous voulez. Le plus simple est de reprendre celles déjà présentes dans `lit.frag`.
## Le G-Buffer
Pour que le lighting fonctionne le G-Buffer doit contenir au minimum l'albedo et les normales de la scène.
Pour cela on comencera par utiliser 2 textures.
* La première utilisera le format `RGBA8_sRGB` et contiendra l'albedo dans les canaux RGB.
* La seconde utilisera le format `RGBA8_UNORM` et contiendra les normales dans les canaux RGB. (Pour le moment les normales seront stockées comme de simples vecteurs XYZ, sans encodage particulier).
<div style="text-align: center;">
<img src="https://i.imgur.com/mrefzj6.png" width="80%"><p></p></div>
:::info
Les canaux alpha sont volontairement laissé vides, vous êtes libre de les utiliser pour ce que vous voulez.
:::
:::warning
Les textures `RGBA8_UNORM` ne peuvent que stocker des valeurs positives comprises dans l’intervalle [0; 1]. Il est donc nécessaire de convertir les normales (dont les composants se trouvent dans l’intervalle [-1; 1]) avant de les écrire.
:::
### Le multi render-target (MRT)
OpenGL permet aux fragment shaders d'avoir plusieurs valeurs de sortie, et de rendre plusieurs render-targets en une seule passe (en plus de la profondeur). Le constructeur de la classe `Framebuffer` qui vous a été fournie accepte un `std::array<Texture*>` en paramètre, ce qui permet de spécifier plusieurs render-targets.
:::warning
* Toutes les textures utilisées comme render-target par un même framebuffer doivent impérativement avoir la même taille. (Mais peuvent avoir différents formats)
* Il y a une limite au nombre de render-target qu'un framebuffer peut avoir. En OpenGL 4.5 cette limite est garantie d’être au moins 8.
:::
Écrire dans plusieurs render-targets depuis un fragment shader est relativement simple. Il suffit de déclarer une variable `out` pour chaque render-target, et de spécifier un index en utilisant `layout(location = /* location */)`. L'index de location correspond à l'index de la texture passée au constructeur du framebuffer.
### Matériaux
Pour le moment tous les matériaux chargés depuis un fichier glTF utilisent un shader faisant un forward simple (contenu dans `lit.frag`). Ce shader n'est évidement pas compatible avec la génération d'un G-Buffer (en plus d’être massivement inefficace), vous devrez donc le remplacer par un shader adapté.
:::danger
Le shader fourni supporte le texturing et le normal-mapping via les defines `TEXTURED` et `NORMAL_MAPPED`. Votre shader de G-Buffer devra lui aussi supporter ces fonctionnalités.
:::
### Vue de debug
Bien que l'on puisse utiliser RenderDoc pour visualiser le contenu du G-Buffer, il peut être pratique de pouvoir le faire dans l'application directement.
Vous devrez donc implémenter des vues de debug pour pouvoir inspecter votre G-Buffer.
Pour activer/désactiver les vue de debug, il faudra rajouter un menu dans la fenêtre ImGui déjà présente dans l'application.
:::info
ImGui est une librairie externe pour créer des UI de debug directement depuis la boucle principale du moteur. ImGui n'a pas de backend graphique et ne fait que générer des vertex/index buffers que l'application est responsable d'afficher.
:::
Le projet contient un renderer ImGui (implémenté dans la classe `ImGuiRenderer`). Pour ajouter des éléments à la fenêtre existante il suffit d'ajouter des commandes entre `imgui.start();` et `imgui.finish();` dans le `main`.
:::info
Vous pouvez trouver des exemples de comment utiliser ImGui dans `imgui_demo.cpp`.
:::
Vos vues de debug devront au moins contenir un moyen de visualiser l'albedo, les normales, ainsi que la depth présentes dans le G-Buffer. Vous êtes libre de choisir comment ces vues seront rendues et d'en ajouter d'autres.
La méthode la plus simple est probablement de rendre un triangle recouvrant tout l'écran avec un fragment shader qui lit le G-Buffer et output la texture voulue.
:::info
Le code donné contient un vertex shader spécialement pour dessiner un triangle recouvrant tout l'écran : `screen.vert`. Ce shader contient déjà la géométrie nécessaire et peux donc être utilisé sans vertex buffer. Pour ce faire, il suffit de bind le shader et demander à OpenGL de dessiner 3 vertices en appelant `glDrawArrays(GL_TRIANGLES, 0, 3);`.
:::
<!---

--->
## Shading
Maintenant que le G-Buffer en place, il ne reste plus qu'à calculer l'éclairage.
La première étape est de calculer l'éclairage du soleil (ainsi que l'éclairage ambiant, si applicable).
Contrairement aux lampes "locales" comme les point-lights ou les spot-lights, le soleil affecte toute la scène, peu importe sa position.
Plutôt que de rendre un volume, on peut donc rendre un triangle couvrant tout l'écran (en utilisant `screen.vert`).
:::info
Il est recommandé d'utiliser la classe `Material` plutôt que manipuler les shaders directement pour rendre vos lampes (locales ou non), car celle-ci contient déjà la majorité de la logique nécessaire à la gestion des états OpenGL comme le depth-test, le blending, ou le culling.
:::
### Lire le G-Buffer
Les textures qui composent le G-Buffer peuvent être lues comme n'importe quelles textures. Il est cependant important de ne pas appliquer de filtrage (bilinéaire ou autre) au moment de la lecture, car certaine informations présentes dans le G-Buffer ne sont pas interpolables.
Il est possible de désactiver le filtrage pour une texture en mettant ses paramètres de filtrage (`GL_TEXTURE_MIN_FILTER` et `GL_TEXTURE_MAG_FILTER`) à `GL_NEAREST` dans OpenGL.
Dans le cas de la lecture d'un G-Buffer, il est plus simple (et plus sûr) de lire la texture en utilisant une fonction GLSL qui n'applique pas de filtrage: [`texelFetch`](https://registry.khronos.org/OpenGL-Refpages/gl4/html/texelFetch.xhtml).
:::danger
`texelFetch` fonctionne différemment de `texture` et prends en paramètre les coordonnées en pixels (en non les UV) du texel a lire.
Dans un fragment shader, vous pouvez récupérer les coordonnées en pixels du fragment courant via [gl_FragCoord](https://registry.khronos.org/OpenGL-Refpages/gl4/html/gl_FragCoord.xhtml).
:::
#### Recalculer la position
Pour calculer l'éclairage, il nous faut la positon du point que nous essayons de shader. La position n'est pas contenu dans le G-Buffer, il faut donc la recalculer à partir du contenu du Z-Buffer.
La valeur contenue dans le Z-Buffer (et la position du pixel) est calculée à partir de la `gl_Position`, qui est elle-même calculée en multipliant la position par les matrices de projection et de view. Pour retrouver la position il suffit donc d'effectuer l’opération inverse.
Pour ce faire vous pouvez utiliser la fonction suivante:
```cpp
vec3 unproject(vec2 uv, float depth, mat4 inv_viewproj) {
const vec3 ndc = vec3(uv * 2.0 - vec2(1.0), depth);
const vec4 p = inv_viewproj * vec4(ndc, 1.0);
return p.xyz / p.w;
}
```
Où `uv` sont les UV du pixel dont on cherche à calculer la position, `depth` est sa depth (lue depuis le Z-Buffer), et `inv_viewproj` est l'inverse de la matrice view-proj de la camera, que vous devrez calculer.
### Point lights
:::info
Dans un "vrai" moteur il existe plusieurs types de lampe locale (typiquement 3 ou 4). Par soucis de simplicité, dans ce TP on ne vous demandera de suporter que les point lights.
:::
Il est maintenant temps de calculer la contribution des lampes locales. Le procédé est similaire à celui utilisé pour calculer l'éclairage du soleil avec trois différences majeures:
* Il doit être répété pour chaque lampe présente dans le frustum
* Le résultat doit être additionné à la contribution des lampes précédentes
* Le rendu devra utiliser le depth test pour cull les pixels se trouvant "derrière" la lampe.
Pour additionner le résultat de l'éclairage de chaque lampe, il faudra utiliser un blending additif. Le code donné ne supporte qu'un seul mode de blending (`Alpha`), il vous faudra donc étendre l'enum `BlendMode` pour ajouter le support du blending additif.
Vous devrez aussi ajouter la possibilité pour un matériau de ne pas écrire dans le Z-Buffer. (On ne veut pas que le rendu du volumes des lampes modifient le Z-Buffer, cela fausserait sérieusement les calculs d'éclairage). OpenGL permet d'activer/désactiver l'écriture dans le Z-Buffer via la fonction [`glDepthMask`](https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthMask.xhtml).
Pour dessiner les volumes des lampes, vous pouvez utiliser le mesh de sphère présent sur [le drive des ressources](https://drive.google.com/drive/folders/1GxC4MsGg8U3jF6NP3U4BVBIct6fK7S61?usp=sharing).
:::warning
Il est conseillé de commencer par rendre les lampes locales en utilisant un triangle couvrant tout l'écran (comme pour le soleil), avant de s'attaquer aux volumes/depth test, afin de pouvoir valider que le shading et blending marchent bien en isolation.
:::
## Scène de test
[Le drive des ressources](https://drive.google.com/drive/folders/1GxC4MsGg8U3jF6NP3U4BVBIct6fK7S61?usp=sharing) contient une scène de test utilisant toute les fonctionnalités du moteur (et plus): `bistro.glb`.
Si vous voulez vérifier que tout marche correctement, c'est un bon benchmark. Il peut aussi être intéressant de voir la différence en performance entre l'implémentation forward naïve donnée, et votre deferred shader.
:::warning
La scène ne contient pas de lampes (et le loader fourni ne les supporte pas) vous devrez donc ajouter les votres, en code.
:::