
                             VGA-Kurs - Part #9

Hallo ihr VGA-Coder und die, die es werden wollen!
Willkommen zur 9. Ausgabe von "T.C.P.'s Beginner's Guide To VGA-Coding"!
Fr diesen Teil habe ich mir einiges vorgenommen, nmlich die Prinzipien der
3D-Grafik. Das dies kein leichtes Thema ist, sollte man sich im Klaren darber
sein, da man das Ganze als 3D-Neuling nicht gleich beim ersten Durchlesen
kapieren kann. Es kostet einfach Zeit, die Zusammenhnge verstehen und diese
dann in eigene Programme umsetzen zu knnen. Deshalb rate ich euch, tippt nicht
einfach nur die Sources ab, sondern versucht herauszukriegen, was
dahintersteckt, oder noch besser: schreibt alles selbst!
Aber genug der Vorrede, lasset uns das heie 3D-Eisen anpacken!
Ich hoffe, jeder von euch kann sich einen dreidimensionalen Raum vorstellen.
Zum Beispiel das Weltall. Nehmen wir einmal an, unsere Sonne wre der
Mittelpunkt des Weltalls. Ihre Position wird also durch die Koordinaten 0,0,0
(X-, Y- und Z-Koordinate) definiert. Auerdem nehmen wir an, da das Weltall
3 Achsen hat, die sich im Mittelpunkt der Sonne treffen. Die erste Achse (X)
verluft geradewegs horizontal von links nach rechts durchs komplette All und
durch die Sonne. Die zweite (Y) verluft vertikal von unten nach oben, und die
dritte (Z) von uns (dem Betrachter) aus direkt durch die Sonne ins tiefe All.
Als Einheiten fr die Achsen nehmen wir Lichtjahre (weils zum Beispiel pat).
Jetzt denken wir uns irgendwo im Raum einen einsamen Stern. Um seine Position
zu definieren, mssen wir seine Entfernung zum Nullpunkt (der Sonne) bestimmen.
Angenommen, er liegt 7 Lichtjahre "rechts" von der Sonne, dann hat er die
X-Koordinate 7. Liegt er zudem noch 2 Lichtjahre unterhalb der Sonne, ist
seine Y-Koordinate -2, denn die Achse fhrt nach oben. Dann liegt er noch 5
Lichtjahre weiter weg von uns als die Sonne, hat also die Z-Koordinate 5.
Knnt ihr euch jetzt ungefhr vorstellen, in welcher Relation sich der Stern
zur Sonne und unserem Auge (dem Sichtpunkt) befindet? Gut, dann ist euch das
Wichtigste schon klar.
Eine Umsetzung dieses Prinzips finden wir in den uns allen bekannten
Sternenflugeffekten aus Demos oder Windows-Bildschirmschonern. Hier fliegt der
Betrachter sozusagen durchs Weltall, wobei sich alle Sterne an ihm
vorbeibewegen. Diesen Effekt werden wir heute besprechen.
Zuallererst mssen wir die Positionen der Sterne definieren. Dazu bedienen wir
uns eines Records:

type Star3D = record
                x,y,z : integer; { X-, Y- und Z-Koordinaten eines Sterns }
              end;

Jetzt noch ein Array, in dem die Positionen aller Sterne gespeichert werden.

const StarNo = 1000; { 1000 Sterne }
var Stars3D : array[1..StarNo] of Star3D; { Array mit allen Koordinaten }

Nun, mit 1000 Sternen haben wir ein sehr eingeschrnktes Universum, aber wie
bei einem normalen Scrolly auch, werden die Sterne einfach geloopt, d.h. die
Sterne, an denen der Betrachter vorbeigezogen ist, werden ans Ende der Schleife
wiederangehngt.
Achtung, jetzt kommt der Trick: Eigentlich mte sich im logischen Sinne der
Betrachter fortbewegen, und die Sterne immer am selben Ort, also an denselben
Koordinaten, verweilen. Aber der Einfachheit halber drehen wir den Spie um:
Wir definieren eine Variable ZAdd, die bei jedem Schleifendurchlauf vor der
Darstellung der Sterne auf die Z-Koordinaten dieser addiert wird. Ist diese
Variable negativ, bewegen sich die Sterne entgegengesetzt der Z-Achse, und damit
auf den Betrachter zu, wodurch der Eindruck entsteht, er bewege sich vorwrts.
Dabei definieren wir die Position des Betrachters der Einfachheit wegen als
0,0,0. Das einzige, was wir dabei beachten mssen ist, da kein Stern die X-
und Y-Koordinaten 0,0 hat, weil wir sonst mit ihm kollidieren wrden.
Alles klar? Nein? Dann das Ganze nochmal als Pseudocode.

begin
  Zufllige_Positionen_fr_Sterne_berechnen;
  VGA_Modus_setzen;
  Palette_setzen;
  repeat
    Sterne_weiterbewegen_durch_addieren_von_ZAdd;
    Alte_Sternpositionen_sichern;
    Sternenkoordinaten_umrechnen;
    Auf_Vertical_Retrace_warten;
    Alle_Sterne_lschen;
    Sterne_darstellen;
  until keypressed;
  readkey;
  Textmodus_setzen;
end.

Aber jetzt zum Wichtigsten: der Darstellung. Wie berechne ich aus den X-,
Y- und Z-Koordinaten eines Sterns (3D) die X- und Y-Koordinaten, die er auf dem
Bildschirm (2D) haben mu?
Eigentlich ganz einfach: Um X2D zu erhalten, teilen wir X3D durch Z3D, und fr
Y2D lautet die Formel Y3D durch Z3D. Warum das so ist, hat folgenden Grund.
Stellen wir uns einmal eine richtig lange, gerade Strae vor, keine einzige
Kurve, bis zum Horizont (der Traum fr jeden Biker ;). Am Straenrand stehen,
wie hierzulande blich, Straenpfhle im Abstand von 50 Metern. Diese Pfhle
stehen alle in einer Linie, haben also alle dieselbe X-Koordinate. Auch die
Y-Koordinate ist bei allen gleich, denn die Strae verluft vllig eben.
Nur die Z-Koordinate unterscheidet sich jeweils um 50 Meter. Vergleichen wir
nun zwei dieser Pfhle auf der linken Straenseite, so fallen uns zwei
Unterschiede auf. Erstens erscheint der hintere kleiner und zweitens ist er
weiter rechts und weiter oben in unserem Blickfeld.
Die erste Erkenntnis hilft uns bei unseren Sternen nicht viel, denn sie sind
alle gleich gro (1 Pixel), aber aus der zweiten knnen wir ersehen, da die
Formel von vorhin richtig war. Je weiter ein Stern von uns entfernt ist, desto
grer ist seine Z-Koordinate, teilen wir als X und Y durch Z, werden diese
kleiner, wie auch in Wirklichkeit. Ist das nicht cool? 3D kann so einfach sein!
So wird die Variable zum Speichern der umgerechneten Koordinaten definiert:

type Star2D = record
                x,y : integer;
              end;
     Star2DArray = array[1..StarNo] of Star2D;
var Stars2D : Star2DArray;
    Backup  : Star2DArray;

Wozu das Backup? Das hat Geschwindigkeitsgrnde. Auf einem Computer unter
Pentium ist nicht genug Zeit da, um zwischen dem Lschen der Sterne und dem
Darstellen der Sterne die neuen Sternpositionen zu berechnen, ohne dabei mit dem
Vertical Retrace in Konflikt zu kommen. Also werden die alten Sternpositionen
in Backup gesichert, und vor der Darstellung die neuen berechnet.
Die fertige Prozedur zum Umrechnen von 3D in 2D mte so aussehen:

procedure Calc_2Dto3D;
var n : integer;
begin
  for n := 1 to StarNo do begin
    Stars2D[n].x := Stars3D[n].x * 128 div Stars3D[n].z + 160;
    Stars2D[n].y := Stars3D[n].y * 128 div Stars3D[n].z + 100;
  end;
end;

Der Formel wurde noch ein '* 128' zugefgt, weil sonst die Werte fr den
Bildschirm zu klein sind, auerdem wird 160 bzw. 100 addiert, um die Mitte des
Koordinatensystems in die Mitte des Screens zu verlagern.
Eins fehlt uns jetzt noch zur perfekten (naja) 3D-Illusion. Die weiter
entfernten Sterne sollten dunkler dargestellt werden als die, die dem Betrachter
am nahsten sind. Das drfte auch kein Problem sein, wenn wir die Sterne
einfach je dunkler zeichnen, desto grer ihre Z-Koordinate ist.
Dies ginge mit der Formel Farbe = Star3D.z div 10. Dies pat allerdings
nur, wenn man StarNo auf 1000 lt, ansonsten mu man den Wert 10 etwas erhhen.
Das Ergebnis der Formel mu dann von 63 abgezogen werden, weil unsere Palette
in 64 Graustufen von Schwarz nach Wei verluft.
Auerdem werden nur Sterne dargestellt, deren Z-Koordinate < 500 ist, dies
lt den Effekt etwas realistischer wirken.
Hier also die Prozeduren zum Darstellen, Lschen und Weiterbewegen der Sterne.

procedure DrawStars;
var n : integer;
begin
  for n := 1 to StarNo do if (Stars2D[n].x > 0) and (Stars2D[n].x < 320)
                             and (Stars2D[n].y > 0) and (Stars2D[n].y < 200)
                             and (Stars3D[n].z < 500)
                          then mem[$A000:Stars2D[n].y*320+Stars2D[n].x] :=
                              63-Stars3D[n].z div 10;
end;

procedure ClearStars;
var n : integer;
begin
  for n := 1 to StarNo do if (Backup[n].x > 0) and (Backup[n].x < 320)
                             and (Backup[n].y > 0) and (Backup[n].y < 200)
                          then mem[$A000:Backup[n].y*320+Backup[n].x] := 0;
end;

procedure MoveStars;
var n : integer;
begin
  for n := 1 to StarNo do begin
    inc(Stars3D[n].z,ZAdd);
    if Stars3D[n].z < 1 then inc(Stars3D[n].z,StarNo);
    if Stars3D[n].z > StarNo then dec(Stars3D[n].z,StarNo);
  end;
end;

Das war's eigentlich schon. Was uns noch fehlt, ist die Prozedur zum Erstellen
des Sternen-Arrays. Das drfte jetzt aber kein Problem mehr darstellen, oder?

procedure InitStars;
var n : integer;
begin
  for n := 1 to StarNo do begin
    repeat
      Stars3D[n].x := random(640) - 320;
      Stars3D[n].y := random(400) - 200;
    until (Stars3D[n].x <> 0) and (Stars3D[n].y <> 0);
    Stars3D[n].z := random(StarNo);
  end;
end;

Und schon haben wir alles zusammen was wir bentigen. Ach, ihr wollt auch noch
das fertige Programm? Na gut, ich will euch ja nicht zu sehr fordern ;)

program Sternen_Effekt;
uses crt;

const StarNo = 1000; { 1000 Sterne }

type Star3D = record
                x,y,z : integer;
              end;
     Star2D = record
                x,y : integer;
              end;
     Star2DArray = array[1..StarNo] of Star2D;

var Stars3D : array[1..StarNo] of Star3D;
    Stars2D : Star2DArray;
    Backup  : Star2DArray;
    ZAdd,n  : integer;

{ Hier Calc_3Dto2D, DrawStars, ClearStars und MoveStars einfgen }

procedure WaitRetrace;assembler;
asm
  mov     dx,3DAh
@1:
  in      al,dx
  and     al,8
  jz      @1
@2:
  in      al,dx
  and     al,8
  jz      @2
end;

procedure SetPal(col,r,g,b:byte);assembler;
asm
  mov     dx,3C8h
  mov     al,col
  out     dx,al
  inc     dx
  mov     al,r
  out     dx,al
  mov     al,g
  out     dx,al
  mov     al,b
  out     dx,al
end;

begin
  randomize;            { Zufallsgenerator anwerfen }
  InitStars;            { Sternenarrays mit Werten fllen }
  asm mov ax,13h; int 10h end; { VGA Modus setzen }
  for n := 0 to 63 do SetPal(n,n,n,n); { Palette setzen }
  ZAdd := -4;           { ZAdd initialisieren }
  repeat
    MoveStars;          { Sterne bewegen }
    Backup := Stars2D;  { Alte Sternpositionen sichern }
    Calc_2Dto3D;        { und neue berechnen }
    WaitRetrace;        { Auf Retrace warten }
    ClearStars;         { Alte Sterne lschen }
    DrawStars;          { und neue zeichnen }
  until keypressed;
  readkey;
  asm mov ax,3; int 10h end;
end.

Die Variable ZAdd bestimmt die Geschwindigkeit der Sterne. Ist sie positiv,
bewegen sie sich vom Betrachter weg.

Und wieder habt ihr eine (hoffentlich nicht zu schwere) Ausgabe meines
VGA-Kurses berstanden. Fr den nchsten Teil plane ich entweder die Fortsetzung
des 3D-Kurses oder einen Kurs ber SVGA-Coding (dafr gab es immerhin 5 Anfragen
im PCH 4/96), inlusive High- und True-Color-Auflsungen. Bis denn!





[ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ]
[ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ]
[                                                                      ]
[ No  part   of  this   document  may  be   reproduced,   transmitted, ]
[ transcribed,  stored in a  retrieval system,  or translated into any ]
[ human or computer language, in any form or by any means; electronic, ]
[ mechanical,  magnetic,  optical,   chemical,  manual  or  otherwise, ]
[ without the expressed written permission of the author.              ]
[                                                                      ]
[ The information  contained in this text  is believed  to be correct. ]
[ The text is subject to change  without notice and does not represent ]
[ a commitment on the part of the author.                              ]
[ The author does not make a  warranty of any kind with regard to this ]
[ material, including,  but not limited to,  the implied warranties of ]
[ merchantability  and fitness  for a particular  purpose.  The author ]
[ shall not be liable for errors contained herein or for incidental or ]
[ consequential damages in connection with the furnishing, performance ]
[ or use of this material.                                             ]
