OpenGL GLSL - Filtre de détection de bords Sobel

9

En ce qui concerne ce sujet, j'ai réussi à implémenter le filtre de détection des bords Sobel dans GLSL. Voici le fragment shader code du filtre:

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D screenTexture;

mat3 sx = mat3( 
    1.0, 2.0, 1.0, 
    0.0, 0.0, 0.0, 
   -1.0, -2.0, -1.0 
);
mat3 sy = mat3( 
    1.0, 0.0, -1.0, 
    2.0, 0.0, -2.0, 
    1.0, 0.0, -1.0 
);

void main()
{
    vec3 diffuse = texture(screenTexture, TexCoords.st).rgb;
    mat3 I;
    for (int i=0; i<3; i++) {
        for (int j=0; j<3; j++) {
            vec3 sample  = texelFetch(screenTexture, ivec2(gl_FragCoord) + ivec2(i-1,j-1), 0 ).rgb;
            I[i][j] = length(sample); 
    }
}

float gx = dot(sx[0], I[0]) + dot(sx[1], I[1]) + dot(sx[2], I[2]); 
float gy = dot(sy[0], I[0]) + dot(sy[1], I[1]) + dot(sy[2], I[2]);

float g = sqrt(pow(gx, 2.0)+pow(gy, 2.0));
color = vec4(diffuse - vec3(g), 1.0);
} 

Et voici le résultat d'un cube avec détection de bords Sobel:

Cube avec filtre de détection de bord Sobel

Si vous agrandissez l'image, vous verrez qu'il y a beaucoup de "bruit" produit par Sobel: Il y a des rayures horizontales grises sur toute la scène en raison du dégradé bleu / blanc. De plus, les cônes lumineux produisent un motif indésirable sur le cube. Les bords noirs à gauche du cube semblent également s'estomper à cause du cône de lumière sur la moitié gauche du cube.

J'ai donc lu cet article qui disait qu'il fallait d'abord mettre l'image en niveaux de gris et utiliser un filtre de flou gaussien pour rendre les bords plus apparents. Au bas de l'article, il y a aussi le filtre de détection de bord rusé qui semble produire de meilleurs résultats.

Maintenant, j'ai deux questions:

  1. Les étapes suivantes sont-elles correctes pour produire les meilleurs résultats de détection de bord possibles:

    • Niveaux de gris
    • Flou gaussien
    • Détection des bords Sobel / Canny
  2. Si oui, comment fusionnerais-je l'image originale avec l'image traitée? Je veux dire, après avoir traité les étapes décrites ci-dessus, j'obtiens une image qui est soit complètement noire avec des bords blancs, soit vice-versa. Comment placer les bords sur mon image / texture d'origine?

Merci de votre aide!

enne87
la source
Je me demande si vous pourriez obtenir les résultats souhaités en utilisant l' indicateur de bord d'OpenGL d' une manière ou d'une autre. Il semble que seules les arêtes entre les sommets avec le drapeau d'arête activé soient dessinées en mode ligne. Il y a une description ici . (Je ne l'ai pas utilisé moi-même ou je publierais un exemple.)
user1118321

Réponses:

9
  1. Les meilleurs résultats dépendent fortement de votre cas d'utilisation. Ils dépendent également de l'effet que vous souhaitez obtenir. Sobel n'est qu'un filtre de détection de bord: les bords dépendront du signal d'entrée, en choisissant le signal d'entrée qui vous appartient.

    Ici, vous utilisez l'image couleur comme entrée, et le filtre détecte à juste titre les bords faibles dans le dégradé bleu, tandis que les bords du cube sont interrompus là où sa couleur est trop proche de la couleur d'arrière-plan.

    Comme je suppose que votre programme est également responsable du dessin du cube, vous avez accès à d'autres informations que vous pouvez alimenter dans votre filtre Sobel. Par exemple, la profondeur et les normales sont de bons candidats pour la détection des bords. L'albédo avant l'éclairage peut être utilisé. Testez avec différentes entrées et décidez lesquelles utiliser en fonction des résultats que vous obtenez.

  2. En ce qui concerne votre question sur la façon de combiner les informations de bord, je vous suggère de filtrer gun peu avant de les utiliser. Ensuite, vous pouvez l'utiliser pour interpoler entre la couleur d'origine et la couleur de bord souhaitée.

    Par exemple, vous pouvez essayer quelque chose comme ceci:

    float g = sqrt(pow(gx, 2.0)+pow(gy, 2.0));

    // Try different values and see what happens
    g = smoothstep(0.4, 0.6, g);

    vec3 edgeColor = vec3(1., 0., 0.2);
    color = vec4(mix(diffuse, edgeColor, g), 1.);

Addenda

Pour utiliser la profondeur ou les normales, vous devez les enregistrer dans une texture si ce n'est pas déjà fait. Lorsque vous créez le tampon d'image pour votre passage de rendu normal, vous pouvez y attacher diverses textures (voir glFramebufferTexture2D) et y écrire d'autres informations que la couleur de la scène.

Si vous attachez une texture de profondeur (avec GL_DEPTH_ATTACHMENT), elle sera utilisée automatiquement pour la profondeur. Si vous attachez une ou plusieurs textures de couleur (avec GL_COLOR_ATTACHMENTi), vous pouvez y écrire en déclarant plusieurs sorties à vos shaders de fragment (cela se faisait auparavant avec gl_FragData; de toute façon, voir glDrawBuffers).

Pour plus d'informations sur le sujet, recherchez «Multi Render Target» (MRT).

Julien Guertault
la source
Belle information, merci Julien. Une autre question: comment utiliser la profondeur ou les informations normales dans mon fragment shader? Je suis encore assez nouveau avec GLSL et je n'ai donc aucune idée de comment inclure les valeurs dans l'algorithme Sobel.
enne87
2
Lorsque vous créez le tampon d'image pour votre passage de rendu, vous pouvez y attacher plusieurs textures. Si vous attachez une texture de profondeur, elle sera utilisée automatiquement pour la profondeur. Si vous attachez une autre texture de couleur, vous pouvez y écrire séparément (voir opengl.org/sdk/docs/man/html/glDrawBuffers.xhtml ), une fonction appelée "Multi Render Target" (MRT).
Julien Guertault
@ enne87: Heureux de vous aider. :)
Julien Guertault
@trichoplax: merci pour la suggestion; terminé.
Julien Guertault