Rendu transparent de la carte en mosaïque (images adjacentes sans bordure)

18

J'ai un moteur de jeu 2D qui dessine des tilemaps en dessinant des tuiles à partir d'une image de jeu de tuiles. Parce que par défaut, OpenGL ne peut envelopper que la texture entière ( GL_REPEAT), et pas seulement une partie, chaque mosaïque est divisée en une texture distincte. Ensuite, les régions de la même tuile sont rendues adjacentes les unes aux autres. Voici à quoi cela ressemble quand il fonctionne comme prévu:

Tilemap sans coutures

Cependant, dès que vous introduisez une mise à l'échelle fractionnaire, des coutures apparaissent:

Tilmap avec coutures

Pourquoi cela arrive-t-il? Je pensais que c'était dû au filtrage linéaire mélangeant les bordures des quads, mais cela se produit toujours avec le filtrage ponctuel. La seule solution que j'ai trouvée jusqu'à présent est de garantir que tout le positionnement et la mise à l'échelle se produisent uniquement aux valeurs entières, et d' utiliser le filtrage de points. Cela peut dégrader la qualité visuelle du jeu (en particulier, le positionnement sous-pixel ne fonctionne plus, donc le mouvement n'est pas si fluide).

Choses que j'ai essayées / considérées:

  • l'anticrénelage réduit, mais n'élimine pas entièrement, les coutures
  • désactiver le mipmapping, n'a aucun effet
  • rendre chaque tuile individuellement et extruder les bords par 1px - mais c'est une désoptimisation, car elle ne peut plus rendre les régions de tuiles en une seule fois, et crée d'autres artefacts le long des bords des zones de transparence
  • ajouter une bordure de 1px autour des images source et répéter les derniers pixels - mais ils ne sont plus à la puissance de deux, ce qui provoque des problèmes de compatibilité avec les systèmes sans prise en charge NPOT
  • écrire un shader personnalisé pour gérer les images en mosaïque - mais que feriez-vous différemment? GL_REPEATdevrait saisir le pixel du côté opposé de l'image aux bords, et ne pas choisir la transparence.
  • la géométrie est exactement adjacente, il n'y a pas d'erreurs d'arrondi en virgule flottante.
  • Si le fragment shader est codé en dur pour retourner la même couleur, les coutures disparaissent .
  • si les textures sont définies sur au GL_CLAMPlieu de GL_REPEAT, les coutures disparaissent (bien que le rendu soit incorrect).
  • si les textures sont définies sur GL_MIRRORED_REPEAT, les coutures disparaissent (bien que le rendu soit à nouveau erroné).
  • si je fais le fond rouge, les coutures sont toujours blanches. Cela suggère qu'il échantillonne du blanc opaque de quelque part plutôt que de la transparence.

Ainsi, les coutures n'apparaissent que lorsque GL_REPEATest défini. Pour une raison quelconque dans ce mode uniquement, aux bords de la géométrie, il y a un fond perdu / une fuite / une transparence. Comment est-ce possible? La texture entière est opaque.

AshleysBrain
la source
Vous pouvez essayer de régler votre échantillonneur de texture pour qu'il pince ou d'ajuster la couleur de la bordure, car je soupçonne que cela peut être lié à cela. De plus, GL_Repeat encapsule simplement les coordonnées UV, donc je suis personnellement un peu confus lorsque vous dites qu'il ne peut répéter que la texture entière, plutôt qu'une partie de celle-ci.
Evan
1
Est-il possible que vous introduisiez des fissures dans votre maillage d'une manière ou d'une autre? Vous pouvez le tester en définissant le shader pour qu'il renvoie simplement une couleur unie plutôt que d'échantillonner une texture. Si vous voyez encore des fissures à ce stade, elles sont dans la géométrie. Le fait que l'anticrénelage réduit les coutures suggère que cela pourrait être le problème, car l'anticrénelage ne devrait pas affecter l'échantillonnage de texture.
Nathan Reed
Vous pouvez implémenter la répétition de carreaux individuels dans un atlas de texture. Vous devez cependant faire des calculs de coordonnées de texture supplémentaires et insérer des texels de bordure autour de chaque tuile. Cet article explique beaucoup de cela (bien que principalement en se concentrant sur la convention de coordonnées de texture agaçante de D3D9). Alternativement, si votre implémentation est suffisamment nouvelle, vous pouvez utiliser des textures de tableau pour votre carte de tuiles; en supposant que chaque tuile a les mêmes dimensions, cela fonctionnera très bien et ne nécessitera aucun calcul de coordonnées supplémentaire.
Andon M. Coleman
Les textures 3D avec GL_NEARESTéchantillonnage dans la Rdirection des coordonnées fonctionnent aussi bien que les textures de tableau pour la plupart des choses dans ce scénario. Le mipmapping ne fonctionnera pas, mais à en juger par votre application, vous n'avez probablement pas besoin de mipmaps de toute façon.
Andon M. Coleman
1
De quelle manière le rendu est-il "incorrect" lorsqu'il est réglé sur CLAMP?
Martijn Courteaux

Réponses:

1

Les coutures sont un comportement correct pour l'échantillonnage avec GL_REPEAT. Considérez la tuile suivante:

carreau de bordure

L'échantillonnage sur les bords de la tuile à l'aide de valeurs fractionnaires mélange les couleurs du bord et du bord opposé, ce qui entraîne des couleurs incorrectes: le bord gauche doit être vert, mais est un mélange de vert et de beige. Le bord droit doit être beige, mais est également de couleur mélangée. Surtout les lignes beiges sur le fond vert sont très visibles, mais si vous regardez de près, vous pouvez voir le vert saigner dans le bord:

tuile à l'échelle

l'anticrénelage réduit, mais n'élimine pas entièrement, les coutures

MSAA fonctionne en prenant plus d'échantillons autour des bords des polygones. Les échantillons prélevés à gauche ou à droite du bord seront "verts" (là encore, si l'on considère le bord gauche de la première image), et un seul échantillon sera "sur" le bord, échantillonnage partiel de la zone "beige". Lorsque les échantillons sont mélangés, l'effet est réduit.

Solutions possibles:

Dans tous les cas, vous devez basculer sur GL_CLAMPpour éviter les saignements du pixel du côté opposé de la texture. Ensuite, vous avez trois options:

  1. Activez l'anticrénelage pour faciliter un peu la transition entre les tuiles.

  2. Définissez le filtrage de texture sur GL_NEAREST. Cela donne à tous les pixels des bords durs, donc les bords de polygone / sprite deviennent indiscernables, mais cela change évidemment un peu le style du jeu.

  3. Ajoutez la bordure 1px déjà discutée, assurez-vous simplement que la bordure a la couleur de la tuile adjacente (et non la couleur du bord opposé).

    • Cela pourrait également être un bon moment pour passer à un atlas de texture (agrandissez-le si vous êtes préoccupé par le support NPOT).
    • Il s'agit de la seule solution "parfaite", permettant un GL_LINEARfiltrage semblable à celui des polygones / sprites.
Jens Nolte
la source
OOh, merci - l'enroulement autour de la texture l'explique. Je vais chercher ces solutions!
AshleysBrain
6

Parce que par défaut, OpenGL ne peut envelopper que la texture entière (GL_REPEAT), et pas seulement une partie de celle-ci, chaque mosaïque est divisée en une texture distincte. Ensuite, les régions de la même tuile sont rendues adjacentes les unes aux autres.

Considérez l'affichage d'un seul quadruple texturé ordinaire dans OpenGL. Y a-t-il des coutures, à n'importe quelle échelle? Non jamais. Votre objectif est de regrouper toutes vos tuiles de scène sur une seule texture, puis de les envoyer à l'écran. EDIT Pour clarifier cela: Si vous avez des sommets de délimitation discrets sur chaque quadrilatère, vous aurez des joints apparemment injustifiés dans la plupart des circonstances. C'est ainsi que fonctionne le matériel GPU ... les erreurs en virgule flottante créent ces écarts en fonction de la perspective actuelle ... les erreurs qui sont évitées sur un collecteur unifié, car si deux faces sont adjacentes dans le même collecteur(submesh), le matériel les rendra sans coutures, c'est garanti. Vous l'avez vu dans d'innombrables jeux et applications. Vous devez avoir une texture compacte sur un seul sous-maillage sans double sommet pour éviter cela une fois pour toutes. C'est un problème qui revient à maintes reprises sur ce site et ailleurs: si vous ne fusionnez pas les sommets d'angle de vos tuiles (toutes les 4 tuiles partagent un sommet d'angle), attendez-vous à des coutures.

(Considérez que vous n'aurez peut-être même pas besoin de sommets, sauf aux quatre coins de la carte entière ... cela dépend de votre approche de l'ombrage.)

Pour résoudre: restituez (à 1: 1) toutes vos textures de tuile dans un FBO / RBO sans lacunes, puis envoyez ce FBO au framebuffer par défaut (l'écran). Parce que le FBO lui-même est essentiellement une texture unique, vous ne pouvez pas vous retrouver avec des lacunes sur la mise à l'échelle. Toutes les limites de texels qui ne tombent pas sur une limite de pixels d'écran seront mélangées si vous utilisez GL_LINEAR .... c'est exactement ce que vous voulez. C'est l'approche standard.

Cela ouvre également un certain nombre de voies différentes pour la mise à l'échelle:

  • mise à l'échelle de la taille du quadruple auquel vous rendrez le FBO
  • changer les UV sur ce quad
  • jouer avec les paramètres de l'appareil photo
Ingénieur
la source
Je ne pense pas que cela fonctionnera bien pour nous. Il semble que vous proposiez qu'avec un zoom de 10%, nous rendions une zone 10 fois plus grande. Cela n'augure rien de bon pour les appareils avec un taux de remplissage limité. De plus, je ne comprends toujours pas pourquoi spécifiquement GL_REPEAT ne cause que des coutures, et aucun autre mode ne le fait. Comment cela pourrait-il être?
AshleysBrain
@AshleysBrain Non sequitur. Je suis mystifié par votre déclaration. Veuillez expliquer comment l'augmentation du zoom de 10% augmente le taux de remplissage de 10x? Ou comment augmenter le zoom de 10x - puisque l'écrêtage de la fenêtre empêcherait un taux de remplissage de plus de 100% en une seule passe? Je vous suggère d'afficher exactement ce que vous avez déjà l'intention de faire - ni plus, ni moins - de manière probablement plus efficace ET transparente, en unifiant votre sortie finale en utilisant RTT, une technique courante. Le matériel gérera l'écrêtage, la mise à l'échelle, le mélange et l'interpolation des fenêtres de visualisation pratiquement sans frais, étant donné qu'il ne s'agit que d'un moteur 2D.
Ingénieur
@AshleysBrain J'ai modifié ma question pour clarifier les problèmes avec votre approche actuelle.
Ingénieur
@ArcaneEngineer Bonjour, j'ai essayé votre approche qui résout parfaitement le problème des coutures. Cependant, maintenant, au lieu d'avoir des coutures, j'ai des erreurs de pixels. Vous pouvez avoir une idée du problème auquel je fais référence ici . Avez-vous une idée de ce qui pourrait en être la cause? Notez que je fais tout dans WebGL.
Nemikolh
1
@ArcaneEngineer Je vais poser une nouvelle question, expliquer ici est trop lourd. Je suis désolé pour l'ennui.
Nemikolh
1

Si les images doivent apparaître comme une seule surface, rendez-les comme une texture de surface (échelle 1x) sur un maillage / plan 3D. Si vous effectuez le rendu de chacun en tant qu'objet 3D distinct, il comportera toujours des coutures en raison d'erreurs d'arrondi.

La réponse de @ Nick-Wiggill est correcte, je pense que vous l'avez mal comprise.

Barry Staes
la source
0

2 possibilités qui méritent d'être essayées:

  1. Utilisez GL_NEARESTau lieu de GL_LINEARpour filtrer votre texture. (Comme l'a souligné Andon M. Coleman)

    GL_LINEAR(en effet) rendra l'image très légèrement floue (interpolation entre les pixels les plus proches), ce qui permet une transition en douceur de la couleur d'un pixel au suivant. Cela peut aider à améliorer l'apparence des textures dans de nombreux cas, car cela empêche vos textures d'avoir ces pixels en bloc partout. Il fait également en sorte qu'un peu de brun d'un pixel d'une tuile sur puisse devenir flou sur le pixel vert adjacent de la tuile adjacente.

    GL_NEARESTtrouve juste le pixel le plus proche et utilise cette couleur. Pas d'interpolation. En conséquence, c'est en fait un peu plus rapide.

  2. Rétrécissez un peu les coordonnées de texture de chaque carreau.

    Un peu comme ajouter ce pixel supplémentaire autour de chaque tuile en tant que tampon, sauf qu'au lieu d'agrandir la tuile sur l'image, vous réduisez la tuile dans le logiciel. Tuiles 14x14px lors du rendu au lieu de tuiles 16x16px.

    Vous pourriez même être en mesure de vous en sortir avec des carreaux 14,5 x 14,5 sans que trop de brun soit mélangé.

--Éditer--

Visualisation de l'explication de l'option # 2

Comme le montre l'image ci-dessus, vous pouvez toujours utiliser facilement une puissance de deux textures. Vous pouvez donc prendre en charge du matériel qui ne prend pas en charge les textures NPOT. Ce qui change, ce sont vos coordonnées de texture, au lieu d'aller de (0.0f, 0.0f)à (1.0f, 1.0f)Vous iriez de (0.03125f, 0.03125f)à (0.96875f, 0.96875f).

Cela met vos tex-coords légèrement à l'intérieur de la tuile, ce qui réduit la résolution effective dans le jeu (mais pas sur le matériel, de sorte que vous avez toujours deux textures), mais devrait avoir le même effet de rendu que l'extension de la texture.

Wolfgang Skyler
la source
J'ai déjà mentionné que j'avais essayé GL_NEAREST (aka filtrage ponctuel), et cela ne le corrige pas. Je ne peux pas faire # 2 car je dois prendre en charge les appareils NPOT.
AshleysBrain
A fait un montage qui pourrait clarifier certaines choses. (Je suppose que "le besoin de prendre en charge les appareils NPOT [(non-power-of-two)]]" était censé signifier quelque chose comme vous devez garder les textures à une puissance de 2?)
Wolfgang Skyler
0

Voici ce que je pense que cela pourrait se produire: Lorsque deux tuiles entrent en collision, la composante x de leurs bords doit être égale. Si ce n'est pas vrai, ils pourraient être arrondis dans la direction opposée. Assurez-vous donc qu'ils ont exactement la même valeur x. Comment vous assurez-vous que cela se produit? Vous pourriez essayer de, disons, multiplier par 10, arrondir puis diviser par 10 (ne le faites que sur le CPU, avant que vos sommets ne pénètrent dans le vertex shader). Cela devrait donner des résultats corrects. De plus, ne dessinez pas mosaïque par mosaïque à l'aide de matrices de transformation, mais placez-les dans un lot VBO pour vous assurer que les mauvais résultats ne proviennent pas du système à virgule flottante IEEE avec perte en combinaison avec des multiplications matricielles.

Pourquoi est-ce que je suggère cela? Parce que d'après mon expérience, si les sommets ont exactement les mêmes coordonnées lorsqu'ils sortent du vertex shader, le remplissage des triangles correspondants créera des résultats homogènes. Gardez à l'esprit que quelque chose qui est mathématiquement correct peut être un peu décalé en raison de l'IEEE. Plus vous effectuez de calculs sur vos chiffres, moins le résultat sera précis. Et oui, les multiplications matricielles nécessitent un certain nombre d'opérations, qui peuvent être effectuées uniquement en multiplication et en ajout lors de la création de votre VBO, ce qui donnera des résultats plus précis.

Ce qui pourrait également être le problème, c'est que vous utilisez une feuille de calcul (également appelée atlas) et que lors de l'échantillonnage de la texture, les pixels de la texture de tuile adjacente sont sélectionnés. Pour éviter que cela ne se produise, vous devez créer une petite bordure dans les mappages UV. Donc, si vous avez une tuile 64x64, votre mapping UV devrait couvrir un peu moins. Combien? Je pense que j'ai utilisé dans mes jeux 1 quart de pixel de chaque côté du rectangle. Compensez donc vos UV de 1 / (4 * widthOfTheAtlas) pour les composants x et 1 / (4 * heightOfTheAtlas) pour les composants y.

Martijn Courteaux
la source
Merci, mais comme je l'ai déjà noté, la géométrie est certainement exactement adjacente - en fait, toutes les coordonnées donnent des nombres entiers exacts, donc c'est facile à dire. Aucun atlas n'est utilisé ici.
AshleysBrain
0

Pour résoudre ce problème dans l'espace 3D, j'ai mélangé des tableaux de textures avec la suggestion de Wolfgang Skyler. Je mets une bordure de 8 pixels autour de ma texture réelle pour chaque tuile (120x120, 128x128 au total) que, pour chaque côté, que je pourrais remplir avec une image "enveloppée" ou le côté qui vient d'être étendu. L'échantillonneur lit cette zone lorsqu'il interpole l'image.

Désormais, grâce au filtrage et au mipmapping, l'échantillonneur peut toujours lire facilement au-delà de la bordure de 8 pixels. Pour attraper ce petit problème (je dis petit car il ne se produit que sur quelques pixels lorsque la géométrie est vraiment asymétrique ou éloignée), je divise les tuiles en un tableau de texture, de sorte que chaque tuile a son propre espace de texture et peut utiliser le serrage sur le bords.

Dans votre cas (2D / plat), j'irais certainement avec le rendu d'une scène parfaite en pixels, puis la mise à l'échelle de la partie souhaitée du résultat dans la fenêtre, comme suggéré par Nick Wiggil.

mukunda
la source