Shaders OpenGL 4

Donald Urquhart

Depuis la version 3, OpenGL a subi beaucoup de modifications, la plus importante étant l'orientation franche vers les shaders. Ce tutoriel les introduit.

Commentez

- Titre : Shaders OpenGL 4

- Auteur : Donald Urquhart

- Parution : 26 juin 2012

- Mise à jour : 28 octobre 2017

- Licence : Copyright ® 2010 Donald Urquhart. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Code

- III-A. Opengl_3.h

- III-B. Opengl_3.cpp

- III-C. Shader.cpp

- III-D. Shader.vert

- III-E. Shader.frag

- IV. Résultat

- V. Remerciements

I - L'article original

Cet article est une adaptation en langue française de 2. OpenGL 4 Shaders, de Donald Urquhart.

II - Introduction

OpenGL 3 et suivants sont très orientés vers les shaders : on montre ici comment intégrer le code de shaders dans une application OpenGL. On ne donnera pas de détails au sujet du langage GLSL, le langage des shaders d'OpenGL, ce sujet est traité à part.

Ce tutoriel est un guide rapide sur la manière de faire fonctionner les shaders dans le framework actuel, le suivant traitera des matrices. On ne présentera que les shaders de type vertex et fragment, les geometric shaders seront présentés plus tard.

III - Code

Le code de ce tutoriel est assez minimal et se base sur le tutoriel GLSL. On commence par récupérer les fichiers shader.h et shader.cpp que l'on ajoute au projet Visual Studio.

shader.h

#ifndef __SHADER_H
#define __SHADER_H

#if ( (defined(__MACH__)) && (defined(__APPLE__)) )
# include <stdlib.h>
# include <OpenGL/gl.h>
# include <GLUT/glut.h>
# include <OpenGL/glext.h>
#else
# include <stdlib.h>
# include <GL/glew.h>
# include <GL/gl.h>
# include <GL/glut.h>
# include <GL/glext.h>
#endif

#include <string>

class Shader {
public:
Shader();
Shader(const char *vsFile, const char *fsFile);
~Shader();

void init(const char *vsFile, const char *fsFile);

void bind();
void unbind();

unsigned int id();

private:
unsigned int shader_id;
unsigned int shader_vp;
unsigned int shader_fp;
};

#endif

shader.cpp

#include &#8220;shader.h&#8221;
#include <string.h>
#include <iostream>
#include <stdlib.h>

using namespace std;

static char* textFileRead(const char *fileName) {
char* text;

if (fileName != NULL) {
FILE *file = fopen(fileName, &#8220;rt&#8221;);

if (file != NULL) {
fseek(file, 0, SEEK_END);
int count = ftell(file);
rewind(file);

if (count > 0) {
text = (char*)malloc(sizeof(char) * (count + 1));
count = fread(text, sizeof(char), count, file);
text[count] = &#8216;\0';
}
fclose(file);
}
}
return text;
}

Shader::Shader() {

}

Shader::Shader(const char *vsFile, const char *fsFile) {
init(vsFile, fsFile);
}

void Shader::init(const char *vsFile, const char *fsFile) {
shader_vp = glCreateShader(GL_VERTEX_SHADER);
shader_fp = glCreateShader(GL_FRAGMENT_SHADER);

const char* vsText = textFileRead(vsFile);
const char* fsText = textFileRead(fsFile);

if (vsText == NULL || fsText == NULL) {
cerr << &#8220;Either vertex shader or fragment shader file not found.&#8220; << endl;
return;
}

glShaderSource(shader_vp, 1, &vsText, 0);
glShaderSource(shader_fp, 1, &fsText, 0);

glCompileShader(shader_vp);
glCompileShader(shader_fp);

shader_id = glCreateProgram();
glAttachShader(shader_id, shader_fp);
glAttachShader(shader_id, shader_vp);
glLinkProgram(shader_id);
}

Shader::~Shader() {
glDetachShader(shader_id, shader_fp);
glDetachShader(shader_id, shader_vp);

glDeleteShader(shader_fp);
glDeleteShader(shader_vp);
glDeleteProgram(shader_id);
}

unsigned int Shader::id() {
return shader_id;
}

void Shader::bind() {
glUseProgram(shader_id);
}

void Shader::unbind() {
glUseProgram(0);
}

shader.vert

void main() {
// Définit la position de l'arête courante.
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

shader.frag

void main() {
// Définit la couleur de sortie du pixel courant.
gl_FragColor = vec4(1.0);
}

III-A - Opengl_3.h

Dans Opengl_3.h, qui contient la structure de la classe de contexte OpenGL, on s'assure de bien inclure la classe de shader :

#include "shader.h"

Dans les variables privées du contexte, on ajoute une instance de variable de shader. Si on travaille avec plusieurs shaders, on peut utiliser un tableau ou une série de variables, avec une préférence pour le tableau.

class OpenGLContext {
...
private:
int windowWidth;  // Largeur de la fenêtre
int windowHeight; // Hauteur de la fenêtre

Shader *shader;   // Shader GLSL
...
};

III-B - Opengl_3.cpp

Avec la variable de shader déclarée, on peut modifier la fonction setupScene pour la prendre en compte : on crée un objet de shader qui utilise shader.vert et shader.frag.

void OpenGLContext::setupScene(void) {
glClearColor(0.4f, 0.6f, 0.9f, 0.0f); // // On définit la couleur à la valeur par défaut de XNA

shader = new Shader("shader.vert", "shader.frag"); // On crée le shader en chargeant les shaders de type vertex et fragment.
}

Ensuite, tout ce qu'il reste à faire est, dans renderScene(), de lier et délier les shaders comme requis. Il n'y a pas encore de géométrie à afficher, on ne voit donc encore rien, mais cela va venir.

void OpenGLContext::renderScene(void) {
glViewport(0, 0, windowWidth, windowHeight); // On définit la zone de dessin à la fenêtre
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // On vide les tampons.

shader->bind(); // Lier le shader

shader->unbind(); // Délier le shader.

SwapBuffers(hdc); // On échange les tampons pour afficher quelque chose.
}

III-C - Shader.cpp

Par rapport au fichier présenté en introduction et repris du tutoriel GLSL, on a dû effectuer un changement majeur : on lie deux variables au shader. Il s'agit des constantes in_Position et in_Color ; elles seront utilisées pour envoyer les positions et les couleurs au shader sans les lier et délier à chaque image. On a modifié comme ceci la fonction d'initialisation :

void Shader::init(const char *vsFile, const char *fsFile) {
...
glBindAttribLocation(shader_id, 0, "in_Position"); // Lier un attribut constant pour les positions des arêtes.
glBindAttribLocation(shader_id, 1, "in_Color"); //  Lier un attribut constant pour la couleur.
...
}

III-D - Shader.vert

Voici un petit aperçu, supplémentaire au tutoriel GLSL, des changements apportés au shaders de type vertex et fragment dans GLSL 1.50.

Pour préciser la version employée, on utilise une ligne comme celle-ci (pour GLSL 1.50) :

#version 150 core

On peut ainsi utiliser toute version valide de GLSL. Dans la 1.50, on ne dispose plus de gl_ModelViewMatrix, gl_ProjectionMatrix, gl_Vertex et autres, on doit maintenant gérer et envoyer toutes ces informations soi-même aux shaders. Dans le prochain tutoriel, on présentera des modèles automanipulés, des matrices de vue et de projection, mais on se limitera ici aux bases.

Avec la création des deux variables in_Position et in_Color pour lier le shader, on doit les créer ici. Il s'agira de variables de type vec3 ; puisqu'on utilise GLSL 1.50, on doit également préciser s'il agit d'entrées in ou de sorties out du shader courant, il n'y a plus de variable uniforme ou variable à gérer.

On crée également une variable de sortie pass_Color, également un vec3, qui servira à passer une couleur de sortie du vertex shader au fragment shader. Heureusement, la définition de la position finale requiert un code similaire, la définition de la variable gl_Position.

#version 150 core
in vec3 in_Position;
in vec3 in_Color;
out vec3 pass_Color;

void main(void) {
gl_Position = vec4(in_Position, 1.0);
pass_Color = in_Color;?
}

III-E - Shader.frag

Dans le fragment shader, on déclare la version de GLSL utilisée et, puisqu'on a une variable de sortie du vertex shader, on doit créer une variable d'entrée de même nom et de même type dans le fragment shader.

À présent, les fragment shaders ne disposent plus de gl_FragColor : on doit maintenant déclarer manuellement une variable de sortie de type vec4, qui donnera la couleur au GPU. On l'appellera out_Color et on la définit à la valeur de pass_Color, la variable envoyée au shader.

#version 150 core
in vec3 pass_Color;
out vec4 out_Color;

void main(void) {
out_Color = vec4(pass_Color, 1.0);
}

IV - Résultat

Si tout s'est bien passé, le projet devrait être prêt à utiliser des shaders et devrait se lancer sans erreur. Encore deux tutoriels : à la fin du quatrième, on affichera des choses à l'écran. OpenGL 3 et 4 requièrent plus de préparation, mais cela fait une grande différence à la fin, surtout quand il s'agit de vitesse : le rendu devrait être beaucoup plus efficace.

Les fichiers de projet Visual Studio 2010 de ce tutoriel sont disponibles au téléchargement.

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/v/6ikam4AekRQ?version=3&feature=player_detailpage

V - Remerciements

Merci à Alexandre Laurent pour son aide à la traduction et à Claude Leloup pour sa relecture orthographique !