Welcome to the new Friends-of-FPC!

Here you can find all kinds of information about the FreePascal Compiler. We have many tutorials and howtos as well as a selection of tools to help you with your programming. We also have some example codes for you. And if you want to contribute some information/ sources/ tools yourself you can do so.
Also we have finally relaunched the FoFPC forum. It's your chance for some Q&A about everything FreePascal.

Friends-of-FPC

Tutorials: Learn how to code with FreePascal.

Source Codes: A collection of examples, miscellaneous source codes and open source stuff.

Tools and Help Files: Intro- duction of some tools that might help you with FPC.

Community

Forum: Ask or answer questions about the FreePascal Compiler, programming or just babble about coding.

Contribute! Contribute your own Tutorial, Source Codes or Tools and send them to us!

Website

About: Information about Friends-of-FPC.org.

Grundlagen der Grafikprogrammierung - Teil 12 - by Delax

Yaaaay!

Dieser Teil wird ziemlich lange, also schnallt euch an. Als erstes werde ich grundlegend den Plasma-Effekt erklären. Dann optimieren wir ihn, so das ihr langsam ein Gefühl dafür bekommt wo man wie weshalb was optimieren kann.

Ganz allgemein gibt es 3 Möglichkeiten ein Plasma-Effekt zu coden:

  • Sinus/ Cosinus Funktionen.
  • Fraktale Gebilde bei denen die Farbpalette rotiert bzw. verschoben wird.
  • Sontige Bilder, bei denen die Farbpalette rotiert bzw. verschoben wird.

Die zweite und dritte Möglichkeit sollte eigentlich klar sein. Man hat eine Vorlage auf dem Bildschirm und jeder Pixel hat eine Farbinformation. Nun ändert man diese Farbinformation Stück für Stück in eine andere Farbe, so daß die Farben flüssig in einander übergehen. Sieht nett aus und ist einfach zu realisieren, aber jetzt mal zu dem ECHTEN Plasma Effekt :).

Dieser funktioniert wie oben angesprochen mit Sinus und Cosinus Berechnungen. Ich gehe mal davon aus, ihr wisst, was eine Sinuswelle ist und wie diese aussieht. Die Formel y = sin(x) sollte auch bekannt sein.

So. Wer jetzt abschaltet und sagt: "Ach, schon wieder Mathe!", der kann gleich wieder hochfahren. Alles, was wir über Sinus/ Cosinus-Funktionen wissen müssen ist, daß sie die Form einer netten Welle haben. Jene geht von -1 bis +1 und wieder zurück. Alles klar? Relax!

Um nun eine Welle zu erhalten, die etwas "spektakulärer" ist, müssen wir die normale Sinuswelle etwas "optimieren". Mit anderen Worten: das Ergebnis mit zusätzlichen Werten verändern. Nun sieht die Formel etwa so aus: y = sin(x*2). Das Ergebnis sieht aus, wie eine "zusammengedrückte" Sinuswelle. Wenn man X durch andere Werte teilen würde, dann wäre die Sinuswelle in die Länge gezogen.

Also - nun haben wir schon 3 Arten von Wellen - die originale Sinuswelle y = sin(x) und 2 kleine Abwandlungen: y = sin(x/2) und y = sin(x*2). Jetzt verbinden wir diese einfach.

y = sin(x) + sin(x*2) + sin(x/2)

Alles klar? Um es noch schlimmer zu machen setzen wir nun noch ein paar weitere Werte ein:

y = 20 * sin(x) + 30 * sin(x*2) + 40 * sin(x/2)

Und Stop! Jetzt haben wir einen Graphen, der eigentlich nicht mehr allzuviel mit unserer ursprünglichen Sinusfunktion zu tun hat. Wer sich obere Formel nicht graphisch vorstellen kann, der sollte an der Stelle erst mal halblang machen, ein paar Werte einsetzen und diese in ein Koordinatensystem übertragen (oder sich ein Programm coden, welches das macht :) ).

Ein Graph ist ja eine statische Angelegenheit - will heißen: da bewegt sich gar nichts. Doch wie animiert man einen Graphen? Antwort: man plottet ihn, nimmt den nächsten Wert, plottet ihn, nimmt etc. Beispiel: y = x + a. X bleibt immer gleich und a zählt hoch. plotten, hochzählen, plotten, etc. Selbiges funktioniert auch bei unserer Funktion.

y = 20 * sin((x) + (a)) + 30 * sin((x*2) + (a)) + 40 * sin((x/2) + (a))

Wer es noch interessanter machen will nimmt so was hier:

y = 20 * sin((x) + (a)) + 30 * sin((x*2) + (a*2)) + 40 * sin((x/2) + (a/2))

Nur nicht aufregen, Leute, alles halb so schlimm. Das einzige, was wichtig ist, sind die Werte für A. Die müssen nämlich für jeden Frame minimal aufaddiert werden. Je kleiner der Wert der Addition, desto flüssiger sieht die Animation aus - wie immer also. Übrigens gibt eben jene Formel einen netten Welleneffekt, nur so nebenbei :)

Und nun machen wir aus der Funktion ein Plasma (wurde aber auch Zeit). Dazu definieren wir ein Feld, in dem unser Plasma ablaufen soll. 100x100, 320x200, was auch immer. Nun nehmen wir wieder unsere Formel, aber anstatt einen X/ Y Graph zu plotten setzen wir natürlich unsere X/ Y Koordinaten für den Bildschirm ein. Weshalb zwei Formeln? Ganz einfach: in eine setzen wir die X-Werte und in die anderen die Y-Werte unseres Bildschirmes ein. Nun addieren wir die zwei Werte und erhalten so die Farbe, mit der wir eben jene X/ Y-Koordinate füllen.

Nun, was dann folgt hatten wir schon: Wert für A ändern und das Spiel von vorne. Hier mal ein Stück Pseudocode, ausgehend von 640x480:

wenn nicht keypressed, dann mache folgendes

für x = 1 bis 640 mache folgendes

xkoordinate =   20 * Sin(Rad((X) + (a))) +
                30 * Sin(Rad((X*2) + (a*2))) + 
                40 * Sin(Rad((X/2) + (a/2)));

für y = 1 bis 480 mache folgendes

ykoordinate =   40 * Sin(Rad((X) + (a))) +
                30 * Sin(Rad((X*4) + (a*4))) + 
                20 * Sin(Rad((X/4) + (a/4)));

PutPixel(X,Y,Trunc(xkoordinate+ykoodinate));
erhöhe a;

und von vorne;

Wie ihr seht, sind da zwei Kleinigkeiten: Das "Rad" steht da, weil ein Wert im Bogenmaß genommen werden soll und nicht vergessen, daß da mit hoher Wahrscheinlichkeit mit Nachkommastellen gerechnet wird - dazu gibt es Trunc. Trunc wandelt eine Real Zahl in eine Integer Zahl um.

Schauen wir uns mal eine fertige Plasma Prozedur basierend auf diesem Algorithmus an:

PROCEDURE plasma(plasmacounter : WORD);
 BEGIN
  FOR counter1 := 1 TO 640 DO
   BEGIN
    xcoord :=  20 * Sin(Rad((counter1*4) + (plasmacounter))) +
    30 * Sin(Rad((counter1) + (plasmacounter * 4))) +
    40 * Sin(Rad((counter1/4) + (plasmacounter / 2)));

  FOR counter2 := 1 TO 480 DO
   BEGIN
    ycoord :=  20 * Sin(Rad((counter2*6) + (plasmacounter))) +
    40 * Sin(Rad((counter2) + (plasmacounter * 4))) +
    20 * Sin(Rad((counter2) + (plasmacounter / 2)));

PutPixel(vidvscr, counter1, counter2,Trunc(xcoord+ycoord),0);
   
   END;
   END;
END;

Hier als Beispiel zum download

Wenn ihr das kompiliert und aufruft merkt ihr es schon: mein Gott, ist das laaaangsam. (Die Leute mit dem 2GHz Rechner bitte still sein). Aber wie haben die dann damals auf nem 486 oder gar 386 flüssige Vollbild Plasmen hinbekommen?! Einfach: erstens einmal war die Bildschirmauflösung niedriger und zweitens haben sie diese Routine noch optimiert.

Als erstes einmal sollte man sich so eine Formel anschauen. Die Multiplikationen nehmen Zeit weg, also her mit Binary Shifting. Ich sprach das ja schon oft an, hier die Erklärung. Eine Zahl läßt sich ja immer als Binär Wert ausdrücken. Beispielweise die Zahl 53 hat den Binärwert 00110101. Will man diesen nun schnell verdoppeln oder halbieren genügt es die Binärzahl nach links oder rechts zu verschieben. Im Endeffekt bedeutet das folgendes: klemmt die linke Zahl ab und hängt recht eine Null dran und ihr habt aus 53 den Wert auf 106 verdoppelt! (01101010) Klemmt ihr die rechte Zahl ab und hängt links eine Null dran bekommt ihr als Ergebnis 27. Wieso nicht 26.5? Ist nicht! Nur ganze Zahlen und alle werden aufgerundet! Also kann man folgendes sagen:

x shl 1 enspricht x := x * 2;
x shl 2 enspricht x := x * 4;
x shr 1 enspricht x := x / 2;
x shr 3 enspricht x := x / 8;

Ich habe schon erwähnt, das Binary Shifting sehr schnell ist. Davon könnt ihr euch selbst überzeugen, wenn ihr in dem Beispiel einfach die Plasma Prozedur durch die folgende austauscht:

PROCEDURE plasma(plasmacounter : WORD);
 BEGIN
  FOR counter1 := 1 TO 640 DO
   BEGIN
    xcoord :=  20 * Sin(Rad((counter1 shl 2) + (plasmacounter))) +
    30 * Sin(Rad((counter1) + (plasmacounter shl 4))) +
    40 * Sin(Rad((counter1 shr 2) + (plasmacounter shr 1)));

  FOR counter2 := 1 TO 480 DO
   BEGIN
    ycoord :=  20 * Sin(Rad((counter2 shl 2) + (plasmacounter))) +
    40 * Sin(Rad((counter2) + (plasmacounter shl 2))) +
    20 * Sin(Rad((counter2) + (plasmacounter shr 1)));

PutPixel(vidvscr, counter1, counter2,Trunc(xcoord+ycoord),0);
   
   END;
   END;
END;

Na gut, ihr wisst jetzt, wie man Multiplikationen und Divisionen schneller macht, aber wieso zur Hölle ist das Plasma noch so langsam? Das nächste, was einem bei Optimierungen ins Auge springen sollte sind Sinus und Cosinus. Die dauern nämlich ewig - und dann auch noch jede Runde, eine Schande! Weg damit. Doch wie? Ganz einfach - wir rechnen einfach sämtliche Sinus-Berechnungen vorher durch, speichern sie und nutzen dann nur noch diese vorher berechneten Werte, ohne noch einmal etwas zu tun.

Wir machen jetzt folgendes: wir definieren einen neuen Array. Ein Array ist eine Serie von Variablen, auf die man zugreifen kann. Bsp: bei einem ARRAY[1..10] namens "Test" gibt es Test[1], Test[2], Test[3], ... Test[10]. Diese haben dann alle eigenständige Inhalte. Wenn wir nun einen Array für die Sinus Werte namens "sinlookup" anlegen, und in jede Zelle dieses Array die Werte für eine Sinus Berechnung einfügen, könnten wir anstatt sin(200) zu berechnen einfach in sinlookup[200] nachsehen.

Aussehen tut das dann so:

PROCEDURE dosinlookup;
 BEGIN
  FOR counter1 := 0 TO 360 DO sinlookup[counter1] := sin(Rad(counter1));
 END;

PROCEDURE plasma(plasmacounter : WORD);
 BEGIN
  FOR counter1 := 100 TO 540 DO
   BEGIN
    xcoord :=  20 * Sinlookup[((counter1 shl 2) + (plasmacounter)) mod 360]+
    30 * Sinlookup[((counter1) + (plasmacounter shl 3)) mod 360]+
    40 * Sinlookup[((counter1 shr 2) + (plasmacounter shr 2)) mod 360];
  FOR counter2 := 100 TO 380 DO
   BEGIN
    ycoord :=  20 * Sinlookup[((counter2 shl 2) + (plasmacounter)) mod 360] +
    30 * Sinlookup[((counter2) + (plasmacounter shl 3)) mod 360]+
    40 * Sinlookup[((counter2) + (plasmacounter shr 2)) mod 360];

PutPixel(vidvscr, counter1, counter2,Trunc(xcoord+ycoord),0);
   
   END;
   END;
END;

Hier als Beispiel zum download

Schon schneller, was? Aber noch entfernt vom Optimum. Die Sache hat nämlich noch einen Haken: Nachkommastellen. Nein, nicht wundern - nachdenken. Nachkommastellen benötigen mehr Prozessorzeit, als ganze Zahlen. Jetzt haben wir aber ein Problem - immerhin rechnen wir mit Sinus, und Divisionen und so weiter - wie will man da ohne Nachkommastellen arbeiten?

Na gut, kleines Gedankenspiel: wir haben die Zahl 1.29383 und mit dieser müssen wir rechnen. Aber folgendes: was, wenn wir die Zahl mit 100000 multiplizieren? Dann wird daraus 129383 und das ist eine ganze Zahl - ohne eine Stelle verloren zu haben. Eigentlich hat sich nur das Komma verabschiedet. Also weg mit Trunc und her mit dem Kram!

PROCEDURE dosinlookup;
 BEGIN
  FOR counter1 := 0 TO 360 DO 
     sinlookup[counter1] := Trunc(Sin(Rad(counter1)) * 1024);
 END;

PROCEDURE plasma(plasmacounter : WORD);
 BEGIN
  FOR counter1 := 1 TO 640 DO
   BEGIN
    xcoord :=  20 * Sinlookup[((counter1 shl 2) + (plasmacounter)) mod 360]+
    30 * Sinlookup[((counter1) + (plasmacounter shl 3)) mod 360]+
    40 * Sinlookup[((counter1 shr 2) + (plasmacounter shr 2)) mod 360];
  FOR counter2 := 1 TO 480 DO
   BEGIN
    ycoord :=  20 * Sinlookup[((counter2 shl 2) + (plasmacounter)) mod 360] +
    30 * Sinlookup[((counter2) + (plasmacounter shl 3)) mod 360]+
    40 * Sinlookup[((counter2) + (plasmacounter shr 2)) mod 360];

PutPixel(vidvscr, counter1, counter2,((xcoord+ycoord) shr 10),0);
   
   END;
   END;
END;

Setzt diese Prozeduren statt der alten ein und wandelt xcoord, ycoord, counter1, counter2 in Integer Werte und sinlookup in einen ARRAY of Integer:

	xcoord, ycoord : Integer;
	counter1, counter2 : Integer;
	sinlookup : ARRAY[1..360] OF Integer;

Und schon ist das Plasma ein Stück schneller. Nein, es ist immer noch nicht auf dem Maximum, aber das soll für dieses Mal reichen. Merkt euch: bei Multiplikationen und Divisionen mit festen Werten möglichst shl und shr nehmen. Sin und Cos sind langsam, genauso wie Trunc und alles andere, was mit REAL Zahlen zu tun hat. Aber - nur weil etwas nicht am schnellsten ist, heißt das nicht, das man es unter bestimmten Umständen nicht in kauf nehmen sollte! Man muß eben abwägen, aber keine Sorge, das bekommt ihr schon noch hin.

So! Ihr seid erlöst für dieses Mal! Bis dann!

Delax/ Sundancer Inc.
[delax@sundancerinc.de]

Back to previous page

Useful Links









Link to us