Was ist das beste Muster, um ein System zu erstellen, bei dem alle Objektpositionen zwischen zwei Aktualisierungszuständen interpoliert werden sollen?
Das Update wird immer mit der gleichen Frequenz ausgeführt, aber ich möchte in der Lage sein, mit jedem FPS zu rendern. Das Rendering wird also so flüssig wie möglich, unabhängig davon, ob die Frames pro Sekunde niedriger oder höher als die Aktualisierungsfrequenz sind.
Ich möchte 1 Frame in die Zukunft aktualisieren und vom aktuellen Frame zum zukünftigen Frame interpolieren. Diese Antwort hat einen Link, der darüber spricht:
Halbfester oder vollständig festgelegter Zeitschritt?
Edit: Wie könnte ich auch die letzte und aktuelle Geschwindigkeit in der Interpolation verwenden? Beispiel: Bei einer reinen linearen Interpolation bewegt sich die Kamera zwischen den Positionen mit derselben Geschwindigkeit. Ich brauche eine Möglichkeit, die Position zwischen den beiden Punkten zu interpolieren, aber berücksichtige die Geschwindigkeit an jedem Punkt für die Interpolation. Dies ist hilfreich für Simulationen mit niedrigen Raten wie Partikeleffekte.
quelle
Antworten:
Sie möchten Aktualisierungsraten (logisches Häkchen) und Ziehungsraten (Renderhäkchen) trennen.
Ihre Aktualisierungen erzeugen die Position aller zu zeichnenden Objekte in der Welt.
Ich werde hier zwei verschiedene Möglichkeiten behandeln, die von Ihnen gewünschte, die Extrapolation und auch eine andere Methode, die Interpolation.
1.
Bei der Extrapolation wird die (vorhergesagte) Position des Objekts im nächsten Frame berechnet und dann zwischen der aktuellen Objektposition und der Position des Objekts im nächsten Frame interpoliert.
Dazu muss jedem zu zeichnenden Objekt ein
velocity
und zugeordnet seinposition
. Um die Position zu ermitteln, an der sich das Objekt im nächsten Frame befindet, addieren wir einfachvelocity * draw_timestep
die aktuelle Position des Objekts, um die vorhergesagte Position des nächsten Frames zu ermitteln.draw_timestep
ist die Zeit, die seit dem vorherigen Render-Tick (auch bekannt als Draw-Call) vergangen ist.Wenn Sie es dabei belassen, werden Sie feststellen, dass Objekte "flackern", wenn ihre vorhergesagte Position nicht mit der tatsächlichen Position im nächsten Frame übereinstimmt. Um das Flackern zu beseitigen, können Sie die vorhergesagte Position speichern und bei jedem Zeichenschritt zwischen der zuvor vorhergesagten Position und der neuen vorhergesagten Position hin- und herschalten , wobei die seit der vorherigen Aktualisierung verstrichene Zeit als Lerp-Faktor verwendet wird. Dies führt immer noch zu einem schlechten Verhalten, wenn sich schnell bewegende Objekte plötzlich ändern und Sie diesen Sonderfall möglicherweise behandeln möchten. Alles, was in diesem Absatz gesagt wird, ist der Grund, warum Sie keine Extrapolation verwenden möchten.
2.
Bei der Interpolation wird der Status der letzten beiden Aktualisierungen gespeichert und zwischen diesen basierend auf der aktuellen Zeitspanne interpoliert, die seit der letzten Aktualisierung vergangen ist. In diesem Setup muss jedem Objekt ein
position
und zugeordnet seinprevious_position
. In diesem Fall stellt unsere Zeichnung im schlimmsten Fall einen Update-Tick hinter dem aktuellen Gamestate dar und im besten Fall genau denselben Status wie der aktuelle Update-Tick.Meiner Meinung nach möchten Sie wahrscheinlich eine Interpolation, wie ich sie beschrieben habe, da sie einfacher zu implementieren ist und es in Ordnung ist, einen winzigen Sekundenbruchteil (z. B. 1/60 Sekunde) hinter Ihrem aktuellen aktualisierten Status zu zeichnen.
Bearbeiten:
Falls das oben Genannte nicht ausreicht, um eine Implementierung durchzuführen, finden Sie hier ein Beispiel für die von mir beschriebene Interpolationsmethode. Ich werde nicht auf die Hochrechnung eingehen, da mir kein reales Szenario einfällt, in dem Sie es vorziehen sollten.
Wenn Sie ein ziehbar Objekt erstellen, wird es um die Eigenschaften speichern erforderlich gezogen werden (dh der Zustand benötigten Informationen zu ziehen).
In diesem Beispiel werden Position und Drehung gespeichert. Möglicherweise möchten Sie auch andere Eigenschaften wie Farbe oder Texturkoordinatenposition speichern (z. B. wenn eine Textur gescrollt wird).
Um zu verhindern, dass Daten geändert werden, während der Render-Thread sie zeichnet (dh die Position eines Objekts wird geändert, während der Render-Thread zeichnet, aber alle anderen wurden noch nicht aktualisiert), müssen wir eine Art Doppelpuffer implementieren.
Ein Objekt speichert zwei Kopien davon
previous_state
. Ich werde sie in ein Array stellen und sie alsprevious_state[0]
und bezeichnenprevious_state[1]
. Es werden ebenfalls zwei Kopien benötigtcurrent_state
.Um zu verfolgen, welche Kopie des Doppelpuffers verwendet wird, speichern wir eine Variable
state_index
, die sowohl dem Update- als auch dem Draw-Thread zur Verfügung steht.Der Update-Thread berechnet zunächst alle Eigenschaften eines Objekts unter Verwendung seiner eigenen Daten (beliebige Datenstrukturen). Dann kopiert er
current_state[state_index]
zuprevious_state[state_index]
und kopiert die neuen Daten relevant für das Zeichnen,position
undrotation
incurrent_state[state_index]
. Dann wirdstate_index = 1 - state_index
die aktuell verwendete Kopie des Doppelpuffers umgedreht.Alles im obigen Absatz muss mit herausgenommenem Schloss gemacht werden
current_state
. Das Update und Draw Threads entfernen beide diese Sperre. Die Sperre wird nur für die Dauer des schnellen Kopierens von Statusinformationen aufgehoben.Im Render-Thread führen Sie dann eine lineare Interpolation von Position und Drehung wie folgt durch:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Wo
elapsed
ist die Zeit, die im Render-Thread seit dem letzten Update-Tick vergangen ist, undupdate_tick_length
wie lange dauert Ihre feste Update-Rate pro Tick (z. B. bei 20FPS-Updatesupdate_tick_length = 0.05
) ?Wenn Sie die
Lerp
obige Funktion nicht kennen , lesen Sie den Artikel von Wikipedia zum Thema: Lineare Interpolation . Wenn Sie jedoch nicht wissen, was Lerping ist, sind Sie wahrscheinlich nicht bereit, entkoppelte Aktualisierungen / Zeichnungen mit interpolierten Zeichnungen zu implementieren.quelle
Lerp(previous_speed, current_speed, elapsed/update_tick_length)
). Sie können dies mit einer beliebigen Nummer tun, die Sie im Bundesstaat speichern möchten. Beim Lerping erhalten Sie nur einen Wert zwischen zwei Werten, wenn ein Lerp-Faktor angegeben wird.Für dieses Problem müssen Sie Ihre Definitionen von Start und Ende etwas anders überlegen. Anfänger denken oft an Positionsänderungen pro Frame und das ist ein guter Anfang. Betrachten wir für meine Antwort eine eindimensionale Antwort.
Angenommen, Sie haben einen Affen an Position x. Jetzt haben Sie auch ein "addX", zu dem Sie die Position des Affen pro Frame basierend auf der Tastatur oder einem anderen Steuerelement hinzufügen. Dies funktioniert, solange Sie eine garantierte Bildrate haben. Nehmen wir an, Ihr x ist 100 und Ihr addX ist 10. Nach 10 Frames sollte sich Ihr x + = addX auf 200 summieren.
Anstelle von addX sollten Sie jetzt bei variabler Bildrate in Bezug auf Geschwindigkeit und Beschleunigung denken. Ich werde Sie durch all diese Arithmetik führen, aber es ist super einfach. Wir möchten wissen, wie weit Sie pro Millisekunde (1/1000 Sekunde) fahren möchten.
Wenn Sie mit 30 Bildern pro Sekunde aufnehmen, sollte Ihre Geschwindigkeit 1/3-Sekunde betragen (10 Bilder aus dem letzten Beispiel bei 30 Bildern pro Sekunde), und Sie wissen, dass Sie in dieser Zeit 100 'x' zurücklegen möchten. Stellen Sie daher Ihre Geschwindigkeit auf 100 Entfernung / 10 FPS oder 10 Entfernung pro Frame. In Millisekunden entspricht dies 1 Abstand x pro 3,3 Millisekunden oder 0,3 'x' pro Millisekunde.
Jetzt müssen Sie bei jedem Update nur noch die verstrichene Zeit herausfinden. Unabhängig davon, ob 33 ms verstrichen sind (1/30 Sekunde) oder was auch immer, multiplizieren Sie einfach den Abstand 0,3 mit der Anzahl der verstrichenen Millisekunden. Dies bedeutet, dass Sie einen Zeitgeber benötigen, der Ihnen ms-Genauigkeit (Millisekunden) gibt, aber die meisten Zeitgeber geben Ihnen dies. Mach einfach so etwas:
var beginTime = getTimeInMillisecond ()
... später ...
var time = getTimeInMillisecond ()
var elapsedTime = time-beginTime
beginTime = Zeit
... Verwenden Sie jetzt diese verstrichene Zeit, um alle Ihre Entfernungen zu berechnen.
quelle