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:
Cependant, dès que vous introduisez une mise à l'échelle fractionnaire, des coutures apparaissent:
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_REPEAT
devrait 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_CLAMP
lieu deGL_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_REPEAT
est 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.
GL_NEAREST
échantillonnage dans laR
direction 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.Réponses:
Les coutures sont un comportement correct pour l'échantillonnage avec GL_REPEAT. Considérez la tuile suivante:
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:
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_CLAMP
pour éviter les saignements du pixel du côté opposé de la texture. Ensuite, vous avez trois options:Activez l'anticrénelage pour faciliter un peu la transition entre les tuiles.
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.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é).
GL_LINEAR
filtrage semblable à celui des polygones / sprites.la source
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:
la source
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.
la source
2 possibilités qui méritent d'être essayées:
Utilisez
GL_NEAREST
au lieu deGL_LINEAR
pour 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_NEAREST
trouve juste le pixel le plus proche et utilise cette couleur. Pas d'interpolation. En conséquence, c'est en fait un peu plus rapide.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--
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.
la source
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.
la source
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.
la source