Rendu de Particules et Billboarding avec le Geometry Shader (GLSL)

Après avoir vu ce qu’est le billboarding et une façon de l’implémenter dans le vertex shader, regardons maintenant une autre méthode, plus adaptée pour le rendu des particules.

En général, les particules sont des points (une position 3D par particule) et le rendu d’une particle texturée nécessite la transformation d’un point en un quad. Les point 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 4 coins, les coordonnées de texture, etc.) il faut reproduire la transformation appliquée à une particule par la fonctionnalité cablée point-sprite.

Pour y parvenir nous allons utiliser le geometry shader qui est tout particulièrement bien adapté à notre problème: transformer un vertex (une particule) en quatre vertices (un quad). Et cette opération d’amplication 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 vertex en entrée (ce vertex vient du vertex shader) en quatre vertices qui formeront le quad suivant composé des vertices a, b, c et d:

Encore une fois (comme dans l’article sur le billboarding dans le vertex shader), nous allons utiliser la matrice ModelView. Mais cette fois-ci, nous allons juste extraire des données qui nous permettront de calculer la position des 4 vertices 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 extrait de la matrice inverse de ModelView. La matrice de model-view est généralement une matrice orthogonale et l’inverse d’une matrice orthogonale est égale à 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 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 camera, la transformation d’un point (avec une position P situé 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 faite, voilà le code complet du programme GLSL de billboarding avec le geometry shader tel qu’il est utilisé dans la demo GLSL Hacker qui accompagne cet article:

Vertex shader

#version 150
in vec4 gxl3d_Position;
in vec4 gxl3d_Color;

// GLSL Hacker automatic uniforms:
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 automatic uniforms:
uniform mat4 gxl3d_ViewProjectionMatrix;
uniform mat4 gxl3d_ModelViewMatrix;

uniform float size; // Particle size

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 allons travailler dans l’espace camera (view space) au niveau du geometry shader. La position du vertex dans le vertex shader est maintenant multipliée par la matrice model-view au lieu de la matrice model. 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 vertex en utilisant des vecteurs unitaires très simples. Attention: 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 automatic uniforms:
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 automatic uniforms:
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: left-bottom 
  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: left-top
  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: right-bottom
  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: right-top
  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émos GLSL Hacker de billboarding avec geometry shader sont disponibles dans le répertoire host_api/GLSL_Billboarding_Geometry_Shader/ du demopack.

Quelques 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
- The View Matrix


Leave a Reply

Your email address will not be published. Required fields are marked *

CommentLuv badge