Comment calculer la position et la normale dans le vertex shader avec OpenGL (GLSL) et Direct3D (HLSL) - Troisième partie

| JeGX | Guillaume Belz |

Traducteur

Alexandre Laurent

Troisième et dernière partie consacrée au calcul de la position et de la normale dans le vertex shader. Cet article revient et détaille la théorie des transformations d'espace de coordonnées.

Commentez

- Titre : Comment calculer la position et la normale dans le vertex shader avec OpenGL (GLSL) et Direct3D (HLSL) - Troisième partie

- Auteurs : JeGX, Guillaume Belz

- Traducteur : Alexandre Laurent

- Parution : 1 février 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Geeks3D. 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. Les espaces de coordonnées

- II. Remerciements

I - Les espaces de coordonnées

Après la publication de mes articles précédents sur le calcul de la position dans le vertex shader (partie 1, partie 2), j'ai reçu une explication détaillée de la part de Graham sur les espaces de coordonnées et bien sûr sur le calcul de… la position ! Je pense que c'est un texte à partager avec mes lecteurs. J'ai légèrement modifié le texte original pour correspondre aux besoins de l'article, mais l'explication est inchangée.

Les espaces de coordonnées sont une notion importante qui doit être claire pour tout le monde. Globalement, quatre systèmes de coordonnées majeurs coexistent : Object (objet), World (monde), View (vue) et Clip.

Nous avons besoin d'une matrice de transformation entre chacun d'entre eux. La matrice de modèle transforme de l'espace objet à l'espace monde, la matrice de vue transforme de l'espace monde à l'espace vue et la matrice de projection (avec la division homogène implicite) transforme la vue en espace de coordonnées clip.

Les vertex entrants (les entrées du vertex shader) sont généralement dans l'espace de coordonnées objet, sauf si nous avions un objet qui est notre image référence (comme un terrain).

Pour l'éclairage, nous avons besoin soit des positions dans l'espace monde, soit dans l'espace vue, mais pas les deux. L'éclairage dans l'espace de coordonnées monde nécessite de connaître la position de la caméra dans l'espace de coordonnées monde, alors que l'éclairage dans l'espace de coordonnées vue nécessite d'avoir la position de la lumière dans l'espace de coordonnées vue. La première solution est généralement plus simple à gérer (une mise à jour par image plutôt qu'une par lumière).

Toutefois, il sera plus efficace de rassembler les matrices. En OpenGL classique, nous aurions eu gl_ModelViewMatrix. Cette matrice rassemble la matrice de modèle avec la matrice de vue et transforme les vertex de l'espace de coordonnées objet à l'espace de coordonnées vue directement. Nous avons aussi les matrices gl_ProjectionMatrix et gl_ModelViewProjectionMatrix, qui transforment les vertex de l'espace de coordonnées vue à l'espace de coordonnées clip et de l'espace de coordonnées objet à l'espace de coordonnées clip respectivement. Il est recommandé d'utiliser des variables uniforms pour celles-ci. Notre vertex shader minimal pourrait donc être aussi simple que :

uniform mat4 model_view_projection_matrix;

void main (void)
{
gl_Position = model_view_projection_matrix * gl_Vertex;
}

Une seule multiplication de matrice est suffisante si nous n'avons pas besoin des résultats intermédiaires.

Si nous avons besoin des coordonnées dans l'espace de coordonnées monde ou dans l'espace de coordonnées clip, nous devons au moins faire une transformation de plus. Les vertex shaders suivants sont équivalents :

uniform mat4 model_view_matrix;
uniform mat4 projection_marix;

void main (void)
{
vec4 view_space_vertex = model_view_matrix * gl_Vertex;
gl_Position = projection_matrix * view_space_vertex;
}

uniform mat4 model_matrix;
uniform mat4 view_projection_matrix;

void main (void)
{
vec4 world_space_vertex = model_matrix * gl_Vertex;
gl_Position = view_projection_matrix * world_space_vertex;
}

Les deux nécessitent deux multiplications de matrices - que nous devons effectuer selon l'espace de coordonnées dont nous avons besoin (si nous voulons effectuer l'éclairage dans l'espace de coordonnées monde ou dans l'espace de coordonnées vue).

Par contre, il y a, ici, des dépendances. Le second peut être meilleur, car la variable uniforme model_matrix changera pour chaque objet affiché alors que la variable uniforme view_projection_matrix ne changera probablement qu'une fois par image. De plus, même si cela ne fera certainement pas une grande différence, la seconde multiplication requiert le résultat de la première. Suivant le contenu de la matrice et de la machine sur laquelle le programme tourne, cela peut provoquer des problèmes. Il serait préférable d'aller de l'espace de coordonnées objet à l'espace dans lequel nous voulons être :

uniform mat4 model_matrix;
uniform mat4 model_view_matrix;
uniform mat4 model_view_projection_matrix;

void main (void)
{
vec4 world_space_vertex = model_matrix * gl_Vertex;
vec4 view_space_vertex = model_view_matrix * gl_Vertex;
gl_Position = model_view_projection_matrix * gl_Vertex;
}

Ici, les trois calculs sont indépendants et plus précis, car il n'y a pas de valeurs intermédiaires. Nous n'avons certainement besoin que de deux des résultats suivant les nécessités de notre algorithme et la façon dont nous rassemblons les matrices.

Finalement, toutes ces transformations sont linéaires et donc leurs résultats peuvent être interpolés. Par exemple, nous voulons la lumière dans l'espace de coordonnées monde. Nous avons la position de notre caméra dans l'espace de coordonnées monde et nous calculons les coordonnées dans l'espace de coordonnées monde de notre vertex pouvant être tous les deux interpolés et, donc, le delta peut être aussi interpolé :

uniform mat4 model_matrix;
uniform mat4 model_view_projection_matrix;
uniform vec3 viewer_position; // Position de la caméra en coordonnées monde
out vec3 view_vector ;

void main (void)
{
vec4 world_space_vertex = model_matrix * gl_Vertex;
view_vector = world_space_vertex.xyz - viewer_position;
gl_Position = model_view_projection_matrix * gl_Vertex;
}

Une nouvelle soustraction est apparue dans le vertex shader, mais évite une variable uniforme model_view_matrix pour chaque mise à jour de model_matrix. C'est particulièrement important si model_matrix est un énorme tableau et que nous utilisons l'instanciation pour indexer dans celui-ci. La soustraction supplémentaire n'est pas très importante, car les opérations sur une UAL de GPU sont peu coûteuses par rapport aux mêmes opérations sur le CPU et aux mises à jour des variables uniform : il est plus économique de mettre à jour deux matrices à chaque image qu'une centaine ou plus. Bien sûr, nous aurons peut-être aussi besoin d'une matrice pour les normales. Celle-ci transforme les normales de l'espace de coordonnées objet à l'espace de coordonnées vue et peut être donc dérivée à partir des matrices de modèle et de vue.

II - Remerciements

Cet article est une traduction autorisée de l'article paru sur Geeks3D.com.

Je tiens à remercier gbdivers, Winjerome et dourouc05 pour leur relecture lors de la traduction de cet article, ainsi que ClaudeLELOUP pour sa relecture orthographique.