Le rendu en texture est une méthode spécifique de rendu d'un objet ou d'une scène 3D en deux passes. L'idée de base est simple, on fait un premier rendu de notre scène dans une texture, puis l'on fait un second rendu de cette texture directement à l'écran. Généralement, on plaque cette texture sur un quadrilatère faisant toute la taille du canvas OpenGL. Cette opération permet de mettre en place de nombreux effets en travaillant sur la texture générée.
Préparation de la pipeline
De nombreuses personnes buttent sur OpenGL à cause d'un versioning assez chaotique et d'un très grands nombres d'extensions à utiliser. Afin, d'éviter tout problème de compatibilité, je vous recommande de respecter ces critères. Dans le cas contraire, il faudra peut être adapté un peu mon code pour assurer sa portabilité.
- Gestionnaire de fenêtrage GLUT (avec GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
- L'utilitaire d'extension GLEW
- OpenGL 3.3+ (core profile)
- GLSL 3.3+
Création d'une texture vierge
Il faut d'abord créer une texture RGB vierge. Cette texture sera remplie directement par les couleurs de sortie du premier fragment shader lors du rendu de votre scène.
// Génération d'une texture GLuint idTexture; glGenTextures(1, &idTexture); // Binding de la texture pour pouvoir la modifier. glBindTexture(GL_TEXTURE_2D, idTexture); // Création de la texture 2D vierge de la taille de votre fenêtre OpenGL glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1024, 768, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); // Paramètrage de notre texture (étirement et filtrage) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
Mise en place d'un FrameBufferObject pour contenir la texture
Dans un rendu normal, il n'y avait pas besoins de créer un FrameBufferObject. En effet, OpenGL en créé un nativement, ce dernier contient l'image affichable à l'écran. Dans notre cas, nous devons
// Génération d'un second FBO GLuint idFBO = 0; glGenFramebuffers(1, &idFBO); // On bind le FBO glBindFramebuffer(GL_FRAMEBUFFER, idFBO); // Affectation de notre texture au FBO glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, idTexture, 0); // Affectation d'un drawbuffer au FBO GLenum DrawBuffers[2] = {GL_COLOR_ATTACHMENT0}; glDrawBuffers(1, DrawBuffers);
Cette partie est optionnelle (mais recommandée). Si, vous modèliser une scène utilisant de la profondeur (Z-buffer), il faut créer un DepthRenderBuffer et l'ajouter dans le FrameBufferObject.
(optionnel) // Création d'un buffer de profondeur GLuint idDepthrenderbuffer; glGenRenderbuffers(1, &idDepthrenderbuffer); // Binding du buffer de profondeur glBindRenderbuffer(GL_RENDERBUFFER, idDepthrenderbuffer); // Paramètrage du RenderBuffer (selon la taille du canvas OpenGL) glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1024, 768); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, idDepthrenderbuffer); (optionnel)Afin de vérifier que votre FrameBufferObject a bien été parametré, vous pouvez utiliser la fonction suivante :
// Vérification if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false; // On débind le FBO glBindFramebuffer(GL_FRAMEBUFFER, 0);
Première passe OpenGL : Génération de la texture
Les shaders
Il est très facile de dire au fragment shader d'écrire ses couleurs RGB dans la texture. En effet, nous avions choisit de créer notre texture dans l'emplacement zéro (GL_COLOR_ATTACHMENT0), il suffit de rajouter dans les définitions des variables de votre fragment shader : layout(location = 0) out vec3 color_out.
#version 330 #classic fragment shader in vec4 color_v; layout(location = 0) out vec4 color_out; // placage des couleurs sur la texture0 void main() { color_out = color_v; }
Changement dans la fonction d'affichage
Il y a très peu de changements par rapport à un rendu classique. Il faut uniquement activer le FBO et la texture que nous avons crée précédemment.
void display(void) { // ------------------------------------------------------------------------------------------------------------- // 1ere passe OpenGL (model -> texture) // ------------------------------------------------------------------------------------------------------------- // Activation du test de profondeur glEnable(GL_DEPTH_TEST); // Activation et binding la texture glBindTexture(GL_TEXTURE_2D, idTexture); glActiveTexture(GL_TEXTURE0); // Activation du FBO glBindFramebuffer(GL_FRAMEBUFFER, idFBO); glViewport(0, 0, g_windowWidth, g_windowHeight); // Changement de la couleur de background glClearColor(0.0f,0.0f,0.0f,1.0f); // Rafraichissement des buffers (reset) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); LoadShaders(); // Chargement des shaders LoadUniform(); // Transmission des variables uniformes au shaders model->draw(); // Rendu du modèle dans la texture [...] }
Seconde passe OpenGL : Placage de la texture sur un quad
Création d'un quad avec des VertexBufferObject (optionnel)
Une des grandes forces de l'OpenGL moderne est certainement les Vertex Buffer Object. Nous allons utiliser cette structure pour modéliser notre quadrilatère devant acquellir la texture. Le quadrilatère prendra toute la taille de l'écran et sera modèliser par deux triangles.
(optionnel) void generateFullQuad() { numberOfVertices = 4; numberOfTriangles = 2; //Initialisation du tableau contenant les coordonnées XYZ des sommets dans le monde positions = new GLfloat[3 * numberOfVertices]; //Initialisation du tableau contenant les coordonnées XYZ des sommets dans la texture texCoords = new GLfloat[2 * numberOfVertices]; //Initialisation du tableau contenant indices des triangles indexes = new GLfloat[3 * numberOfTriangles]; // Coin bas-gauche positions[0] = -1.0f; positions[1] = -1.0f; positions[2] = 0.0f; texCoords[0] = 0.0f; texCoords[1] = 0.0f; // Coin haut-gauche positions[3] = -1.0f; positions[4] = 1.0f; positions[5] = 0.0f; texCoords[2] = 0.0f; texCoords[3] = 1.0f; // Coin haut-droit positions[6] = 1.0f; positions[7] = 1.0f; positions[8] = 0.0f; texCoords[4] = 1.0f; texCoords[5] = 1.0f; // Coin bas-droit positions[9] = 1.0f; positions[10] = -1.0f; positions[11] = 0.0f; texCoords[6] = 1.0f; texCoords[7] = 0.0f; // Face triangulaire 1 indexes[0] = 0; indexes[1] = 1; indexes[2] = 2; // Face triangulaire 2 indexes[3] = 2; indexes[4] = 3; indexes[5] = 0; //Génération des VBO glGenBuffers(1, &idOfPositionArray); glGenBuffers(1, &idOfTexCoordArray); glGenBuffers(1, &idOfIndexArray); //Remplissage des VBO glBindBuffer(GL_ARRAY_BUFFER, idOfPositionArray); glBufferData(GL_ARRAY_BUFFER, 3 * numberOfVertices * sizeof(GLfloat), positions, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, idOfTexCoordArray); glBufferData(GL_ARRAY_BUFFER, 2 * numberOfVertices * sizeof(GLfloat), texCoords, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idOfIndexArray); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * numberOfTriangles * sizeof(GLuint), indexes, GL_STATIC_DRAW); } (optionnel)
Les shaders
Le quad final sera texturé à l'aide d'un nouveau couple de shaders.
Vertex shader
#version 330 in vec3 vertex_in; in vec2 texCoord_in; out vec2 texCoord_v; void main() { texCoord_v = texCoord_in; gl_Position = vec4(vertex_in, 1.0); }
Fragment shader
#version 330 in vec2 texCoord_v; uniform sampler2D tex0; out vec4 color_out; void main() { // Recupération de la couleur du pixel courant color_out = texture(tex0, texCoord_v.xy);; }
Changement dans la fonction d'affichage
Pour finir, il faut rajouter quelques instructions dans notre fonction d'affichage.void display() { [...] // ------------------------------------------------------------------------------------------------------------- // Deuxième passe OpenGL (texture -> quad) // ------------------------------------------------------------------------------------------------------------- // Désactivation du FBO glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, g_windowWidth, g_windowHeight); // Desactivation du test de profondeur glDisable(GL_DEPTH_TEST); LoadShaders(); // Chargement des shaders LoadUniform(); // Transmission des variables uniformes au shaders quad->draw(); // Rendu du quad // Désactivation de la texture glBindTexture(GL_TEXTURE_2D, 0); }
MnK a écrit : le 10/03/2013 à 8:09pm
Nice, nice.
Je m'en inspirera surement dépendant de la direction qu'on va prendre dans le TER.
Pour ton prochain tutoriel de programmation tu pourrais essayer de détailler l'ensemble des fonctions auxquelles tu fais appel? Cela semble un peu obscur par moments.
Merci en tout cas.
KottoGeek a écrit : le 13/09/2014 à 10:14pm
Ok pour le principe mais il en manque tellement de code (les fonctions par exemple) que c'est non expoitable
En l'occurrence comment tu lies les seconds shaders au programme?