HTML5-Tutorium: Canvas: MiniPong 02: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Wechseln zu:Navigation, Suche
(requestAnimationFrame)
Zeile 46: Zeile 46:
 
die Beschleunigung eines Balles stetig. Die Simulation derartiger Größenänderungen erfolgt an Rechner dagegen fast immer
 
die Beschleunigung eines Balles stetig. Die Simulation derartiger Größenänderungen erfolgt an Rechner dagegen fast immer
 
in diskreten Schritten (Ausnahme: [[Analogrechner]]): Mehrmals pro Sekunde werden die Größen neu berechnet. Durch die
 
in diskreten Schritten (Ausnahme: [[Analogrechner]]): Mehrmals pro Sekunde werden die Größen neu berechnet. Durch die
diskrete Berechnung kontinuierlicher Änderungen kommt es unweigerlich zu Rechenfehlern. Um diese möglichst gering zu halten,
+
diskrete Berechnung kontinuierlicher Änderungen kommt es unweigerlich zu Rechenfehlern.  
sollte die [[Frequenz|Berechnungsfrequenz]] so hoch wie nur immer möglich sein.  
 
  
 
Aus mathematischer Sicht wird bei der Simulation einer physikalischen Welt die [[Integration]] durch die Bildung diskreter Summen ersetzt.  
 
Aus mathematischer Sicht wird bei der Simulation einer physikalischen Welt die [[Integration]] durch die Bildung diskreter Summen ersetzt.  
Zeile 54: Zeile 53:
 
die Anzahl der Flächen ist unendlich groß. Diese Grenzwertbildung können wir am Rechner natürlich nicht nachbilden.
 
die Anzahl der Flächen ist unendlich groß. Diese Grenzwertbildung können wir am Rechner natürlich nicht nachbilden.
 
Wir können allerdings versuchen, diskrete Summen von möglichst vielen, möglichst kleinen Elementen zu bilden.
 
Wir können allerdings versuchen, diskrete Summen von möglichst vielen, möglichst kleinen Elementen zu bilden.
Das erreichen wir, indem wir die Frequenz der physikalischen Berechnungen möglichst groß wählen.
+
Das erreicht man, indem die Frequenz der physikalischen Berechnungen möglichst groß gewählt wird.
 +
500 Neuberechnungen pro Sekunde sind deutlich besser als 20 Neuberechnungen.
 +
Und diese sollten möglichst gleichmäßig erfolgen, ohne große Lücken
 +
zwischen den einzelnen Schritten, da der zeitliche Abstand zwischen zwei Berechnungsschritten bei  der Simulation
 +
berücksichtigt werden muss: Ein Ball bewegt sich in 20 ms weiter als in 10. Es ist zwar auch möglich bei jeder Neuberechnung
 +
die aktuelle Berechnungsfrequenz zu berücksichtigen – indem man für jeden Berechnungsschritt einen Zeitstempel vergibt, mit
 +
dem man ermitteln kann, wann die letzte Berechnung erfolgt ist –, besser ist es jedoch, wenn zwischen zwei Schritten stets
 +
gleich viel Zeit vergeht.
  
 +
Ganz anders stellt sich die Situation im Falle der Grafikausgabe dar. Während für die physikalische Simulation ein paar tausend
 +
oder zehntausend Rechenschritte notwendig sind, benötigt man für die Darstellung der Objekte auf dem Bildschirm meist
 +
mehrere Millionen Operationen. Beispielsweise besteht ein Full-HD-Bild  aus $1920 \cdot 1080 = 2.073.600$  Pixeln.
 +
Heute werden {{iAllg}} Hochleistungsgrafikarten eingesetzt, um mindestens 60 Bilder pro Sekunde zeichnen zu können.
 +
Anderenfalls würde das Bild zu sehr flackern. Eine Framerate von 500 Bildern pro Sekunde ist meist vollkommen undenkbar.
 +
Das ist aber auch gar nicht notwendig. Es reicht, wenn das Bild auf dem Minitor so oft wie möglich aktualisiert wird. Es müssen
 +
nur jeweils alle Objekte, an ihren aktuellen Positionen gezeichnet werden. Und ob zwischen zwei Frames mal ein paar
 +
Millisekunden mehr oder oder weniger vergehen, ist auch nicht wesentlich. Wichtiger ist, dass der Rechner für die
 +
Verarbeitung des nächsten Bildes bereit ist. Wenn er das letzte Bild noch nicht gezeichnet hat, sollte er nicht mit
 +
dem nächsten anfangen. Sonst kann es passieren, das viele aufeinander folgende Bilder nur jeweils unvollständig gezeichnet werden
 +
oder dass das System zu ruckeln anfängt, weil die Zeichnen eines jeden Bildes zu lange dauert.
  
 +
Um diese Probleme zu umgehen, gibt es in JavaScript den Befehl „[https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame <code>window.requestAnimationFrame</code>]“. Diese Methode erwartet als Input eine Callback-Funktion, die aufgerufen wird,
 +
sobald der Bildschirm wieder für eine Grafikausgabe bereitsteht. Üblicherweise aktualisiert diese Callback-Funktion zunächst
 +
die Bildschirmdarstellung (Neuzeichnen eins Canvas-Elements, Ändern von [[SVG]]-Elementen im DOM-Baum etc.) und ruft
 +
dann <code>requestAnimationFrame</code> rekursiv mit derselben Call-Backfunktion auf:
 +
 +
<source lang="javascript">
 +
function updateView()
 +
{
 +
  // app: update view
 +
  window.requestAnimationFrame(updateView);
 +
}
 +
</source>
 +
 +
Der Callback-Funktion wird bei jedem Aufruf ein Zeitstempel mitgegeben. Damit kann beispielsweise die aktuelle Framerate ermittelt werden:
 +
 +
<source lang="javascript">
 +
var v_last_step = null;
 +
function updateView(p_timestamp)
 +
{
 +
  if (v_last_step == null)
 +
      v_last_step = p_timestamp:
 +
  else
 +
  {
 +
    // app: update view
 +
    console.log(1/(p_timestamp - v_last_step))  //console.log ist to slow, use, e.g., the canvas itself as output device
 +
    v_last_step = p_timestamp:
 +
  }
 +
  window.requestAnimationFrame(updateView);
 +
}
 +
</source>
 +
 +
Jetzt muss die Animation nur noch gestartet werden. Dazu muss  <code>requestAnimationFrame</code> einfach einmal direkt aufgerufen werden:
 +
 +
<source lang="javascript">
 +
window.requestAnimationFrame(updateView);
 +
</source>
 +
 +
Dieser Befehl liefert übrigens einen Identifikator zurück, den man in einer Variablen speichern kann:
 +
 +
<source lang="javascript">
 +
var v_animation_id = window.requestAnimationFrame(updateView);
 +
</source>
 +
 +
Damit ist es möglich, die Animation zu einem späteren Zeitpunkt mittels [https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame <code>cancelAnimationFrame</code>] wieder zu stoppen:
 +
 +
<source lang="javascript">
 +
window.cancelAnimationFrame(v_animation_id);
 +
</source>
  
 
==Quellen==
 
==Quellen==

Version vom 2. November 2016, 14:42 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 3
(einige wichtige Fakten fehlen)
Quellenangaben: 5
(vollständig vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 5
(ausgezeichnet)

HTML-Tutorium: MiniPong

MiniPong: | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5

Musterlösung: [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong02/web/index.html MiniPong 2 (SVN-Repository)

1 Ziel: Animation des Balls

Die Ball-Animation des ersten Teils des Tutoriums zeichnet sich noch durch ein prinzipielles Problem aus: Bei der Darstellung der Animation wird die Leistungsfähigkeit des Rechners des Benutzers der Anwendung nicht berücksichtigt. Das ist bei einer einfachen Ball-Animation sicher auch nicht notwendig. Bei komplexeren Anwendungen kann dies allerdings schnell zu großen Performanz-Problemen führen.

Mit Hilfe zweier Optimierungstechniken kann man diese Probleme deutlich abmildern. (Das heißt allerdings nicht, dass jede Animation auf jedem Endgerät läuft.)

2 Neues Projekt anlegen

Legen Sie ein neues Projekt mit dem Namen „MiniPong02“ an.

Kopieren Sie alle Dateien des Projektes MiniPong01 in das neue Projekt.

Nehmen Sie folgende Anpassungen vor:

  • Umbenennung des Ordners „app“ in „app1“.
  • Umbenennung der Datei „main.js“ in „main1.js“.
  • In der Datei „main1.js“:
    • Ersetzen von „app: 'app'“ durch „app: 'app1'“.
  • In der Datei „index.html“:
    • Ändern des Titels in „MiniPong02“:
    • Ersetzen von „js/main'“ durch „js/main1“ im Script-Befehl.

Nun sollte die Anwendung wieder laufen.

3 requestAnimationFrame

Prinzipiell müssen in jedem Animationsschritt der Anwendung „MiniPong“ aus zwei wesentliche Aktionen durchgeführt werden:

  1. Aktualisierung der Simulation eine physikalischen Welt.
  2. Aktualisierung der Darstellung der Objekte, deren Physik simuliert wird, auf der Bühne.

Der erste Schritt ist meist nicht sonderlich rechenintensiv: Es müssen einige mathematische Operationen durchgeführt werden, um die Position, die Masse, den Zustand und evtl. andere Eigenschaften der simulierten Objekte zu ermitteln. Allerdings ist es notwendig, diese Berechnungen möglichst häufig pro Sekunde durchzuführen. In der realen Welt erfolgen die meisten Änderungen physikalischer Größen kontinuierlich. Beispielsweise ändern sich die Position, die Geschwindigkeit und die Beschleunigung eines Balles stetig. Die Simulation derartiger Größenänderungen erfolgt an Rechner dagegen fast immer in diskreten Schritten (Ausnahme: Analogrechner): Mehrmals pro Sekunde werden die Größen neu berechnet. Durch die diskrete Berechnung kontinuierlicher Änderungen kommt es unweigerlich zu Rechenfehlern.

Aus mathematischer Sicht wird bei der Simulation einer physikalischen Welt die Integration durch die Bildung diskreter Summen ersetzt. Bekanntlich kann ein Integral mit Hilfe von Grenzwerten von Flächensummen definiert werden. Die Grenzwertbildung bedeutet, dass die Flächen von immer mehr, immer kleineren Bereichen aufsummiert werden. Im Grenzfall sind die betrachteten Flächen unendlich klein und die Anzahl der Flächen ist unendlich groß. Diese Grenzwertbildung können wir am Rechner natürlich nicht nachbilden. Wir können allerdings versuchen, diskrete Summen von möglichst vielen, möglichst kleinen Elementen zu bilden. Das erreicht man, indem die Frequenz der physikalischen Berechnungen möglichst groß gewählt wird. 500 Neuberechnungen pro Sekunde sind deutlich besser als 20 Neuberechnungen. Und diese sollten möglichst gleichmäßig erfolgen, ohne große Lücken zwischen den einzelnen Schritten, da der zeitliche Abstand zwischen zwei Berechnungsschritten bei der Simulation berücksichtigt werden muss: Ein Ball bewegt sich in 20 ms weiter als in 10. Es ist zwar auch möglich bei jeder Neuberechnung die aktuelle Berechnungsfrequenz zu berücksichtigen – indem man für jeden Berechnungsschritt einen Zeitstempel vergibt, mit dem man ermitteln kann, wann die letzte Berechnung erfolgt ist –, besser ist es jedoch, wenn zwischen zwei Schritten stets gleich viel Zeit vergeht.

Ganz anders stellt sich die Situation im Falle der Grafikausgabe dar. Während für die physikalische Simulation ein paar tausend oder zehntausend Rechenschritte notwendig sind, benötigt man für die Darstellung der Objekte auf dem Bildschirm meist mehrere Millionen Operationen. Beispielsweise besteht ein Full-HD-Bild aus $1920 \cdot 1080 = 2.073.600$ Pixeln. Heute werden i. Allg. Hochleistungsgrafikarten eingesetzt, um mindestens 60 Bilder pro Sekunde zeichnen zu können. Anderenfalls würde das Bild zu sehr flackern. Eine Framerate von 500 Bildern pro Sekunde ist meist vollkommen undenkbar. Das ist aber auch gar nicht notwendig. Es reicht, wenn das Bild auf dem Minitor so oft wie möglich aktualisiert wird. Es müssen nur jeweils alle Objekte, an ihren aktuellen Positionen gezeichnet werden. Und ob zwischen zwei Frames mal ein paar Millisekunden mehr oder oder weniger vergehen, ist auch nicht wesentlich. Wichtiger ist, dass der Rechner für die Verarbeitung des nächsten Bildes bereit ist. Wenn er das letzte Bild noch nicht gezeichnet hat, sollte er nicht mit dem nächsten anfangen. Sonst kann es passieren, das viele aufeinander folgende Bilder nur jeweils unvollständig gezeichnet werden oder dass das System zu ruckeln anfängt, weil die Zeichnen eines jeden Bildes zu lange dauert.

Um diese Probleme zu umgehen, gibt es in JavaScript den Befehl „window.requestAnimationFrame“. Diese Methode erwartet als Input eine Callback-Funktion, die aufgerufen wird, sobald der Bildschirm wieder für eine Grafikausgabe bereitsteht. Üblicherweise aktualisiert diese Callback-Funktion zunächst die Bildschirmdarstellung (Neuzeichnen eins Canvas-Elements, Ändern von SVG-Elementen im DOM-Baum etc.) und ruft dann requestAnimationFrame rekursiv mit derselben Call-Backfunktion auf:

function updateView()
{
   // app: update view
   window.requestAnimationFrame(updateView);
 }

Der Callback-Funktion wird bei jedem Aufruf ein Zeitstempel mitgegeben. Damit kann beispielsweise die aktuelle Framerate ermittelt werden:

var v_last_step = null;
function updateView(p_timestamp)
{
   if (v_last_step == null)
      v_last_step = p_timestamp:
  else
  {
     // app: update view
    console.log(1/(p_timestamp - v_last_step))  //console.log ist to slow, use, e.g., the canvas itself as output device
     v_last_step = p_timestamp:
  }
   window.requestAnimationFrame(updateView);
 }

Jetzt muss die Animation nur noch gestartet werden. Dazu muss requestAnimationFrame einfach einmal direkt aufgerufen werden:

window.requestAnimationFrame(updateView);

Dieser Befehl liefert übrigens einen Identifikator zurück, den man in einer Variablen speichern kann:

var v_animation_id = window.requestAnimationFrame(updateView);

Damit ist es möglich, die Animation zu einem späteren Zeitpunkt mittels cancelAnimationFrame wieder zu stoppen:

window.cancelAnimationFrame(v_animation_id);

4 Quellen

  1. Braun (2011): Herbert Braun; Webanimationen mit Canvas; in: c't Webdesign; Band: 2011; Seite(n): 44–48; Verlag: Heise Zeitschriften Verlag; Adresse: Hannover; 2011; Quellengüte: 5 (Artikel)
  2. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)
  3. Musterlösung MiniPong 01 (SVN)
  4. Musterlösung MiniPong 01a (SVN)