4
votes

GLSL Geometry shader pour remplacer glLineWidth

J'essaye d'écrire un shader de géométrie pour remplacer le comportement de glLineWidth . Je veux dessiner des lignes avec une largeur personnalisable (le faire avec un uniforme suffit pour l'instant). Les lignes doivent toujours avoir la même épaisseur, quelle que soit la projection de la caméra ou la distance par rapport à l'emplacement des lignes.

En me basant sur beaucoup de recherches sur Google, j'ai trouvé le shader de géométrie suivant:

#version 330

uniform vec4 u_color = vec4(1, 0, 1, 1);
out vec4 fragColor;

void main() {
    fragColor = u_color;
}

Pour être complet, voici le vertex shader:

#version 330

in vec4 a_position;

void main() {
    gl_Position = a_position;
}

... et mon fragment shader:

#version 330

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

uniform mat4    u_model_matrix;
uniform mat4    u_view_matrix;
uniform mat4    u_projection_matrix;
uniform float   u_thickness = 4; // just a test default

void main()
{
    float r = u_thickness / 2;

    mat4 mv = u_view_matrix * u_model_matrix;
    vec4 p1 = mv * gl_in[0].gl_Position;
    vec4 p2 = mv * gl_in[1].gl_Position;

    vec2 dir = normalize(p2.xy - p1.xy);
    vec2 normal = vec2(dir.y, -dir.x);

    vec4 offset1, offset2;
    offset1 = vec4(normal * r, 0, 0);
    offset2 = vec4(normal * r, 0, 0);

    vec4 coords[4];
    coords[0] = p1 + offset1;
    coords[1] = p1 - offset1;
    coords[2] = p2 + offset2;
    coords[3] = p2 - offset2;

    for (int i = 0; i < 4; ++i) {
        coords[i] = u_projection_matrix * coords[i];
        gl_Position = coords[i];
        EmitVertex();
    }
    EndPrimitive();
}


0 commentaires

3 Réponses :


1
votes

Je ne suis pas un expert, mais ayant déjà fait cela, je vais vous donner mon avis.

Je suppose que vos gl_Position sont directement issus du vertex shader qui a été calculé à l'aide une matrice de projection. Cela signifie que leur composant w est la "position de l'espace de clip" du point; c'est ce qui est utilisé par le pipeline pour donner son effet à la projection (plus loin, les choses sont plus petites). Il faut donc en tenir compte.

Heureusement, la seule chose que vous devez faire est de multiplier votre offset avec.

coords[0] = p1 + offset1 * p1.w;
coords[1] = p1 - offset1 * p1.w;
coords[2] = p2 + offset2 * p2.w;
coords[3] = p2 - offset2 * p2.w;

Cela devrait donner vous l'effet que vous voulez.


4 commentaires

J'ai déjà essayé ça, ça ne fait aucune différence. Notez que je fais le travail dans l'espace caméra / vue (je multiplie d'abord avec la matrice modèle / vue, je fais le calcul, puis je multiplie par la matrice de projection). Le faire dans l'espace de l'écran a donné des résultats encore pires pour moi.


Je vois, ça m'a manqué. Cependant, je pense que vous devrez le faire dans l'espace de l'écran car l'effet souhaité est relatif à l'écran, non? Quoi qu'il en soit, vous devrez concevoir une méthode pour agrandir les autres décalages afin qu'ils apparaissent de la même taille. Que ce soit en utilisant les positions projetées ou la coordonnée z de l'espace de vue dépend de vous, je suppose.


Merci pour cet indice! Je réalise maintenant que je dois réduire mon rayon pour qu'il ne soit pas en pixels, mais par rapport à la taille de la fenêtre. Le calcul manuel et le piratage temporaire du shader ont en fait fait beaucoup mieux fonctionner! (Et en effet toujours aussi multiplier par w!)


Heureux d'avoir pu vous aider!



2
votes

J'ai réussi à résoudre ce problème en tenant compte de la taille de la fenêtre et en mettant à l'échelle mon r en utilisant cela. Je ne sais pas si c'est le moyen le plus efficace de résoudre ce problème (je ne suis en aucun cas un mathématicien), mais cela fonctionne.

Dans le code ci-dessous, je fais maintenant tout le travail dans l'espace écran plutôt que caméra / espace de vue, et j'utilise u_viewportInvSize vec2 (qui vaut 1 / viewportSize) pour mettre à l'échelle le rayon souhaité!

#version 330

layout (lines) in;                              // now we can access 2 vertices
layout (triangle_strip, max_vertices = 4) out;  // always (for now) producing 2 triangles (so 4 vertices)

uniform vec2    u_viewportInvSize;
uniform mat4    u_modelviewprojection_matrix;
uniform float   u_thickness = 4;

void main()
{
    float r = u_thickness;

    vec4 p1 = u_modelviewprojection_matrix * gl_in[0].gl_Position;
    vec4 p2 = u_modelviewprojection_matrix * gl_in[1].gl_Position;

    vec2 dir = normalize(p2.xy - p1.xy);
    vec2 normal = vec2(dir.y, -dir.x);

    vec4 offset1, offset2;
    offset1 = vec4(normal * u_viewportInvSize * (r * p1.w), 0, 0);
    offset2 = vec4(normal * u_viewportInvSize * (r * p2.w), 0, 0); // changing this to p2 fixes some of the issues

    vec4 coords[4];
    coords[0] = p1 + offset1;
    coords[1] = p1 - offset1;
    coords[2] = p2 + offset2;
    coords[3] = p2 - offset2;

    for (int i = 0; i < 4; ++i) {
        gl_Position = coords[i];
        EmitVertex();
    }
    EndPrimitive();
}


0 commentaires

0
votes

Le shader Geometry n'est pas connu pour être rapide. l'utilisation d'un shader de géométrie est un tueur de performances, il ne devrait donc être suggéré que si toutes les autres options sont hors de la table. Une solution possible sans utiliser un shader de géométrie est présentée dans la réponse à Largeur de ligne OpenGL .


Quoi qu'il en soit, si vous souhaitez utiliser un shader de géométrie, alors transformez la coordonnée de sommet par la matrice de projection de la vue du modèle x dans le shader de vertex:

#version 330

out vec4 fragColor;
uniform vec4 u_color = vec4(1, 0, 1, 1);

void main()
{
    fragColor = u_color;
}

Calculez la normalisée coordonnée de l'appareil dans le geometry shader, par Division de perspective : p >

#version 330

layout (lines) in;                              // now we can access 2 vertices
layout (triangle_strip, max_vertices = 4) out;  // always (for now) producing 2 triangles (so 4 vertices)

uniform vec2  u_viewportSize;
uniform float u_thickness = 4;

void main()
{
    vec4 p1 = gl_in[0].gl_Position;
    vec4 p2 = gl_in[1].gl_Position;

    vec2 dir    = normalize((p2.xy - p1.xy) * u_viewportSize);
    vec2 offset = vec2(-dir.y, dir.x) * u_thickness / u_viewportSize;

    gl_Position = p1 + vec4(offset.xy * p1.w, 0.0, 0.0);
    EmitVertex();
    gl_Position = p1 - vec4(offset.xy * p1.w, 0.0, 0.0);
    EmitVertex();
    gl_Position = p2 + vec4(offset.xy * p2.w, 0.0, 0.0);
    EmitVertex();
    gl_Position = p2 - vec4(offset.xy * p2.w, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

L'espace normalisé de l'appareil est un cube avec la gauche, le bas, près de (-1, -1, -1) et la droite, le haut, l'arrière de (1, 1, 1 ).

Calcule le vecteur entre les points des lignes. Mettez à l'échelle en fonction de la taille de la fenêtre pour prendre en compte les proportions de la fenêtre. Enfin, récupérez le vecteur d'unité sur la ligne:

gl_Position = vec4((ndc_1 + offset) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex(); 
gl_Position = vec4((ndc_1 - offset) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();
gl_Position = vec4((ndc_2 + offset) * gl_in[1].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();
gl_Position = vec4((ndc_2 - offset) * gl_in[1].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();


0 commentaires