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.
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