Envoyez en utilisant
le
mail de l'université une archive des sources de votre
tp ainsi qu'un rapport de quelques pages précisant les
tansformations et algorithmes utilisés pour chaque exercice
(notamment les transformations nécessaires pour les ombres). Si
vous n'avez pas terminé l'exercice 3, rédigez quand
même l'algorithme en précisant les transformations
nécessaires pour trouver les sources de lumières
utilisées pour éclairer indirectement chaque fragment.
Vous préciserez vos noms, prénoms et numéros
d'étudiants dans le corps du mail.
On souhaite afficher un objet et lui donner un aspect particulier, en
"simulant" les interactions de la lumière avec sa
matière.
Chargez un objet et ses descriptions de matières :
#include "MeshIO.h"
#include "GLTexture2DIO.h"
gk::Mesh *mesh= gk::MeshIO::read("bigguy.obj");
if(mesh == NULL)
return "erreur de chargement";
for(int i= 0; i < mesh->subMeshCount(); i++)
{
const gk::MeshMaterial& material= mesh->subMeshMaterial(i);
// eventuellement, charger la texture
diffuse associée à la matière, lorsqu'elle existe :
if(material.diffuse_texture.empty() == false)
{
GLTexture2D *texture=
gk::GLTexture2DIO::read(material.diffuse_texture);
}
}
Un subMesh est un groupe de triangles partageant la même
matière, tous les triangles d'un objet Mesh sont triés par
matière et groupés dans des subMesh, il y a donc un
subMesh par matière dans un objet Mesh. Un subMesh est
décrit par une séquence d'indices : begin, end et un
indice de matière.
Pour afficher tous les groupes / submesh, il faut donc
découper le dessin de l'objet en plusieurs fois, et
paramètrer correctement le glDrawXXX( ).
Pour un submesh il y a (end - begin) indices à dessiner et ils
sont placés dans le buffer à partir de la position (begin *
sizeof(type des indices)).
Exemple pour afficher un objet gk::Mesh :
#include "MeshIO.h"
gk::Mesh *mesh= gk::MeshIO::read("bigguy.obj");
if(mesh == NULL)
return "erreur de chargement";
glUseProgram(program);
for(int i= 0; i < mesh->subMeshCount(); i++)
{
const gk::subMesh& submesh= mesh->subMesh(i);
const
gk::MeshMaterial& material= mesh->subMeshMaterial(i);
// parametrer le shader avec la description de matiere :
// par exemple, pour une matiere diffuse
// gk::setUniform(program->uniform("kd"), material.kd);
//
gk::setUniform(program->uniform("diffuse"), material.diffuse.r,
material.diffuse.g, material.diffuse.b);
const int count= submesh.end - submesh.begin;
const long int
offset= submesh.begin * sizeof(unsigned int);
glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, (const GLvoid *) offset);
}
1.1 Ecrivez une paire de vertex / fragment shaders permettant de
représenter une matière diffuse (modèle de Lambert)
et une autre paire de shaders pour une matière
réfléchissante (modèle de Blinn-Phong).
Les différents paramètres sont disponibles dans la classe gk::MeshMaterial, cf. gk::Mesh::subMeshMaterial() :
1.2 Chargez les textures référencées par les
matières de l'objet et utilisez-les pour "moduler" le
résultat précédent.
Utilisez gk::GLTextureIO::read()
pour charger une image (.png, .jpg, .bmp, .tga) et créer la texture 2d openGL correspondante.
remarque : Vous pouvez comparer vos modèles de matières à une implementation de référence avec Brdf Explorer.
remarque : les matières sont décrites dans un
fichier texte, "simple.mtl", il est très simple de le modifier pour
tester vos shaders.
Par exemple, la matière par défaut s'appelle "white", elle est déclarée :
newmtl white
Kd 0.75 0.75 0.75
ce qui correspond à une matière diffuse grise qui réflechit 75% de la lumière incidente. La couleur décrite par Kd dans le fichier mtl est conservée dans le champ diffuse de la structure gk::MeshMaterial.
pour utiliser une texture pour moduler le résultat, il suffit d'ajouter la ligne map_Kd nom_image, par exemple :
newmtl white
Kd 0.75 0.75 0.75
map_Kd simple.png
Voici les correspondances entre le fichier mtl et les valeurs stockées dans un objet MeshMaterial :
Kd r g b
|
MeshMaterial::diffuse= Normalize(r,g,b)
MeshMaterial::kd= Length(r,g,b)
|
map_Kd image
|
MeshMaterial::diffuse_texture= image
|
Ks r g b
|
MeshMaterial::specular= Normalize(r,g,b)
MeshMaterial::ks= Length(r,g,b)
|
map_Ks image
|
MeshMaterial::specular_texture= image
|
Ns |
MeshMaterial::n= Ns
|
gk::MeshMaterial material;
material.name= "white"
material.kd= 0.75;
material.diffuse= gk::Energy(1.0, 1.0, 1.0);
material.map_Kd= "simple.png"
Le calcul des ombres se fait en 2 étapes, la première
construit un zbuffer de la scène du point de vue de la source de
lumière et la deuxième étape utilise cette
information pour déterminer quels sont les fragments visibles de l'observateur et
éclairés par la source de lumière.
cf gk::createFramebuffer(w, h, gk::COLOR0_BIT | gk::DEPTH_BIT);
pour créer une texture couleur et une texture de profondeur et
les attacher dans un framebuffer. L'annexe en fin de sujet
présente plus en détails la
classe gk::GLFramebuffer.
L'exercice précédent permet d'afficher des objets éclairés ainsi que leurs ombres. Mais la lumière est réfléchie dans plusieurs directions à chaque interaction avec une matière, comment calculer rapidement la quantité de lumière qui éclaire un objet après avoir rebondi sur un autre objet ? Autre manière de se poser la question : lorsque l'étape 1 de l'exercice précédent calcule les objets visibles par la source de lumière, que manque-t-il pour calculer la lumière réfléchie par ces objets éclairés ?
Dans l'exemple ci-dessus, quelle information faut-il garder lors de la passe 1, pour pouvoir éclairer xq avec la lumière réfléchie par xp (lors de la passe 2) ?
indication : l'energie incidente et les paramètres de la matière ?
Comment trouver les fragments qui peuvent réfléchir de la lumière vers le fragment q ?
indication : ils sont proches de q dans l'image de la scène du point de vue de la source de lumière...
remarque : toutes les paires de points ne peuvent pas
échanger de l'énergie, quel critère peut on
utiliser pour déterminer que x et y ne peuvent pas
éclairer xq, alors que xp peut éclairer xq ?
3.1 Modifier la construction de la shadow map de l'exercice 2 pour stocker les informations supplémentaires pour chaque pixel.
3.2 Ecrivez une paire de shader qui choisit un ensemble de points xp, qui vérifie que chaque point peut éclairer le fragment en cours de traitement et accumule l'énergie indirecte reçue par le fragment.
Comment évaluer efficacement la visibilité entre les points xp et le fragment en cours de traitement ?
détails supplémentaires dans :
"Reflective Shadow Maps"
C. Dachsbacher, M. Stamminger,
I3D 2005
#include "FramebufferManager.h"
gk::createFramebuffer( largeur, hauteur, buffers,
color_texture_format, depth_texture_format );
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 (32 bits),
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,
gk::GLFramebuffer *framebuffer1=
gk::createFramebuffer(largeur, hauteur, gk::COLOR0_BIT |
gk::DEPTH_BIT, gk::TextureRGBA16F);
if(framebuffer1 == NULL)
return "erreur"
une fois la configuration du framebuffer terminée, il faut
créer l'objet openGL : if(framebuffer1 == NULL ||
framebuffer1->createGLResource() < 0)
return "erreur"
gk::GLFramebuffer::attachTexture( buffer, texture );
gk::GLFramebuffer::texture( buffer )
et gk::GLFramebuffer::zbuffer( )
permettent de recupérer les
GLTexture attachées à un framebuffer. glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
framebuffer1->name());
const std::vector<GLenum>&buffers=
framebuffer1.drawBuffers( );
glDrawBuffers(buffers.size(), &buffers.front());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
#include "ShaderManager.h"
#include "SamplerManager.h"
#include "TextureManager.h"
#include "FramebufferManager.h"
gk::GLSampler *sampler= ...; //
creer un sampler dans init()
gk::GLShaderProgram *program= ...;
// creer un shader program dans init()
gk::GLFramebuffer *framebuffer1=
...; // creer un framebuffer et une texture couleur
attache 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 couleur lue dans la texture
dans le pixel
vec4 color=
texture(color_texture, vertex_texcoords);
fragment_color= color;
}
il est également possible d'utiliser gk::setTexture( const ProgramSampler& sampler, gk::GLTexture *texture, gk::GLSampler *sampler )
pour simplifier l'utilisation des textures par un shader :
#include "ShaderManager.h"
#include "SamplerManager.h"
#include "TextureManager.h"
#include "FramebufferManager.h"
gk::GLSampler *sampler= ...; //
creer un sampler dans init()
gk::GLShaderProgram *program= ...;
// creer un shader program dans init()
gk::GLFramebuffer *framebuffer1=
...; // creer un framebuffer et une texture couleur
attache 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...
gk::setTexture(program->sampler("color_texture"), texture, sampler);
gk::Transform mvp= ...;
gk::setUniform(program->uniform("mvpMatrix"),
mvp.matrix()); // finir de parametrer le shader
program
glDrawXXX( ); // dessiner
quelquechose