Rendu de particules et billboarding avec le geometry shader (GLSL)

JeGX

Après avoir étudié ce qu'est le billboarding et une façon de l'implémenter dans le vertex shader, intéressons-nous maintenant à une autre méthode, plus adaptée au rendu des particules.

Commentez

- Titre : Rendu de particules et billboarding avec le geometry shader (GLSL)

- Auteur : JeGX

- Parution : 14 octobre 2014

- 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 ® 2014 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. Introduction

- II. Références

- III. Remerciements

I - Introduction

Après avoir étudié ce qu'est le billboarding et une façon de l'implémenter dans le vertex shader, intéressons-nous maintenant à une autre méthode, plus adaptée au rendu des particules.

En général, les particules sont des points (une position 3D par particule) et le rendu d'une particule texturée nécessite la transformation d'un point en un quad. Les points sprites sont une solution efficace au rendu des particules. Mais si l'on veut avoir un contrôle plus fin sur la génération du quad (la position des quatre coins, les coordonnées de texture, etc.), il faut reproduire la transformation appliquée à une particule par la fonctionnalité câblée de point sprite.

Pour y parvenir, nous utiliserons le geometry shader qui est tout particulièrement bien adapté à notre problème : transformer un sommet (une particule) en quatre sommets (un quad). Et cette opération d'ampliation particulière (1:4) est si importante que les GPUs Radeon ont un support spécial pour ce cas :

… and similarly since the geometry shader is expected to be commonly used for replacing point sprites (by expanding point primitives to a triangle strip with two triangles) there is also special hardware for taking care of the 1:4 case.

Source : Radeon HD 2000 Programming Guide (page 9)

La tâche du geometry shader est donc de transformer un sommet en entrée (ce sommet vient du vertex shader) en quatre sommets qui formeront le quad suivant composé des sommets a, b, c et d :

Encore une fois (comme dans l'article sur le billboarding dans le vertex shader), nous utiliserons la matrice ModelView. Mais cette fois-ci, nous extrairons des données qui nous permettront de calculer la position des quatre sommets du quad afin qu'il soit toujours vu de face.

Tout le secret réside dans la récupération de deux vecteurs qui permettent de définir le plan de la caméra. Ces deux vecteurs, right et up sont extraits de la matrice inverse de modèle vue. La matrice modèle vue est généralement une matrice orthogonale et l'inverse de cette dernière est égal à sa transposée : les colonnes et les lignes sont inversées par rapport à la diagonale principale.

La matrice ModelView est la suivante :

L'inverse (ou la transposée dans notre cas) est :

Les vecteurs right et up sont donc les suivants (on utilise la matrice ModelView non transposée) :

right.x = ModelView[0][0]  // 0
right.y = ModelView[1][0]  // 4
right.z = ModelView[2][0]  // 8

up.x = ModelView[0][1]  // 1
up.y = ModelView[1][1]  // 5
up.z = ModelView[2][1]  // 9

Maintenant que nous avons les deux vecteurs qui forment le plan de la caméra, la transformation d'un point (avec une position P située sur un plan quelconque parallèle au plan de la caméra) en un quad de taille size se fait de la manière suivante :

vec3 a = P - (right + up) * size;
vec3 b = P - (right - up) * size;
vec3 d = P + (right - up) * size;
vec3 c = P + (right + up) * size;

Et voilà, nous avons notre quad billboardé !

Maintenant que la partie la plus importante de la théorie est terminée. Voici le code complet du programme GLSL de billboarding avec le geometry shader tel qu'il est utilisé dans la démonstration GLSL Hacker qui accompagne cet article :

Vertex shader

#version 150
in vec4 gxl3d_Position;
in vec4 gxl3d_Color;

// GLSL Hacker uniformes automatiques :
uniform mat4 gxl3d_ModelMatrix;

out Vertex
{
vec4 color;
} vertex;

void main()
{
gl_Position = gxl3d_ModelMatrix * gxl3d_Position;
vertex.color = gxl3d_Color;
}

Geometry shader

#version 150

layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;

// GLSL Hacker uniformes automatiques :
uniform mat4 gxl3d_ViewProjectionMatrix;
uniform mat4 gxl3d_ModelViewMatrix;

uniform float size; // Taille de la particule

in Vertex
{
vec4 color;
} vertex[];

out vec2 Vertex_UV;
out vec4 Vertex_Color;

void main (void)
{
mat4 MV = gxl3d_ModelViewMatrix;

vec3 right = vec3(MV[0][0],
MV[1][0],
MV[2][0]);

vec3 up = vec3(MV[0][1],
MV[1][1],
MV[2][1]);

vec3 P = gl_in[0].gl_Position.xyz;

mat4 VP = gxl3d_ViewProjectionMatrix;

vec3 va = P - (right + up) * size;
gl_Position = VP * vec4(va, 1.0);
Vertex_UV = vec2(0.0, 0.0);
Vertex_Color = vertex[0].color;
EmitVertex();

vec3 vb = P - (right - up) * size;
gl_Position = VP * vec4(vb, 1.0);
Vertex_UV = vec2(0.0, 1.0);
Vertex_Color = vertex[0].color;
EmitVertex();

vec3 vd = P + (right - up) * size;
gl_Position = VP * vec4(vd, 1.0);
Vertex_UV = vec2(1.0, 0.0);
Vertex_Color = vertex[0].color;
EmitVertex();

vec3 vc = P + (right + up) * size;
gl_Position = VP * vec4(vc, 1.0);
Vertex_UV = vec2(1.0, 1.0);
Vertex_Color = vertex[0].color;
EmitVertex();

EndPrimitive();
}

Fragment shader

#version 150
uniform sampler2D tex0;
in vec2 Vertex_UV;
in vec4 Vertex_Color;
out vec4 FragColor;
void main (void)
{
vec2 uv = Vertex_UV.xy;
uv.y *= -1.0;
vec3 t = texture(tex0,uv).rgb;
FragColor = vec4(t, 1.0) * Vertex_Color;
}

Voyons maintenant une autre approche très similaire mais plus simple, puisque le calcul des vecteurs right et up n'est plus nécessaire. Pour cela, nous travaillerons dans l'espace caméra (view space) au niveau du geometry shader. La position du sommet dans le vertex shader est maintenant multipliée par la matrice modèle vue au lieu de la matrice modèle. Le vertex en entrée du geometry shader est à présent exprimé dans l'espace de la caméra et il est alors très simple de le transformer en un quad : il suffit de translater le sommet en utilisant des vecteurs unitaires très simples.

Il faut juste modifier les coordonnées x et y et surtout ne pas modifier les coordonnées z et w.

Après quoi, la nouvelle position est simplement multipliée par la matrice de projection de la caméra :

Vertex shader

#version 150
in vec4 gxl3d_Position;
in vec4 gxl3d_Color;

// GLSL Hacker uniformes automatiques :
uniform mat4 gxl3d_ModelViewMatrix;

out Vertex
{
vec4 color;
} vertex;

void main()
{
gl_Position = gxl3d_ModelViewMatrix * gxl3d_Position;
vertex.color = gxl3d_Color;
}

Geometry shader

#version 150

layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;

// GLSL Hacker uniformes automatiques :
uniform mat4 gxl3d_ProjectionMatrix;

uniform float particle_size;

in Vertex
{
vec4 color;
} vertex[];

out vec2 Vertex_UV;
out vec4 Vertex_Color;

void main (void)
{
vec4 P = gl_in[0].gl_Position;

// a: bas gauche
vec2 va = P.xy + vec2(-0.5, -0.5) * particle_size;
gl_Position = gxl3d_ProjectionMatrix * vec4(va, P.zw);
Vertex_UV = vec2(0.0, 0.0);
Vertex_Color = vertex[0].color;
EmitVertex();

// b: haut gauche
vec2 vb = P.xy + vec2(-0.5, 0.5) * particle_size;
gl_Position = gxl3d_ProjectionMatrix * vec4(vb, P.zw);
Vertex_UV = vec2(0.0, 1.0);
Vertex_Color = vertex[0].color;
EmitVertex();

// d: bas droit
vec2 vd = P.xy + vec2(0.5, -0.5) * particle_size;
gl_Position = gxl3d_ProjectionMatrix * vec4(vd, P.zw);
Vertex_UV = vec2(1.0, 0.0);
Vertex_Color = vertex[0].color;
EmitVertex();

// c: haut droit
vec2 vc = P.xy + vec2(0.5, 0.5) * particle_size;
gl_Position = gxl3d_ProjectionMatrix * vec4(vc, P.zw);
Vertex_UV = vec2(1.0, 1.0);
Vertex_Color = vertex[0].color;
EmitVertex();

EndPrimitive();
}

Fragment shader

#version 150
uniform sampler2D tex0;
in vec2 Vertex_UV;
in vec4 Vertex_Color;
out vec4 FragColor;
void main (void)
{
vec2 uv = Vertex_UV.xy;
uv.y *= -1.0;
vec3 t = texture(tex0,uv).rgb;
FragColor = vec4(t, 1.0) * Vertex_Color;
}

Les démonstrations GLSL Hacker de billboarding avec geometry shader sont disponibles dans le répertoire host_api/GLSL_Billboarding_Geometry_Shader/ du pack de démonstrations.

II - Références

- Simple Introduction to Geometry Shaders in GLSL (Part1)

- Simple Introduction to Geometry Shaders in GLSL (Part2)

- Matrice orthogonale

- Billboarding Tutorial - Cheating - Faster but not so easy

- OpenGL Transformation - ModelView

- La matrice de vue

III - Remerciements

Nous remercions JeGX de nous avoir permis de republier son article.

Merci à Wachter pour sa relecture orthographique.