# OM3D - TP3 : Deferred Shading Dans le cadre de ce TP, vous serez chargés d'implémenter le Deferred Shading en exploitant le G-Buffer que vous avez précédemment mis en place lors du TP2. Pour ce faire, vous devrez créer deux nouveaux fragment shaders. Le premier aura pour responsabilité le calcul de l'éclairage provenant du soleil et sera appliqué à l'ensemble de l'écran. En revanche, le second sera dédié à l'éclairage des lampes ponctuelles et utilisera une sphère par lampe afin de restreindre le calcul aux seuls pixels impactés par chaque lampe, en utilisant la méthode abordée dans le cours précédent. L'éclairage, autant celui provenant du soleil que des lampes locales, sera rendu dans un nouveau framebuffer, contenant une texture de sortie au format HDR. Cette texture sera ensuite soumise à un tone mapping avant d'être affichée à l'écran. ## Shading Avec le G-Buffer en place, la prochaine étape consiste à calculer l'éclairage. La première étape est le calcul de l'éclairage provenant du soleil ainsi que sur l'éclairage ambiant. À la différence des lampes "locales" telles que les point-lights ou les spot-lights, le soleil impacte l'ensemble de la scène, indépendamment de sa position. Pour représenter cela, il vous faudra dessiner un triangle qui recouvre l'intégralité de l'écran, plutôt que de définir un volume (de la même manière que pour le tone-mapping ou le shader de débogage que vous avez implémenté dans le TP précédent). :::info Il est recommandé d'utiliser la classe `Material` plutôt que de 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 tels que le depth-test, le blending, ou le culling. ::: ### Lire le G-Buffer Comme vous l'avez vu dans le TP précédent, 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 certaines 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 prend en paramètre les coordonnées en pixels (et non les UV) du texel à 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). Vous pouvez vous inspirer du shader de tone mapping, qui utilise déjà `texelFetch`. ::: #### Recalculer la position Pour calculer l'éclairage, il nous faut la position du point que nous essayons de shader. La position n'est pas présente dans le G-Buffer, il faut donc la recalculer à partir de la depth. La valeur de depth (et la position du pixel) est calculée à partir de la `gl_Position`, qui est elle-même calculée en multipliant la position des vertex 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 depth buffer), et `inv_viewproj` est l'inverse de la matrice view-proj de la caméra, que vous devrez calculer. ### Point lights :::info Dans un moteur graphique conventionnel, plusieurs types de lampes locales sont généralement pris en charge. Cependant, par souci de simplification pour ce TP, nous vous demandons de ne supporter que les point lights. ::: Il est maintenant temps de calculer la contribution des lampes locales. Le processus est similaire à celui utilisé pour calculer l'éclairage du soleil, mais 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 doit utiliser le depth test pour éliminer les pixels situés "derrière" la lampe, en utilisant la méthode vue dans le cours précédent. Pour additionner le résultat de l'éclairage de chaque lampe, vous devrez utiliser un blending additif. Notez que le code fourni ne supporte actuellement qu'un seul mode de blending (`Alpha`), vous devrez donc étendre l'enum `BlendMode` pour ajouter le support du blending additif. :::info Cette méthode utilise le depth test. Le framebuffer dans lequel vous dessinerez votre éclairage devra donc réutiliser le depth-buffer construit pendant le rendu de la scène. ::: Vous devrez également ajouter la possibilité pour un matériau de ne pas écrire dans le depth-buffer. On ne souhaite pas que le rendu du volume des lampes modifie le depth-buffer, car cela fausserait sérieusement les calculs d'éclairage pour les lampes suivantes. OpenGL permet d'activer/désactiver l'écriture dans le depth-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). La sphère fournie a un rayon de 1 mètre. Les volumes rendus pour calculer l'éclairage doivent correspondre aux rayons des lampes correspondantes. Il faudra donc faire attention à dessiner le mesh avec l'échelle calculée à partir du rayon de la lampe. :::danger Lorsque vous dessinez les volumes des lampes, il est impératif de configurer le culling pour n'afficher que les faces intérieures du volume. Sans cela, la lampe ne sera pas visible si la caméra se trouve à l'intérieur du volume. ::: :::warning Il est recommandé 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. Cela permettra de valider que le shading et le blending fonctionnent correctement en isolation. ::: ## Scène de test Si vous voulez vérifier que tout marche correctement, ainsi que de constater les différences de performance entre le forward et le deferred, la scène `bistro_lights.glb` présente sur [le drive des ressources](https://drive.google.com/drive/folders/1GxC4MsGg8U3jF6NP3U4BVBIct6fK7S61?usp=sharing) est un bon benchmark. ![](https://i.imgur.com/1Kzt9WO.png) Cependant, le modèle d'éclairage utilisé étant *très* simple et peu coûteux, il est probable que le gain de performance soit nul, voire même négatif.