Quel est le meilleur modèle pour créer un système avec toutes les positions des objets à interpoler entre deux états de mise à jour?
La mise à jour s'exécutera toujours à la même fréquence, mais je veux pouvoir effectuer un rendu à n'importe quel FPS. Ainsi, le rendu sera aussi fluide que possible, peu importe les images par seconde, qu'elles soient inférieures ou supérieures à la fréquence de mise à jour.
Je voudrais mettre à jour 1 image dans l'interpolation future de l'image actuelle vers la future image. Cette réponse a un lien qui parle de faire ceci:
Horodatage semi-fixe ou entièrement fixe?
Edit: Comment pourrais-je également utiliser la dernière vitesse et la vitesse actuelle dans l'interpolation? Par exemple, avec une interpolation uniquement linéaire, il se déplacera à la même vitesse entre les positions. J'ai besoin d'un moyen pour qu'il interpole la position entre les deux points, mais prenne en considération la vitesse à chaque point pour l'interpolation. Il serait utile pour les simulations à faible débit comme les effets de particules.
la source
Réponses:
Vous souhaitez séparer les taux de mise à jour (tick logique) et de dessin (tick de rendu).
Vos mises à jour produiront la position de tous les objets dans le monde à dessiner.
Je couvrirai ici deux possibilités différentes, celle que vous avez demandée, l'extrapolation, et aussi une autre méthode, l'interpolation.
1.
L'extrapolation est l'endroit où nous calculerons la position (prédite) de l'objet à l'image suivante, puis interpolerons entre la position actuelle des objets et la position que l'objet sera à l'image suivante.
Pour ce faire, chaque objet à dessiner doit avoir un
velocity
et associéposition
. Pour trouver la position que l'objet sera à l'image suivante, nous ajoutons simplementvelocity * draw_timestep
à la position actuelle de l'objet, pour trouver la position prédite de l'image suivante.draw_timestep
est le temps qui s'est écoulé depuis le tick de rendu précédent (alias l'appel de dessin précédent).Si vous vous en tenez à cela, vous constaterez que les objets "scintillent" lorsque leur position prédite ne correspond pas à la position réelle à l'image suivante. Pour supprimer le scintillement, vous pouvez stocker la position prédite et lerp entre la position prédite précédemment et la nouvelle position prédite à chaque étape de dessin, en utilisant le temps écoulé depuis la dernière mise à jour cocher comme facteur lerp. Cela se traduira toujours par un mauvais comportement lorsque des objets en mouvement rapide changent soudainement d'emplacement, et vous voudrez peut-être gérer ce cas spécial. Tout ce qui est dit dans ce paragraphe est la raison pour laquelle vous ne voulez pas utiliser d'extrapolation.
2.
L'interpolation est l'endroit où nous stockons l'état des deux dernières mises à jour et nous les interpolons en fonction du temps actuel écoulé depuis la dernière mise à jour. Dans cette configuration, chaque objet doit avoir un
position
et associéprevious_position
. Dans ce cas, notre dessin représentera au pire un tick de mise à jour derrière le gamestate actuel, et au mieux, exactement au même état que le tick de mise à jour actuel.À mon avis, vous voulez probablement une interpolation telle que je l'ai décrite, car c'est la plus facile des deux à implémenter, et dessiner une infime fraction de seconde (par exemple 1/60 seconde) derrière votre état actuel mis à jour est très bien.
Modifier:
Dans le cas où ce qui précède ne suffit pas pour vous permettre d'effectuer une implémentation, voici un exemple de la façon de faire la méthode d'interpolation que j'ai décrite. Je ne couvrirai pas l'extrapolation, car je ne pense à aucun scénario du monde réel dans lequel vous devriez le préférer.
Lorsque vous créez un objet dessinable, il stockera les propriétés nécessaires à dessiner (c'est-à-dire les informations d' état nécessaires pour le dessiner).
Pour cet exemple, nous allons stocker la position et la rotation. Vous pouvez également vouloir stocker d'autres propriétés comme la position des coordonnées de couleur ou de texture (c'est-à-dire si une texture défile).
Pour éviter que les données ne soient modifiées pendant que le thread de rendu les dessine (c'est-à-dire que l'emplacement d'un objet est modifié pendant que le thread de rendu dessine, mais tous les autres n'ont pas encore été mis à jour), nous devons implémenter un certain type de double tampon.
Un objet en stocke deux copies
previous_state
. Je vais les mettre dans un tableau et les désigner commeprevious_state[0]
etprevious_state[1]
. De même, il en a besoin de deux copiescurrent_state
.Pour garder une trace de la copie du double tampon utilisée, nous stockons une variable
state_index
, qui est disponible à la fois pour le fil de mise à jour et de dessin.Le thread de mise à jour calcule d'abord toutes les propriétés d'un objet en utilisant ses propres données (toutes les structures de données que vous souhaitez). Ensuite, il copie
current_state[state_index]
àprevious_state[state_index]
, et copie les nouvelles données pertinentes pour le dessin,position
etrotation
encurrent_state[state_index]
. Ensuite, ilstate_index = 1 - state_index
retourne la copie actuellement utilisée du double tampon.Tout dans le paragraphe ci-dessus doit être fait avec un verrou retiré
current_state
. Les fils de mise à jour et de dessin retirent tous les deux ce verrou. Le verrou n'est retiré que pendant la durée de la copie des informations d'état, ce qui est rapide.Dans le fil de rendu, vous effectuez ensuite une interpolation linéaire sur la position et la rotation comme suit:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Où
elapsed
est le temps qui s'est écoulé dans le fil de rendu, depuis la dernière mise à jour, etupdate_tick_length
le temps que met votre taux de mise à jour fixe par tick (par exemple, lors des mises à jour 20FPS,update_tick_length = 0.05
).Si vous ne savez pas quelle est la
Lerp
fonction ci-dessus, consultez l'article de wikipedia sur le sujet: Interpolation linéaire . Cependant, si vous ne savez pas ce qu'est le lerping, vous n'êtes probablement pas prêt à implémenter la mise à jour / dessin découplé avec le dessin interpolé.la source
Lerp(previous_speed, current_speed, elapsed/update_tick_length)
). Vous pouvez le faire avec n'importe quel numéro que vous souhaitez stocker dans l'état. Lerping vous donne juste une valeur entre deux valeurs, étant donné un facteur lerp.Ce problème vous oblige à réfléchir un peu différemment à vos définitions de début et de fin. Les programmeurs débutants pensent souvent au changement de position par image et c'est une bonne façon de procéder au début. Pour le bien de ma réponse, considérons une réponse unidimensionnelle.
Disons que vous avez un singe en position x. Maintenant, vous avez également un "addX" auquel vous ajoutez à la position du singe par image en fonction du clavier ou d'un autre contrôle. Cela fonctionnera tant que vous aurez une fréquence d'images garantie. Disons que votre x est 100 et votre addX est 10. Après 10 images, votre x + = addX devrait s'accumuler jusqu'à 200.
Maintenant, au lieu d'addX, lorsque vous avez une fréquence d'images variable, vous devriez penser en termes de vitesse et d'accélération. Je vais vous guider à travers toute cette arithmétique mais c'est super simple. Ce que nous voulons savoir, c'est la distance que vous souhaitez parcourir par milliseconde (1 / 1000e de seconde)
Si vous photographiez à 30 FPS, votre velX devrait être au 1/3 de seconde (10 images du dernier exemple à 30 FPS) et vous savez que vous voulez parcourir 100 'x' pendant ce temps, alors réglez votre velX sur 100 distance / 10 FPS ou 10 distance par image. En millisecondes, cela équivaut à 1 distance x par 3,3 millisecondes ou 0,3 'x' par milliseconde.
Maintenant, à chaque mise à jour, il vous suffit de déterminer le temps écoulé. Que 33 ms se soient écoulées (1 / 30ème de seconde) ou autre, vous multipliez simplement la distance 0,3 par le nombre de millisecondes passées. Cela signifie que vous avez besoin d'un temporisateur qui vous donne une précision en ms (millisecondes), mais la plupart des temporisateurs vous en donnent. Faites simplement quelque chose comme ceci:
var beginTime = getTimeInMillisecond ()
... plus tard ...
var time = getTimeInMillisecond ()
var elapsedTime = time-beginTime
beginTime = heure
... utilisez maintenant ce temps écoulé pour calculer toutes vos distances.
la source