M2 - Images


TP 2 - Ambient Occlusion Volumes



mise à jour 9/2 :
utilisez la mise à jour de gKit, la creation de framebuffer et de textures est simplifiee.

cf gk::createFramebuffer(w, h, gk::COLOR0_BIT | gk::DEPTH_BIT, gk::TextureRGBA16F);
L'annexe en fin de sujet présente plus en détails la classe GLFramebuffer.




resultats



L'objectif du tp est de réaliser une approximation efficace de l'éclairement global. La méthode est décrite dans l'article :

"Ambient Occlusion Volumes"
M. McGuire
HPG 2010
[pdf]


Partie 1 : lire l'article.


Partie 2 : choisir une solution technique pour la construction des "volumes" (cpu ou geometry shader).


Partie 3 : réalisation

La méthode de calcul est un peu particulière, elle fonctionne à l'envers du code que l'on pourrait écrire avec un lancer de rayons, par exemple. Au lieu de chercher tous les objets dans un rayon delta autour d'un point, la méthode proposée travaille en deux étapes :

étape 1 :
    créer un framebuffer avec un color buffer et un depth buffer,
    dessiner tous les objets de la scène,
        stocker la position du point visible pour chaque pixel de l'image.

étape 2 :
    créer un framebuffer avec un ambient buffer (texture couleur) et le depth buffer de l'étape 1,
    calculer les englobants des objets visibles,
    (soit sur cpu, soit avec un geometry shader)

    dessiner les englobants des objets visibles :
        pour chaque pixel sur lequel se projette un triangle de l'englobant :
            retrouver la position du point visible à travers le pixel (utiliser le résultat de l'étape 1),
            évaluer l'occultation ambiente entre le point et le triangle associé à l'englobant dessiné,
            accumuler le résultat dans l'ambient buffer.

ce qui revient bien à la même chose :
trouver tous les triangles qui peuvent "faire de l'ombre" à chaque point des objets visibles de la scène.
(au lieu de chercher tous les triangles qui peuvent faire de l'ombre à chaque point,
la méthode proposée trouve tous les points à l'ombre de chaque triangle)

il ne reste plus qu'à utiliser l'occultation ambiante accumulée dans la texture 'ambient buffer' pour moduler la quantité de lumière éclairant chaque objet, ce qui se fait dans une troisième étape.



Partie 4 : comparer les résultats et performances avec les autres binomes.



Annexes : manipulations de framebuffers avec gKit


créer des textures et les attacher à un framebuffer :

utilisez FramebufferManager :

    #include "FramebufferManager.h"
    gk::createFramebuffer( largeur, hauteur, buffers, color_texture_format, depth_texture_format );

    cette fonction crée une ou plusieurs textures couleurs/profondeur, un framebuffer, et associe les textures au framebuffer.
    le parametre buffers décrit la liste des textures à créer et à attacher : utilisez une combinaison des constantes gk::COLOR0_BIT, gk::COLOR1_BIT, gk::DEPTH_BIT, etc.

    gk::GLFramebuffer *framebuffer= gk::createFramebuffer( largeur, hauteur, gk::COLOR0_BIT | gk::DEPTH_BIT );
    if(framebuffer == NULL)
        return "erreur"

    les parametres color_texture_format et depth_texture_format permettent de décrire le type/format de texture à créer, cf gk::TextureFormat. Plusieurs format courants sont déja déclarés :

gk::TextureRGBA :     texture couleur standard, 4 canaux rgba,
gk::TextureRGBA16F :  texture couleur 4 canaux rgba, codés sur des float 16bits,
gk::TextureRGBA32F :  texture couleur 4 canaux rgba, codés sur des float,

gk::TextureDepth :    texture de profondeur standard,
gk::TextureDepth24 :  texture de profondeur codée sur 24bits,
gk::TextureDepth32 :  texture de profondeur codée sur 32bits,

gk::TextureR32UI :    texture 1 canal, codé sur un unsigned int 32bits,
gk::TextureR16UI :    texture 1 canal, codé sur un unsigned short 16bits,

gk::TextureRG16UI :   texture 2 canaux, codés sur des unsigned shorts 16bits,


pour déclarer un format différent, il suffit de créer un objet TextureFormat( ) et de l'utiliser lors de la création des textures :
par exemple pour créer une texture 3 canaux rgb codés sur des float 16bits, gk::TextureFormat( GL_RGB16F, GL_RGB, GL_FLOAT );
les combinaisons de formats possibles sont décrites sur la page glTexImage2D, par exemple.

pour le tp vous aurez besoin d'un framebuffer pour la première étape :

    gk::GLFramebuffer *framebuffer1= gk::createFramebuffer(windowWidth(), windowHeight(), gk::COLOR0_BIT | gk::DEPTH_BIT, gk::TextureRGBA16F);
    if(framebuffer1 == NULL)
        return "erreur"

et d'un autre framebuffer pour la 2ieme étape, mais il faut qu'il utilise le même zbuffer que la première étape :

    gk::GLFramebuffer *framebuffer2= gk::createFramebuffer(windowWidth(), windowHeight(), gk::COLOR0_BIT);
    if(framebuffer2 == NULL)
        return "erreur"
   
    framebuffer2->attachTexture(gk::DEPTH, framebuffer1->zbuffer());

une fois la configuration du framebuffer terminée, il faut créer l'objet openGL :

    if(framebuffer1 == NULL || framebuffer1->createGLResource() < 0)
        return "erreur"
    if(framebuffer2 == NULL || framebuffer2->createGLResource() < 0)
        return "erreur"

attacher une texture à un framebuffer :

    utilisez la fonction gk::GLFramebuffer::attachTexture( buffer, texture );
    le paramètre buffer est une constante gk::COLOR0, gk::COLOR1, gk::DEPTH, etc.

    les fonctions gk::GLFramebuffer::texture( buffer ) et gk::GLFramebuffer::zbuffer( ) permettent de recupérer les GLTexture créees / attachées à un framebuffer.

    gk::GLFramebuffer *framebuffer2= gk::createFramebuffer(windowWidth(), windowHeight(), gk::COLOR0_BIT);
    framebuffer2->attachTexture(gk::DEPTH, framebuffer1->zbuffer());

dessiner dans un framebuffer :

    l'application doit activer l'utilisation d'un framebuffer déja configuré avec :

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1->name());

    si plusieurs textures couleurs sont attachées au framebuffer, il faut également configurer l'écriture dans ces textures avec glDrawBuffers( ). La classe GLFramebuffer détermine les paramètres à transmettre à openGL (l'ensemble des color buffers dans lesquels dessiner) :

    const std::vector<GLenum>&buffers= framebuffer1.drawBuffers( );
    glDrawBuffers(buffers.size(), &buffers.front());

désactiver le dessin dans un framebuffer :

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

utiliser une texture dans un shader

    l'application doit sélectionner une unite de texture GL_TEXTURE0 + index, activer la texture sur l'unité de texture, paramétrer le mode de filtrage et indiquer au shader sur quelle unite de texture se trouve la texture.

    gk::GLSampler *sampler= ...;    // creer un sampler dans init()

    gk::GLShaderProgram *program= ...;    // creer un shader program dans init()
    glUseProgram(program->name());    // active le shader program
   
    gk::GLTexture *texture= framebuffer1->texture(gk::COLOR0);    // recupere la texture a utiliser, dans un framebuffer, par exemple...
    glActiveTexture(GL_TEXTURE0 + unit);    // selectionne une unite de texture
    glBindTexture(texture->target(), texture->name());    // active la texture sur l'unite de texture

    glBindSampler(unit, sampler->name());    // active un sampler et parametre le filtrage sur l'unite de texture

    gk::setSamplerUniform(program->sampler("color_texture"), unit);    // indique au shader sur quelle unite de texture se trouve la texture

    gk::Transform mvp= ...;
    gk::setUniform(program->uniform("mvpMatrix"), mvp.matrix());    // finir de parametrer le shader program

    glDrawXXX( );    // dessiner quelquechose


    dans le fragment shader, l'acces à la texture ressemble à ça :

    #version 330     // par exemple
   
    uniform sampler2D color_texture;    // declare le type de la texture, 2D, 1D, 3D, CUBE...

    in vec2 vertex_texcoord;    // coordonnees de texture interpolees depuis le vertex shader
    out vec4 fragment_color;    // couleur en sortie du fragment shader

    void main( )
    {
        // copie simplement la texture dans le pixel
        vec4 color= texture(color_texture, vertex_texcoords);
        fragment_color= color;
    }


    un exemple complet se trouve dans image_viewer.cpp