Comment déplacez-vous un sprite par incréments de sous-pixels?


11

Les pixels sont activés ou désactivés. La quantité minimale que vous pouvez déplacer un sprite est d'un seul pixel. Alors, comment faire pour que le sprite se déplace plus lentement que 1 pixel par image?

La façon dont je l'ai fait était d'ajouter la vitesse à une variable et de tester si elle avait atteint 1 (ou -1). Si c'était le cas, je déplacerais le sprite et redéfinirais la variable à 0, comme ceci:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

J'ai détesté cette approche car elle semble idiote et le mouvement du sprite semble très saccadé. Alors, comment implémenteriez-vous le mouvement sous-pixel?


La seule chose que vous puissiez faire est le filtrage bilinéaire.
AturSams

Si vous déplacez moins de 1 px par image, comment peut-il paraître saccadé?

Réponses:


17

Il y a plusieurs options:

  1. Faites comme vous. Vous avez déjà dit que cela n'avait pas l'air fluide. Il y a cependant quelques défauts avec votre méthode actuelle. Pour x, vous pouvez utiliser les éléments suivants:

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1
    

    cela devrait être mieux. J'ai changé les instructions if pour utiliser 0,5, car lorsque vous passez 0,5, vous êtes plus proche de la valeur suivante que de la précédente. J'ai utilisé des boucles while pour permettre le mouvement de plus de 1 pixel par pas de temps (ce n'est pas la meilleure façon de le faire, mais cela rend le code compact et lisible). Pour les objets se déplaçant lentement, cela restera cependant nerveux, car nous n'avons pas traité le problème fondamental de l'alignement des pixels.

  2. Ayez plusieurs graphiques pour votre sprite et utilisez-en un différent en fonction du décalage de sous-pixel. Pour effectuer un lissage de sous-pixels uniquement en x, par exemple, vous pouvez créer un graphique pour votre image-objet à x+0.5, et si la position est entre x+0.25et x+0.75, utilisez cette image-objet au lieu de votre original. Si vous voulez un positionnement plus fin, créez simplement plus de graphiques en sous-pixels. Si vous faites cela dans xet que yvotre nombre de rendus peut rapidement monter en flèche, car le nombre évolue avec le carré du nombre d'intervalles: un espacement de 0.5nécessiterait 4 rendus, 0.25nécessiterait 16.

  3. Suréchantillon. Il s'agit d'une façon paresseuse (et potentiellement très coûteuse) de créer une image avec une résolution sous-pixel. Essentiellement, doublez (ou plus) la résolution à laquelle vous effectuez le rendu de votre scène, puis réduisez-la au moment de l'exécution. Je recommanderais des soins ici, car les performances peuvent chuter rapidement. Une façon moins agressive de faire cela serait simplement de suréchantillonner votre sprite et de le réduire à l'exécution.

  4. Comme suggéré par Zehelvion , il se peut que la plate-forme que vous utilisez le prenne déjà en charge. Si vous êtes autorisé à spécifier des coordonnées x et y non entières, il peut y avoir des options pour modifier le filtrage de texture. Le plus proche voisin est souvent la valeur par défaut, ce qui entraînera un mouvement "saccadé". Un autre filtrage (linéaire / cubique) entraînerait un effet beaucoup plus fluide.

Le choix entre 2. 3. et 4. dépend de la façon dont vos graphiques sont implémentés. Un style graphique bitmap convient beaucoup mieux au pré-rendu, tandis qu'un style vectoriel peut convenir au suréchantillonnage de sprites. Si votre système le prend en charge, 4. pourrait bien être le chemin à parcourir.


Pourquoi remplacer le seuil par 0,5 en opt 1? Je ne comprends pas non plus pourquoi vous avez déplacé les instructions dans une boucle while. Les fonctions de mise à jour sont appelées une fois par tick.
bzzr

1
Bonne question - j'ai clarifié la réponse sur la base de votre commentaire.
Jez

Le suréchantillonnage peut être "paresseux", mais dans de nombreux cas, le coût de performance d'un suréchantillonnage 2x ou même 4x ne serait pas un problème particulier, surtout s'il permettait de simplifier d'autres aspects du rendu.
supercat

Dans les jeux à gros budget, je vois généralement 3 comme une option répertoriée dans les paramètres graphiques comme anti-aliasing.
Kevin

1
@Kevin: Vous n'en avez probablement pas. La plupart des jeux utilisent le multi - échantillonnage , ce qui est quelque peu différent. D'autres variantes sont également couramment utilisées, avec par exemple une couverture variable ainsi que des échantillons de profondeur. Le suréchantillonnage n'est presque jamais effectué en raison du coût d'exécution du fragment shader.
imallett

14

Une façon dont de nombreux jeux old-skool ont résolu (ou caché) ce problème était d'animer le sprite.

Autrement dit, si votre image-objet devait se déplacer de moins d'un pixel par image (ou, surtout, si le rapport pixels / image devait être quelque chose d'étrange comme 2 pixels en 3 images), vous pourriez masquer la secousse en faisant un n boucle d'animation d'image qui, sur ces n images, a fini par déplacer le sprite de quelques k < n pixels.

Le fait est que, tant que l'image-objet se déplace toujours d' une manière ou d' une autre sur chaque image, il n'y aura jamais une seule image où l'image-image entière "se branlerait" soudainement en avant.


Je n'ai pas pu trouver un véritable sprite d'un ancien jeu vidéo pour illustrer cela (bien que je pense par exemple que certaines des animations de fouille de Lemmings étaient comme ça), mais il s'avère que le motif "planeur" du jeu de la vie de Conway fait un très belle illustration:

entrez la description de l'image ici
Animation par Kieff / Wikimedia Commons , utilisée sous la licence CC-By-SA 3.0 .

Ici, les petits blocs de pixels noirs rampant rampant vers le bas et à droite sont les planeurs. Si vous regardez attentivement, vous remarquerez qu'il faut quatre images d'animation pour parcourir un pixel en diagonale, mais comme elles se déplacent d' une manière ou d' une autre sur chacune de ces images, le mouvement n'a pas l'air si saccadé (enfin, du moins plus) saccadé que quoi que ce soit regarde cette fréquence d'images, de toute façon).


2
C'est une excellente approche si la vélocité est fixe, ou si elle est inférieure à 1/2 pixel par image, ou si l'animation est suffisamment "grande" et assez rapide pour masquer les variations de mouvement.
supercat

C'est une bonne approche, bien que si une caméra doit suivre le sprite, vous rencontrerez toujours des problèmes car elle ne se déplacera pas assez facilement.
Elden Abob

6

La position de l'image-objet doit être conservée sous forme de quantité à virgule flottante et arrondie à un entier juste avant l'affichage.

Vous pouvez également maintenir l'image-objet à une super résolution et sous-échantillonner l'image-objet avant l'affichage. Si vous mainteniez l'image-objet à une résolution d'affichage de 3x, vous auriez 9 images-objets réelles différentes selon la position des sous-pixels de l'image-objet.


3

La seule vraie solution ici est d'utiliser le filtrage bilinéaire. L'idée est de laisser le GPU calculer la valeur de chaque pixel en fonction des quatre pixels d'image-objet qui se chevauchent avec lui. C'est une technique courante et efficace. Vous avez simplement besoin de placer le sprite sur une plaine 2D (un panneau d'affichage) comme texture; puis utilisez le GPU pour rendre ces plaines. Cela fonctionne bien, mais attendez-vous à obtenir des résultats quelque peu flous et à perdre l'aspect 8 ou 16 bits si vous le visiez.

avantages:

Solution matérielle déjà implémentée, très rapide.

les inconvénients:

Perte de fidélité 8 bits / 16 bits.


1

Le point flottant est la façon normale de le faire, surtout si vous effectuez finalement un rendu sur une cible GL où le matériel est assez satisfait d'un polygone ombré avec des coordonnées flottantes.

Cependant, il existe une autre façon de faire ce que vous faites actuellement, mais un peu moins saccadée: le point fixe. Une valeur de position de 32 bits peut représenter des valeurs de 0 à 4 294 967 295, même si votre écran a presque certainement moins de 4 000 pixels le long de l'un ou l'autre axe. Donc, mettez un "point binaire" fixe (par analogie avec le point décimal) au milieu et vous pouvez représenter les positions de pixels de 0 à 65536 avec encore 16 bits de résolution sous-pixel. Vous pouvez ensuite ajouter des nombres comme d'habitude, il vous suffit de vous rappeler de convertir en décalant vers la droite de 16 bits chaque fois que vous convertissez en espace d'écran ou lorsque vous multipliez deux nombres ensemble.

(J'ai écrit un moteur 3D en virgule fixe 16 bits à l'époque où j'avais un Intel 386 sans unité à virgule flottante. Il atteignait la vitesse aveuglante de 200 polygones ombragés Phong par image.)


1

En plus des autres réponses ici, vous pouvez utiliser le tramage dans une certaine mesure. Le tramage est l'endroit où le bord des pixels d'un objet est de couleur plus claire / plus foncée pour correspondre à l'arrière-plan, ce qui donne un bord plus doux. Par exemple, disons que vous aviez un carré de 4 pixels avec lequel je vais approximativement:

OO
OO

Si vous deviez déplacer ce 1/2 pixel vers la droite, rien ne bougerait vraiment. Mais avec quelques tramages, vous pourriez faire un peu illusion de mouvement. Considérez O comme noir et o comme gris, vous pourriez donc faire:

oOo
oOo

Dans un cadre et

 OO
 OO

Ensuite. Le "carré" réel avec les bords grisés prendrait en fait 3 pixels de diamètre, mais comme ils sont gris plus clair que le noir, ils apparaissent plus petits. Cela peut être difficile à coder, cependant, et je ne suis pas vraiment au courant de quoi que ce soit qui le fasse actuellement. À peu près au moment où ils ont commencé à utiliser le tramage pour les choses, les résolutions sont rapidement devenues meilleures et il n'y avait pas autant de besoins que dans des choses telles que la manipulation d'images.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.