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 :
#
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;
}
#
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
(
);
}
#
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 :
#
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;
}
#
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
(
);
}
#
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▲
III. Remerciements▲
Nous remercions JeGX de nous avoir permis de republier son article.
Merci à Wachter pour sa relecture orthographique.