web-dev-qa-db-fra.com

Sprites ponctuels pour le système de particules

Les sprites ponctuels sont-ils le meilleur choix pour construire un système de particules?

Les sprites ponctuels sont-ils présents dans les nouvelles versions d'OpenGL et les pilotes des dernières cartes graphiques? Ou dois-je le faire en utilisant vbo et glsl?

28
Javier Ramírez

Les sprites ponctuels sont en effet bien adaptés aux systèmes de particules. Mais ils n'ont rien à voir avec les VBO et le GLSL, ce qui signifie qu'ils sont une fonctionnalité complètement orthogonale. Peu importe si vous utilisez des sprites ponctuels ou non, vous devez toujours utiliser des VBO pour télécharger la géométrie, que ce soit juste des points, des sprites prédéfinis ou autre chose, et vous devez toujours mettre cette géométrie à travers un ensemble de shaders (dans OpenGL moderne bien sûr).

Cela étant dit, les sprites ponctuels sont très bien pris en charge dans OpenGL moderne, mais pas automatiquement comme avec l'ancienne approche à fonction fixe. Ce qui n'est pas pris en charge, ce sont les fonctionnalités d'atténuation des points qui vous permettent de mettre à l'échelle la taille d'un point en fonction de sa distance à la caméra, vous devez le faire manuellement à l'intérieur du vertex shader. De la même manière, vous devez effectuer la texturation du point manuellement dans un shader de fragment approprié, en utilisant la variable d'entrée spéciale gl_PointCoord (Qui indique où dans le carré [0,1] du point entier le courant fragment est). Par exemple, un pipeline Sprite ponctuel de base pourrait ressembler à ceci:

...
glPointSize(whatever);              //specify size of points in pixels
glDrawArrays(GL_POINTS, 0, count);  //draw the points

vertex shader:

uniform mat4 mvp;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = mvp * position;
}

fragment shader:

uniform sampler2D tex;

layout(location = 0) out vec4 color;

void main()
{
    color = texture(tex, gl_PointCoord);
}

Et c'est tout. Bien sûr, ces shaders font juste le dessin le plus basique des sprites texturés, mais sont un point de départ pour d'autres fonctionnalités. Par exemple, pour calculer la taille du Sprite en fonction de sa distance à la caméra (peut-être afin de lui donner une taille fixe de l'espace mondial), vous devez glEnable(GL_PROGRAM_POINT_SIZE) et écrire dans la variable de sortie spéciale gl_PointSize dans le vertex shader:

uniform mat4 modelview;
uniform mat4 projection;
uniform vec2 screenSize;
uniform float spriteSize;

layout(location = 0) in vec4 position;

void main()
{
    vec4 eyePos = modelview * position;
    vec4 projVoxel = projection * vec4(spriteSize,spriteSize,eyePos.z,eyePos.w);
    vec2 projSize = screenSize * projVoxel.xy / projVoxel.w;
    gl_PointSize = 0.25 * (projSize.x+projSize.y);
    gl_Position = projection * eyePos;
}

Cela donnerait à tous les sprites ponctuels la même taille d'espace mondial (et donc une taille d'espace d'écran différente en pixels).


Mais les sprites ponctuels tout en étant parfaitement pris en charge dans OpenGL moderne ont leurs inconvénients. L'un des plus gros inconvénients est leur comportement d'écrêtage. Les points sont découpés à leur coordonnée centrale (car le découpage est effectué avant la pixellisation et donc avant que le point ne soit "agrandi"). Donc, si le centre du point est à l'extérieur de l'écran, le reste qui pourrait encore atteindre la zone de visualisation n'est pas affiché, donc au pire une fois que le point est à mi-chemin de l'écran, il disparaîtra soudainement. Ceci n'est cependant perceptible (ou annyoing) que si les sprites ponctuels sont trop grands. Si ce sont de très petites particules qui ne couvrent pas beaucoup plus de quelques pixels chacune de toute façon, alors ce ne sera pas vraiment un problème et je considérerais toujours les systèmes de particules comme le cas d'utilisation canonique pour les sprites ponctuels, ne le faites tout simplement pas utilisez-les pour les grands panneaux d'affichage.

Mais si c'est un problème, alors OpenGL moderne offre de nombreuses autres façons d'implémenter des sprites ponctuels, à part la manière naïve de pré-construire tous les sprites en quads individuels sur le CPU. Vous pouvez toujours les rendre juste comme un tampon plein de points (et donc dans la façon dont ils sont susceptibles de sortir de votre moteur de particules basé sur GPU). Pour générer la géométrie quadruple, vous pouvez alors utiliser le shader de géométrie, qui vous permet de générer un quad à partir d'un seul point. Tout d'abord, vous ne faites que la transformation modelview à l'intérieur du vertex shader:

uniform mat4 modelview;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = modelview * position;
}

Ensuite, le shader de géométrie fait le reste du travail. Il combine la position du point avec les 4 coins d'un [0,1] -quad générique et complète la transformation en clip-espace:

const vec2 corners[4] = { 
    vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) };

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

uniform mat4 projection;
uniform float spriteSize;

out vec2 texCoord;

void main()
{
    for(int i=0; i<4; ++i)
    {
        vec4 eyePos = gl_in[0].gl_Position;           //start with point position
        eyePos.xy += spriteSize * (corners[i] - vec2(0.5)); //add corner position
        gl_Position = projection * eyePos;             //complete transformation
        texCoord = corners[i];                         //use corner as texCoord
        EmitVertex();
    }
}

Dans le fragment shader, vous utiliseriez alors bien entendu la variable texCoord personnalisée au lieu de gl_PointCoord Pour la texturation, car nous ne dessinons plus de points réels.


Ou une autre possibilité (et peut-être plus rapide, car je me souviens des shaders de géométrie réputés pour être lents) serait d'utiliser un rendu instancié. De cette façon, vous avez un VBO supplémentaire contenant les sommets de juste un seul quad 2D générique (c'est-à-dire le carré [0,1]) et votre bon vieux VBO contenant uniquement les positions des points. Ce que vous faites ensuite est de dessiner ce seul quadruple plusieurs fois (instancié), tout en recherchant les positions des instances individuelles à partir du point VBO:

glVertexAttribPointer(0, ...points...);
glVertexAttribPointer(1, ...quad...);
glVertexAttribDivisor(0, 1);            //advance only once per instance
...
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, count);  //draw #count quads

Et dans le vertex shader, vous assemblez ensuite la position par point avec la position réelle du coin/quad (qui est également la coordonnée de texture de ce sommet):

uniform mat4 modelview;
uniform mat4 projection;
uniform float spriteSize;

layout(location = 0) in vec4 position;
layout(location = 1) in vec2 corner;

out vec2 texCoord;

void main()
{
    vec4 eyePos = modelview * position;            //transform to eye-space
    eyePos.xy += spriteSize * (corner - vec2(0.5)); //add corner position
    gl_Position = projection * eyePos;             //complete transformation
    texCoord = corner;
}

Cela donne les mêmes résultats que l'approche basée sur un ombrage géométrique, des sprites ponctuels correctement coupés avec une taille cohérente de l'espace mondial. Si vous voulez réellement imiter la taille en pixels de l'espace d'écran des sprites ponctuels réels, vous devez y consacrer plus d'efforts de calcul. Mais cela est laissé comme un exercice et serait tout à fait l'opposé de la transformation du monde à l'écran à partir du shader point Sprite.

95
Christian Rau