OpenGL["noël"]

OpenGL["noël"]

  • Intro et SDK
  • Bronze
  • Silver
  • Gold
  • Blog
  • OpenGL Documentation
  • OpenGL Wiki
  • GitHub
  • My glTF Viewer Tutorial

›Deferred Renderer

Introduction et SDK

  • Introduction
  • Fiche d'information
  • Projet - Un viewer glTF

Forward Renderer

  • Pipeline de Rendu
  • Geometrie
  • Shaders
  • Transformations
  • Lighting
  • Textures
  • Chargement de modèles OBJ
  • Aller plus loin

Deferred Renderer

  • Pipeline de Rendu
  • Geometrie Pass
  • Shading Pass
  • Aller plus loin

Shadow Mapping

  • Introduction
  • Directional Shadow Map
  • Percentage Closest Filtering
  • Aller Plus Loin

Post Processing

  • Introduction
  • Gamma Correction
  • Extraction et Rendu de Contours
  • Depth of Field

Annexe: API OpenGL

  • Contexte OpenGL
  • Pipeline de Rendu
  • Extensions OpenGL
  • Objets OpenGL
  • Buffer Objects
  • Vertex Array Objects
  • Texture Objects
  • Framebuffer Objects
  • Shaders

Geometrie Pass

Comme indiqué en introduction, la première passe de rendu est la Geometry Pass, dont l'objectif est de "dessiner" dans un GBuffer les informations concernant les objets visibles à l'écran.

Pour cela, il va falloir créer les textures OpenGL destinées à contenir ces informations, et les attacher à un framebuffer object, qui va permettre d'écrire dans ces textures plutot qu'a l'écran.

Pour ces TPs, dupliquez l'app de l'exercice du foward renderer afin d'avoir une scene chargée et stockée sur GPU pour être rendue. Nommez la nouvelle app "deferred-renderer".

Shaders

Nous allons commencer par les shaders car c'est le plus simple. Renommez les shaders forward.vs.glsl et forward.fs.glsl en geometryPass.vs.glsl et geometryPass.fs.glsl.

Il faut ensuite modifier le fragment shader (plus exactement le simplifier).

Tout d'abord modifier les sorties. On avait:

out vec3 fColor;

a remplacer par:

layout(location = 0) out vec3 fPosition;
layout(location = 1) out vec3 fNormal;
layout(location = 2) out vec3 fAmbient;
layout(location = 3) out vec3 fDiffuse;
layout(location = 4) out vec4 fGlossyShininess;

On va donc écrire dans 5 textures en tout.

Dans le main du shader, remplacez tout le code d'illumination par des écritures dans les variables de sortie. Ne pas oublier de normaliser vViewSpaceNormal avant de l'écrire dans fNormal.

A noter que l'on écrit pas les texCoords en sortie car on ne les utilise que pour lire les coefficients ambiant, diffus et glossy des textures de l'objet en cours de rendu. Les texCoords ne sont donc pas necessaire à la Shading Pass puisqu'on écrit directement ces coefficients dans des textures du GBuffer.

Les variables de sortie de matériaux doivent stocker la multiplication du coefficient associé avec la valeur lue dans la texture. Par exemple:

uniform vec3 uKd;
uniform sampler2D uKdSampler;

[...]

// Dans le main:
vec3 kd = uKd * vec3(texture(uKdSampler, vTexCoords));
fDiffuse = kd;

Enfin, il faut packer la shininess dans le canal alpha de la variable de sortie fGlossyShininess.

Dans le code de Application, chargez ces shaders pour tester leur compilation.

Textures du GBuffer

Dans la classe Application, déclarez un tableau de GLuint pour stocker les texture objects, ainsi que l'enum suivant:

enum GBufferTextureType
{
    GPosition = 0,
    GNormal,
    GAmbient,
    GDiffuse,
    GGlossyShininess,
    GDepth, // On doit créer une texture de depth mais on écrit pas directement dedans dans le FS. OpenGL le fait pour nous (et l'utilise).
    GBufferTextureCount
};
GLuint m_GBufferTextures[GBufferTextureCount];

Dans le constructeur, créer et initialiser ces textures avec comme dimension la taille de la fenêtre.

Vous pouvez créer toutes les textures en un seul appel à glGenTextures (ou glCreateTextures en DSA) car nos identifiants sont stockés dans un tableau. Il suffit de passer à la fonction le nombre de textures à créer.

Le format à passer à glTexStorage2D dépend du type de texture, vous pouvez utiliser le tableau suivant, en correspondance avec l'enum:

const GLenum m_GBufferTextureFormat[GBufferTextureCount] = { GL_RGB32F, GL_RGB32F, GL_RGB32F, GL_RGB32F, GL_RGBA32F, GL_DEPTH_COMPONENT32F };

Il est inutile de remplir les textures avec glTexSubImage2D car elles sont destinées à être remplies par le fragment shader.

Fonctions GL à utiliser:

Sans DSADSA
glGenTexturesglCreateTextures
glBindTexture(GL_TEXTURE_2D, texID)
glTexStorage2DglTextureStorage2D

Framebuffer object

Déclarez une variable GLuint m_FBO dans la classe Application.

Dans le constructeur, après création des texture objects, créez le FBO (glGenFramebuffers) et bindez le sur la cible GL_DRAW_FRAMEBUFFER.

Il faut ensuite attacher toutes nos textures au FBO en utilisant la fonction glFramebufferTexture2D.

glFramebufferTexture2D( GLenum target,
    GLenum attachment,
    GLenum textarget,
    GLuint texture,
    GLint level);

La target est la cible sur laquelle est bindée de FBO (GL_DRAW_FRAMEBUFFER).

L'attachment est un point d'attache de la forme GL_COLOR_ATTACHMENT0 + i, ou i est l'index de la texture dans son tableau (de GPosition à GGlossyShininess). Pour la texture de profondeur (GDepth), elle doit être attachée sur le point GL_DEPTH_ATTACHMENT.

Dans notre cas, textarget doit être mis à GL_TEXTURE_2D car toutes nos textures sont 2D (on pourrait aussi faire le rendu dans des layers de textures 3D, il faudrait alors changer ce paramètre).

Enfin, level doit être mis à 0 (c'est le niveau de mipmap dans lequel dessiner).

Une fois les textures attachées, il faut indiquer à OpenGL une association "sortie du fragment shader" vers texture. Comme vous le verrez au prochain exercice, le fragment shader peut avoir plusieurs sorties indicées avec des layout(location = i) (de la meme manière que les entrée du vertex shader). Il faut dire à OpenGL comment les locations doivent être connectées aux textures du FBO.

Cela passe par la fonction glDrawBuffers, qui prend le nombre de sorties du fragment shader et un tableau de GL_COLOR_ATTACHMENTi afin de faire la liaison. Nous allons faire assez simple:

GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4 };
glDrawBuffers(5, drawBuffers);

Ici on va envoyer la sortie i du fragment shader vers l'attachment GL_COLOR_ATTACHMENTi du FBO. C'est ce qui parait le plus logique mais, si on voulait, on pourrait tout à fait envoyer la sortie 3 du fragment shader vers la texture attachée sur GL_COLOR_ATTACHMENT0, par exemple.

Utilisez ensuite la fonction glCheckFramebufferStatus afin de vérifier si le framebuffer créé est correct (si ce n'est pas le cas, bon debug !).

Enfin débindez le framebuffer de la cible GL_DRAW_FRAMEBUFFER.

Fonctions GL à utiliser:

Sans DSADSA
glGenFramebuffersglCreateFramebuffers
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo)
glFramebufferTexture2DglNamedFramebufferTexture
glDrawBuffersglNamedFramebufferDrawBuffers
glCheckFramebufferStatusglCheckNamedFramebufferStatus

Boucle de rendu

Au rendu assez peu de chose à changer.

Avant de dessiner, binder le framebuffer sur la cible GL_DRAW_FRAMEBUFFER. Cela va indiquer à OpenGL que le fragment shader va écrire dans les textures attachées au FBO.

Il faut également faire un .use() sur le programme correspondant aux geometryPass.glsl

Après le dessin de la scène, débindez le FBO. Voila.

Pour tester que tout fonctionne bien, on peut blitter les textures du GBuffer à l'écran.

Après le débind du FBO, rebindez le, cette fois ci sur la cible GL_READ_FRAMEBUFFER.

Utilisez ensuite la fonction glReadBuffer, qui prend en paramètre un GL_COLOR_ATTACHMENT0 + i, correspondant à la texture du GBuffer que vous voulez afficher. Vous pouvez par example afficher la texture de normals en passant GL_COLOR_ATTACHMENT0 + GNormal. Faites ensuite en sorte de pouvoir choisir la texture à afficher via la GUI (avec des boutons radio, ImgGui::RadioButton).

Puis utilisez la fonction glBlitFramebuffer qui permet de "copier-coller" (avec filtre) une portion du FBO bindé sur GL_READ_FRAMEBUFFER vers le FBO bindé sur GL_DRAW_FRAMEBUFFER (c'est à dire l'écran, lorsque rien n'est bindé dessus).

Finalement, débindez le FBO de GL_READ_FRAMEBUFFER. A noter qu'en DSA il n'y a rien à binder pour l'étape du blit (il faut quand même binder sur GL_DRAW_FRAMEBUFFER avant de dessiner la scène).

Fonctions GL à utiliser:

Sans DSADSA
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo)glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo)
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo)
glReadBufferglNamedFramebufferReadBuffer
glBlitFramebufferglBlitNamedFramebuffer
← Pipeline de RenduShading Pass →
  • Shaders
  • Textures du GBuffer
  • Framebuffer object
  • Boucle de rendu
OpenGL["noël"]
About Me
Personal websiteGithub
About This Website
BlogopenglnoelPowered by Docusaurus
About OpenGL
Documentation (docs.gl)Wiki
Copyright © 2021 Laurent NOEL