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

JeGX

Traducteur

Alexandre Laurent

Suite du précédent tutoriel où l'on apprenait comment calculer la position et la normale dans le vertex shader. Cet article revient sur ce calcul pour en analyser la performance.

Commentez

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

- Auteur : JeGX

- Traducteur : Alexandre Laurent

- Parution : 7 janvier 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. Optimisation du vertex shader

- I-A. Mise à jour du 31 octobre 2011

- II. Remerciements

I - Optimisation du vertex shader

Précédemment, j'ai publié un simple code d'exemple pour calculer la position et la normale dans le vertex shader en OpenGL (GLSL) et Direct3D (HLSL). Le but était de montrer le fonctionnement des différentes matrices : projection, view (vue) et model (modèle) pour déterminer la variable gl_Position et notamment l'ordre des multiplications des matrices avec OpenGL et Direct3D. Voici le vertex shader GLSL (nommé VS_A) :

Vertex shader GLSL : VS_A

uniform mat4 M; // Matrice du modèle
uniform mat4 V; // Matrice de vue
uniform mat4 P; // Matrice de projection

void main()
{
vec4 v = gl_Vertex;
mat4 MV = V * M;
mat4 MVP = P * MV;
vec4 v1 = MVP * v;
gl_Position = v1;
}

Cette méthode construit la matrice de transformation finale (MVP ou ModelViewProjection) et la multiplie avec la position du vertex dans l'espace local de l'objet (gl_Vertex). Cette méthode fonctionne mais génère un grand nombre d'instructions GPU.

J'ai démarré l'outil GPU Shader Analyzer et compilé le vertex shader précédent :

Une fois compilé, le vertex shader VS_A génère 49 instructions UAL(1).

Daniel Rakos m'a montré une solution plus rapide :

Vertex shader GLSL : VS_B

uniform mat4 M; // Matrice du modèle
uniform mat4 V; // Matrice de vue
uniform mat4 P; // Matrice de projection
void main()
{
vec4 v = gl_Vertex;
vec4 v1 = M * v;
vec4 v2 = V * v1;
vec4 v3 = P * v2;
gl_Position = v3;
}

Une fois compilé dans GPU ShaderAnalyzer, VS_B génère 13 instructions UAL.

J'ai rapidement effectué un test dans GeeXLab avec un objet (sphère) contenant deux millions de faces et un million de vertex :

- VS_A : 425 FPS ;

- VS_B : 425 FPS.

J'ai aussi effectué un test avec une simple démonstration OpenGL (une simple application de test win32) avec de l'instanciation géométrique (10 000 instances, 900 vertex par instance), sans trouver de différence entre les deux vertex shaders. Je m'attendais à une petite différence. Une explication peut être due à la simplicité de la scène. Je suis cependant sûr que nous allons voir une différence dans des shaders ou des scènes 3D plus complexes, car il doit y avoir un gain de performances entre 49 et 13 instructions ALU…

I-A - Mise à jour du 31 octobre 2011

Je pense avoir trouvé une réponse possible : le surcoût provoqué par l'appel du shader et/ou de l'accès aux vertex. En effet, si la charge de travail réelle du vertex shader est trop petite, la majorité du temps est perdue dans d'autres tâches telles que la récupération des vertex ou l'appel au shader ou, plus généralement, toute tâche venant avant ou après l'exécution du vertex shader. En conséquence, une différence d'une trentaine d'instructions ALU n'est pas visible.

Pour valider cette idée, j'ai augmenté le travail du vertex shader et comparé les deux vertex shaders suivants avec un objet constitué d'un million de vertex :

Vertex shader A (vsA)

uniform mat4 M; // Matrice du modèle
uniform mat4 V; // Matrice de vue
uniform mat4 P; // Matrice de projection

void main()
{
vec4 v = gl_Vertex;
vec4 pos = vec4(0.0);
for (int i=0; i<100; i++)
{
mat4 MV = V * M;
mat4 MVP = P * MV;
vec4 v1 = MVP * v;
pos += v1;
}
gl_Position = pos / 100.0;
}

et

Vertex shader B (vsB)

uniform mat4 M; // Matrice du modèle
uniform mat4 V; // Matrice de vue
uniform mat4 P; // Matrice de projection

void main()
{
vec4 v = gl_Vertex;
vec4 pos = vec4(0.0);
for (int i=0; i<100; i++)
{
vec4 v1 = M * v;
vec4 v2 = V * v1;
vec4 v3 = P * v2;
pos += v3;
}
gl_Position = pos / 100.0;
}

Avec le vertex shader A, la démonstration tourne à 19 FPS. Avec le vertex shader B, la démonstration tourne à 64 FPS.

Ouf, trouvé ! L'intuition était bonne : il y a une importante différence de vitesse entre ces deux vertex shaders.

II - Remerciements

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

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

(1)Note de traduction : unité arithmétique et logique (en anglais, Arithmetic Logic Unit, soit ALU). Il s'agit du composant effectuant les opérations arithmétiques et logiques dans un processeur. Dans le contexte de l'article, cela correspond aux cœurs du processeur de la carte graphique exécutant les shaders.