3D Grafik Basic's verstehen ("Programmieren" lernen advanced)

  • Nachdem hier neulich mal jemand eine Anfrage bezüglich einer 3D Routine hatte und ich ihm da schon bißchen was "getextet" hatte, kommt jetzt mal noch eine kleine generelle Hinführung in die dritte Dimension auf dem Homecomputer. (Mal sehen was es so wird.)



    Prinzipiell sollte man natürlich erstmal was über 2D schreiben, und ja, da gibt es mehr als man so denkt und was teilweise überhaupt nicht mehr so richtig benutzt wird, heutzutage. Dithering etwa. Was man aber natürlich i.P. jeder als Basis von 2D kennt ist, daß es irgendwie einen leeren Bildschirm gibt und auf den läßt sich was zeichnen. Damit das vernünftig klappt hat der ein Koordinatensystem - und bereits da, also bevor (!) der erste Punkt überhaupt gezeichnet ist, wird es schon schwierig. Warum ?


    Nun, die meisten werden das kennen: Es gibt irgendwie ein Bildschirmkoodinatensystem was z.B. bei [0,0] losgeht und z.B. bei [320,200] oder [640x200] endet. Mathematisch ist das aber unschön, weil man so ja nur einen Quadranten darstellen kann, nämlich den mit den positiven Werten für die X-Achse und den ebenfalls positiven für die Y-Achse. Es läßt sich also quasi nur ein "doppelt-positiver" Punkt zeichnen, und das auch nur, wenn er die Grenzen in X [320] (oder [640]) und in Y [200] nicht überschreitet. Ein Punkt an [64,32] oder [123,196] ist also problemlos zeichenbar, aber einer an [-32,111] oder [96,-144] wird erstmal einen Fehler bringen.





    Nun kann man natürlich sagen, daß das kein Problem ist, man muß halt nur eine vernünftige Programmiersprache benutzen ... sowas wie GFA Basic oder ein vernünftiges grafikfähiges Pascal (Borland Turbo) oder eine brauchbare Graphikbibliothek für C oder gleich Python oder ... , aber:

    das Problem verschwindet dadurch nicht, es wird nur vorm User verborgen. Denn was macht eine solche Sprache, damit das klappt ? Sie paßt einfach den Punktwert "irgendwie" so an, daß man auch negative Werte benutzen kann. Und was passiert da genau ? Der Punktwert wird schlicht verschoben.

    Wenn also der Punkt [-132,12] dargestellt werden soll, addiert man einfach einen Wert auf X, so daß eine positive Zahl entsteht. Damit das funktioniert, gibt es natürlich bestimmte Vorgaben. Zum Beispiel könnte einfach festgelegt sein, daß der Punkt [0,0] exakt in der Bildschirmmitte sitzen soll. Bei einer Beispielauflösung von [320,200] ist diese Mitte also irgendwo bei [160,100].

    Um jetzt den Punkt [-132,12] an die richtige Position zu bringen, addiert man einfach den Wert der Mitte auf die Koordinatenwerte des Punktes, also

    [-132,12] + [160,100] = [ (-132+160) , (12+100) ] = [28,112]


    Das klappt natürlich auch mit anderen Punkten, z.B. [-23,-56], und alle landen dann auf einer Bildschirmkoordinate, die der Rechner auch wirklich anzeigen kann. Die einzige Einschränkung bisher ist aber natürlich, daß man so natürlich nur Werte in einem Bereich der halben Bildschirmbreite bzw. halben Bildschirmhöhe darstellen kann, also im positiven nicht mehr bis X [320] sondern nur noch bis X [160] zeichnen kann. Das gleich gilt für Y dort natürlich mit einem Bereich von [-100] bis [100], eine Y [200] ist dann schon zu groß. Dafür kann man nun alle 4 Quadranten benutzen.




    Was macht man hier also eigentlich ?


    Nun man muß sich klarmachen, daß man in zwei unterschiedlichen Koordinatensystemen "unterwegs" ist - einmal in dem, was der Rechner bzw. Bildschirm so hergibt und was immer (!) eine irgendwie begrenzte Darstellung bietet, etwa eine maximale Auflösung von [320,200], und den Umstand, daß man die Einzelpunkte eigentlich nur mit positiven Werten ansprechen kann und zweitens in dem eigentlichen Koordinatensystem, in dem man seine Punkte gerne berechnen möchte. Also hier in dem "normalen", d.h. "üblichen" mathematischen 2D Koordinatensystem (momentan noch mit der Einschränkung, daß man in keiner Achse über die Maximalwerte hinauskommt).


    Damit man diese beiden auseinanderhalten kann, heißt das eine das Bildschirmkoordinatensystem bzw. Bildkoordinatensystem und das andere ist das sogenannte Weltkoordinatensystem, nämlich das, in dem "die Welt der Punkte" sich eigentlich abspielt.



    laut WikiP:

    "Das Weltkoordinatensystem bezeichnet das Ursprungskoordinatensystem, mit dem die damit verknüpften relativen Koordinatensysteme referenziert sind. Das Weltkoordinatensystem wird durch orthogonale Achsen beschrieben und durch ein kartesisches Koordinatensystem repräsentiert."


    Es ist also eigentlich nicht die echte Welt gemeint, sondern das Bild der Welt, was durch die Punkte beschrieben wird - also quasi nur die Abbildung der Welt in Koordinaten - und diese wird wieder auf den Bildschirm abgebildet - was als die "damit verknüpften relativen Koordinatensysteme", die referenzieren, beschrieben wird.


    Und das mit dem kartesischen Koordinatensystem ist halt so der übliche Modus, es spräche aber nichts gegen eine andere Art (z.B. Polarkoordinaten), auch da kann man den Punkt aus der "Welt der Punkte" auf einen Bildschirmpunkt abbilden, der darstellbar ist. Nur ist es evtl. nicht mehr so einfach durch simple Addition von [ +halbe Bildschirmbreite, +halbe Bildschirmhöhe ] möglich.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    2 Mal editiert, zuletzt von ThoralfAsmussen ()

  • Möchte man nun aber eine "Welt" mit größeren Werten darstellen, hat man eigentlich nur 2 Möglichkeiten.


    Variante A: Man kauft einen größeren Bildschirm. Damit kann man dann statt der [320,200] Pixel z.B. [1280,960] oder [3840,2160] anzeigen.Damit ließe sich dann ein deutlich größerer Bereich abbilden, auch wieder indem man den Koordinatenursprung in die Mitte verschiebt.


    Noch interessanter ist aber Variante B: Die ist nämlich i.P. unabhängig vom Bildschirm und bedeutet, daß man schlicht alle Werte einmal mit einem bestimmten Fakto multipliziert. Man skaliert alle Punkte in seinem Weltsystem bevor man die Verschiebung macht.


    So kann man z.B. mit einem Faktor 4 die "Auflösung" direkt in dem Weltkoordinatensystem vervierfachen und so Bildpunkte auf der X-Achse zwischen [-640] und [640] sowie auf der Y-Achse zwischen [-400] und [400] darstellen.



    Um diese Werte aus der "Welt" auf den Bildschirm abzubilden, muß man sie einmal durch 4 dividieren und erhält so das kleine Koordinatensystem im Bild. Dieses kann man dann wie gehabt auf den Bildschirm abbilden, indem man die Addition von oben durchführt.


    Das Schöne daran ist jetzt, daß man eigentlich völlig unabhängig von der Art des Bildschirmes ist und quasi jede beliebig hohe Auflösung der "Weltkoordinaten" auf jeden beliebig gut auflösenden Monitor abbilden kann.


    Praktisch wird man natürlich versuchen die Koordinatenwerte so zu gestalten, daß der benutzte Rechner sie auch gut be- und umrechnen kann. Auf einem 16 Bit Rechner wäre es daher evtl. sinnvoll ein Koordinatensytem (Weltkoordinaten) von nicht mehr als 65536 Werten zu benutzen, also z.B. [-32768] bis [32767] auf der X-Achse.


    Diese Art der Abbildung von Weltkoordinaten auf Bildschirmkoordinaten legt man i.P. einmal ganz am Anfang fest. Im weiteren arbeitet man dann einfach mit den Weltkoordinaten - und kann dann dort dann auch mal den ersten Punkt zeichnen.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Man benötigt also bisher zumindest mal: Eine Abbildungsroutine !

    Diese nimmt als Input einen Punkt in Weltkoordinaten an und rechnet diesen auf Bildkoordinaten um und zeichnet damit dann einen Punkt auf den Bildschirm.


    Code
    DEF PROC Abbildung (xwelt, ywelt)
    var x,y, xbild, ybild
      x = xwelt DIV 4
      y = ywelt DIV 4
      xbild = x + (halbe-bildschirmbreite-der-aktuellen-auflösung)
      ybild = y + (halbe-bildschirmhöhe-der-aktuellen-auflösung)
      PLOT(xbild,ybild)
    ENDPROC


    Wer mehr Punkte in seiner Welt haben will, muß i.P. nur den Skalierungsfaktor anpassen.


    Und um es schön zu machen gehören da natürlich noch Fehlerabfragen rein, also etwa, daß Punkte die die Grenzen überschreiten gar nicht erst umgerechnet und dann auch nicht gezeichnet werden, aber hier geht erstmal nur ums Prinzip.


    So, und nun kann's eigentlich schon losgehen ... mit einem Punkt !

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Der Punkt hat nun natürlich (!) Weltkoordinaten zu haben !


    Um hier im Beispiel(bild) zu bleiben also X von [-640] bis [640] und Y von [-400] bis [400].

    Wenn man etwa bei [100,100] einen Punkt setzt, landet der nun an passender Position auf der Anzeige. Natürlich bei der passenden Bildschirmkoordinate.


    Nun kann man aber mit diesem Punkt bereits verschiedenes machen. Man kann ihn etwa hoch und runter wandern lassen. Dazu ändert man die Punktkoordinaten und läßt anschließend jedesmal den Punkt plotten.


    Code
    DIM punktx[10],punkty[10]
    punktx[0]=100
    punkty[0]=100
    FOR I=100 TO -100
      punkty[0]=I
      xwelt=punktx[0]
      ywelt=punkty[0]
      PROC Abbildung(xwelt,ywelt)
    NEXT


    Die Dimensionierung erlaubt das ganze auch bereits mit mehr als einem Punkt zu machen, wenn man entsprechende Schleifen mit einbaut und unterschiedliche Startwerte für die Punkte vorgibt.


    Man kann die Routine aber - bei gleichem Effekt - auch anders ansehen. Nämlich, indem man zu ändernden Wert nicht direkt vorgibt (wird ja hier gemacht, über das I was von 100 bis -100 läuft), sondern stattdessen nur sagt, daß der Punkt in jedem Schleifendurchlauf um einen Y Wert nach unten verändert wird, das ist also


    Code
    DIM punktx[10],punkty[10]
    punktx[0]=100
    punkty[0]=100
    FOR I=100 TO -100
      punkty[0]=punkty[0]-1               <--- hier ist der Unterschied
      xwelt=punktx[0]
      ywelt=punkty[0]
      PROC Abbildung(xwelt,ywelt)
    NEXT


    Der Unterschied ist subtil und am erzeugten Bild nicht zu sehen - aber WESENTLICH !

    Im zweiten Fall wird nämlich der Punktwert in der Punktwelt in einer Fom verändert, die UNABHÄNGIG von seiner aktuellen Position ist. Er wird also relativ verändert.


    Und in dem Fall hier - wenn der Punkt seine Position zu seiner Ausgangsposition um einen bestimmten Wert ändert, nennt man das eine Translation.


    Sowas kann man natürlich auch für die andere Achse noch machen, indem man X entsprechend ändert, also noch über die markierte Zeile ein


    Code
      punktx[0]=punktx[0]-1


    einfügt. Damit würde dann auch in X-Richtung um -1 verschoben werden.

    Damit es einfacher wird, bleiben wir hier aber erstmal nur bei der ersten Variante (nur Y-Wert ändert sich).


    Das interessante ist nun, das sich solch ein Operation auch in anderer Form darstellen läßt. Man kann nämlich eine Matrix vorgeben, in der quasi drinsteht, daß eine Verschiebung um -1 stattfinden soll.


    Damit das funktioniert, benötigt man die Punktwerte, die man sich dafür in einer besonderen Form, nämlich als Vektor, aufschreibt. Aus [X,Y] wird so ein


    (X)

    (Y)


    und damit man damit etwas machen kann benötigt man eine Matrix, die man mit diesem Punktwert verrechnet. Das sieht dann so aus


    ( A B )

    ( C D )


    und dafür gibt es dann folgende Rechenregel fürs Multiplizieren


    ( A B ) (X)

    ( C D ) (Y)


    mit


    (Xneu) = A * X + B * Y

    (Yneu) = C * X + D * Y


    man kann jetzt für A,B,C,D Werte vorgeben und schauen, ob man damit eine Verschiebung hinbekommt.

    z.B. ginge sowas wie A=0 und B=1 mit C=2 und D=0 und als Ergebnis bekommt man


    (Xneu) = 0*X + 1*Y

    (Yneu) = 2*X + 0*Y


    was irgendwie nur was komisches macht, indem es dem neuen X das alte Y zuweist und das neue Y errechnet sich aus X*2 + 0 , also steht hier dann der doppelte X-Wert im Y drin. Es werden mit dieser Kombination aus A,B,C,D quasi nur die beiden Koordinaten vertauscht und dabei ein Wert verdoppelt.


    Man kann das lange probieren, aber so richtig ein zusätzliches +1 oder -1 bekommt man so nicht hin.


    Aber es gibt einen Trick ... und der läuft unter dem Stichwort homogene Koordinaten (WikiP).

    Dabei wird einfach eine zusätzliche Zeile eingeführt - quasi ein sinnloser Wert (hier "W" genannt), der aber ermöglicht solche Translationen mithilfe einer Matrixmultiplikation auszuführen.


    Aus


    (X)

    (Y)


    wird somit ein


    (X)

    (Y)

    (W)


    wobei man sich da natürlich nur für X und Y interessiert, wenn man es ausgerechnet hat. Aber auch die Matrix wird nun größer.


    (A B C)

    (D E F)

    (G H I)


    und die Rechenregel wird entsprechend leicht komplizierter


    (Xneu) = A * X + B * Y + C * W

    (Yneu) = D * X + E * Y + F * W

    (Nneu) = G * X + H * Y + I * W


    und damit läßt sich jetzt eine Matrix schreiben, die zunächst mal einfach lautet


    (1 0 0)

    (0 1 0)

    (0 0 1)


    und man sieht nun, daß sich hier (Xneu) einfach aus dem alten X ergibt und (Yneu) direkt den alten Y-Wert übernimmt, aber (!) man hat in der Gleichung ja nun noch einen Zusatz nämlich für (Xneu) das C*W und für (Yneu) das F*W, was jeweils addiert werden kann. Dafür muß natürlich das W einen Wert von 1 haben und dann kann man in die hinterste Stelle der Matrix reinschreiben, um wieviele Punkte sich der jeweilige Wert ändern soll,


    (1 0 delta-x)

    (0 1 delta-y)

    (0 0 1)


    also ist damit


    (Xneu) = 1*X + 0*Y + delta-x*W

    (Yneu) = 0*X + 1*Y + delta-y*W

    (Wneu) = 0*X + 0*Y + 1*1


    und somit, wenn vorher W=1 gesetzt wird


    (Xneu) = X + delta-x

    (Yneu) = Y + delta-y


    Es ist also - jetzt erstmal - eine extrem komplizierte Variante die gleiche Summe zu bilden wie oben.

    Aber es gibt dabei durchaus Vorteile, die sich später erschließen werden.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • So, eigentlich war das jetzt schon das Essentielle - wer bis hier gelesen hat, hat quasi den komplexen Teil überstanden. ;)


    Nochmal kurz: Man überlegt sich nicht, wie man welche Variablen aufaddiert, um einen Punkt zu verschieben, sondern man benutzt dafür eine Matrix, in die man einfach den gewünschten Verschiebungsbetrag reinschreibt. Durch Multiplikation der Matrix mit dem Vektor - was einfach nur eine andere Schreibweise für ein Punktpaar [X,Y] ist - ergibt sich der Ergebnis-Vektor, in dem also das neue Punktpaar [Xneu,Yneu] drinsteht.


    Wenn man sich die Matrix nochmal in schönerer Form ansieht, erkennt man da evtl. auch ein Muster


    Hier sind mal alle Sachen, die mit X verrechnet werden, rot unterlegt. Wichtig dabei - die 2x2 Matrix hat natürlich eine andere Buchstabenanordnung als die mit 3x3 Feldern. Also bitte "optisch" draufschauen. Da sieht man, daß in der ersten Spalte alles das steht, was irgendwie mit X multipliziert wird. In der zweiten Spalte (B,D bzw B,E,H) alles, was mit Y verrechnet wird.


    Außerdem sieht man, daß die 3x3 Matrix einfach nur quasi das Gleiche wie die 2x2 Matrix ist - aber eben um den blau unterlegten Teil erweitert - und der wird ja nur für Wneu benötigt (was uns aber zum Plotten nicht interessiert) und für die Addition von C*W bzw. F*W , soll heißen X bzw. Y kommen da eigentlich nicht drin vor, zumindest nicht in direkter Rückwirkung auf das Ergebnis.


    Für das weitere kann man sich nunmal anschauen, was man so sinnvollerweise eintragen könnte in die Matrix.


    Etwa würde mit einem


    ( 1 0 0 )

    ( 0 1 0 )

    ( 0 0 1 )


    einfach der alte X Wert übernommen nach Xneu, und ebenso alte Y Wert zu Yneu (und Wneu bekommt natürlich - wegen der 1 rechts unten - auch den alten Wert von W zugewiesen).


    Das wäre dann quasi einfach eine 1:1 Kopie.


    Nun haben wir oben schonmal "spaßeshalber" eine 2 in für so einen Wert eingesetzt (dort für C an komischer Stelle). Nun kann man ja einfach mal die gleiche Matrix wie eben nehmen und da zwei sinnvollere "2en" einsetzen:


    ( 2 0 0 )

    ( 0 2 0 )

    ( 0 0 1 )


    Was passiert nun ?

    (Für jemanden, der das noch nie gemacht hat, wäre das jetzt der richtige Zeitpunkt mal Bleistift und Papier zu nehmen, und das schön schrittweise "aufzudröseln". Mit den Formeln von oben.)


    Man kann es eigentlich direkt "sehen" - X wird mit 2 multipliziert und sonst nix. Ebenso wird Y mit 2 Multipliziert und sonst passiert damit auch nix. Also Ergebnis bekommt man also [2*X,2*Y].


    Und wenn nun jemand da einträgt


    ( 9 0 0 )

    ( 0 9 0 )

    ( 0 0 1 )


    was "sieht" man ?


    ( Man guckt dabei erstmal einfach nur die quasi "eingebettete" 2x2 Matrix oben links an, und dann noch den rechten Rand, wo ja der im Bild blau markierte Zusatz steht.)


    Es wird wohl ein [9*X,9*Y] herauskommen.

    Das kann man nun mit beliebigen Werten machen und da man ja X und Y um den gleichen Faktor vergrößert, bleiben also quasi die Proportionen gewahrt und man vergrößert zudem alle Punkte, die man so behandelt um die gleiche Größe.

    Und sowas ähnliches haben wir ja oben schonmal gemacht, beim Umrechnen der Welt- auf die Bildschirmkoordinaten. Es ist nämlich einfach wieder eine Skalierung.


    Der Unterschied ist nur eben, daß man jetzt hier ausdrücklich nur die Weltkoordinaten skaliert. Das Punktmodell innerhalb "der Welt" wird also quasi größer (und danach kann man es natürlich auch auf dem Bildschirm darstellen, aber das ist eben ein anderer Schritt).


    Man kann jetzt natürlich auch die Punkte "ungleich" skalieren, etwa mit


    ( 4 0 0 )

    ( 0 7 0 )

    ( 0 0 1 )


    da würde also der X-Wert 4x größer, aber der Y-Wert 7x. Wenn das jetzt viele Punkte waren, die zusammen zum Beispiel eine Kugel dargestellt haben, und nun alle auf gleiche Art umgerechnet werden, wird die Kugel erstens größer und zweitens in Y-Richtung deutlich mehr als in X-Richtung - sie wird also verzerrt und man erhält ein Ei, bzw. vornehm gesagt: einen ellipsoiden Körper.


    Und nun kann man schonmal kurz schauen, warum das mit den Matrizen überhaupt sinnvoll sein soll.

    Bisher können wir einen Punkt, oder eben auch eine ganze Punktwolke, wenn man die Berechnung für alle Punkte einzeln in immer gleicher Art macht, Skalieren und Translatieren.


    Wenn wir etwa um einen Faktor 7 ohne Verzerrung skalieren (vergrößern) wollen, bräuchten wir


    ( 7 0 0 )

    ( 0 7 0 )

    ( 0 0 1 )


    und wenn wir die Punkte um z.B. 25 in X und 15 in Y Richtung verschieben wollten, würde man schreiben


    ( 1 0 25 )

    ( 0 1 15 )

    ( 0 0 1 )


    Nun kann man natürlich erstmal die Skalierung machen, d.h. alle Punkte mit der ersten Matrix verrechnen. Und danach die jetzt bereits skalierten Punkte alle nochmal mit der zweiten Matrix verrechnen, was sie dann noch verschiebt.


    Es geht aber auch einfacher, indem man einfach erstmal beide Matrizen zu einer zusammenfaßt - also eine neue "baut", in der dann Skalierung und Translation gleichzeitig "drinstehen", nämlich


    ( 7 0 25 )

    ( 0 7 15 )

    ( 0 0 1 )


    und diese benutzt man, um damit alle Punkte umzurechnen ! Der Rechenweg ist dabei exakt der Gleiche wie oben angegeben.


    Wo liegt nun der Vorteil ?

    Man müßte ja, wenn man z.B. 500 Punkte umrechnen will für die Multiplikation mit der Skalierungsmatrix Rechenzeit einplanen, und dann anschließend nochmal i.P. genausoviel für die zweite Operation, hier das Verschieben. Faßt man aber beide Berechnungen erst einmal zusammen und berechnet dann damit alle Punkte, hat man sich einen Durchgang des alle-500-Punkte-Durchrechnen eingespart ! Bei gleichem Ergebnis !

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Wie sieht das nun als Programm aus ?


    Eigentlich nicht viel anders als oben schon angedeutet



    Das Programm wird bei einem Zoomfaktor von 7 relativ schnell an eine Grenze kommen, wo es einfach abbricht, weil der Zahlenbereich der Darstellung überschritten wird, aber evtl. kann man da ja auch kleinere Faktoren zum "spielen" nehmen.


    Und auch die "Form" ist maximal einfach - eben nur eine Linie. Interessanter wäre z.B. schonmal ein Viereck - also vier Linien. Normalerweise steht an so einer Stelle dann eine Laderoutine, wo man die Punktwolke aus einer Datei einlesen kann, die man vorher mit einem Punktwolkeneditor angelegt hat.


    Was man aber mit einer Linie schön schön Ansehen kann sind andere Matrizen, die man sich auch selbst überlegen kann


    Etwa


    ( -1 0 0 )

    ( 0 -1 0 )

    ( 0 0 1 )


    oder einfacher


    ( -1 0 0 )

    ( 0 1 0 )

    ( 0 0 1 )


    bzw.


    ( 1 0 0 )

    ( 0 -1 0 )

    ( 0 0 1 )


    Was soll das sein ?


    Nun, die Werte für X bzw. Y werden in gleicher Größe beibehalten, ändern aber teils ihr Vorzeichen.

    Daher wird die Figur natürlich auf der jeweils anderen Seite dargestellt werden - wenn dabei nur Xneu=-1*X gesetzt wird, ist das weiteste X auf der positiven Seite danach dann am weitesten links. Und alle X wechseln ihre Seite - die postiven an die gleiche Postion im Negativen und die mit Minusvorzeichen werden positiv.

    Die Figur wird daher über die Y-Achse gespiegelt.


    Analog dazu wird die dritte Matrix den Y-Wert ändern und daher die Figur über die X-Achse spiegeln.


    Und die erste Version macht halt Beides auf einmal.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Wir können also jetzt schon Translatieren, Skalieren, Spiegeln. Aber immer noch nur in 2D ...


    Da das 3D sich aber quasi dann "von selbst" ergibt, würde ich gern noch zwei Sachen ansprechen, die auch schon nett sind, wenn man es in 2D probiert - wobei eines wohl eher ein Problem aufwirft, was man aber für das 2te schonmal angedacht haben muß.


    1.) Skalieren in die andere Richtung

    2.) Rotieren


    Skaliert haben wir ja nur als "Vergrößerung" gemacht, mit Faktoren von z.B. 4 oder 7. Nun könnte man ja auf die Idee kommen, daß andersherum zu machen - also als "Verkleinerung". Dazu müßten also X-Wert und Y-Wert gleichsinnig kleiner werden, was man am Besten natürlich mit einer Division erledigt (dann ist es für beide Richtungen "gleichstark").


    Xneu müßte also sein = X / Verkleinerung

    Yneu wäre = Y / Verkleinerung


    Und prinzipiell kann das die Matrix natürlich auch, wenn man da einfach einen Faktor einträgt, der der Verkleinerung entspricht; also z.B. bei Verkleinern um 3 muß da 1/3 drinstehen oder bei Verkleinern auf die Hälfte ein 1/2. Für Letzteres wäre also zu schreiben


    ( 0.5 0 0 )

    ( 0 0.5 0 )

    ( 0 0 1 )

    Das klappt auch schön ! ... ABER ! :

    Im Gegensatz zum Multiplizieren mit natürlichen Zahlen kommt da nicht immer wieder eine natürliche Zahl heraus. Bei Skalieren mit 0.5 betrifft das halt jede ungerade Zahl, die geteilt wird - augenfälliger ist es bei 1/3 (bzw. 0.3333333333) - da weiß man schon nicht, wie viele Nachkommastellen man in die Matrix eintragen soll, damit das Ergebnis genau genug wird, damit es nicht im Bild auffällt.


    Besonders problematisch ist aber, daß man ja die Punktwerte als Weltkoordinaten ständig umrechnet, zumindest, wenn man mehrere solche Zyklen hintereinander macht - und dabei addieren sich dann solche Fehler.

    Dafür gibt es natürlich Varianten das zu umgehen, ich wollte hier aber schonmal darauf hingewiesen haben.


    Insbesondere ausgefeilt schöne geometrische Gebilde sind nach ein paar Vergrößerungs/Verkleinerungs Zyklen oder - noch schlimmer, nach mehreren Drehungen des Gebildes - recht schnell nur noch dezent verzerrt darstellbar.


    Auf Geräten mit hinreichend hoher Genauigkeit der Fließkommazahlen ist das weniger schnell ein Problem. Und auch auf den gängigen Homecomputer ist so ein "Rechnen direkt in den Weltkoordinaten" inkl. Fehlerfortpflanzung und Rundungsfehlern für Demos etc. problemlos.



    Nun noch die "wichtigste" Operation - das Rotieren.


    Dafür benutzt man einen besonderen Eintrag in der Matrix - und zwar stehen da dann zwei Winkelfunktionen und ein Winkel, um den gedreht werden soll, drin.

    Das Drehen selbst bezieht sich dabei auf den Koordinatenursprung - es wird also jeder Punkt in seinem Abstand zum Punkt [0,0] um genau diesen Ursprung herumgedreht, wobei man sagen kann, um wieviel Grad.


    Die Matrix dazu sieht so aus


    ( cos(winkel) -sin(winkel) 0 )

    ( sin(winkel) cos(winkel) _0 )

    ( ______0 __________0 ______1 )



    Um das Ganze ein bißchen zu beschleunigen, ist es hier sinnvoll, die Werte für cos(winkel) und sin(winkel) vor dem Einsetzen und außerhalb der Rechenschleife zu ermitteln; sonst ist die Routine hauptsächlich mit dem ständigen Ermitteln dieser Werte beschäftigt, 4x für jeden Punkt.


    Für einen Drehwinkel von 5 Grad trägt man also ein


    ( 0.0996 -0.0871 0 )

    ( 0.0871 0.0996 _0 )

    ( ___0 _______0 ___1 )


    womit nun die Punktwolke bei jeder Berechnung um 5 Grad entgegen dem Uhrzeigersinn weitergedreht wird.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • i.P. = "im Prinzip", wenn man dann noch das "man" streicht, wirds halbwegs sinnig; man sollte eben nicht Sätze im Nachhinein umbauen ...



    Hier gleich noch die Diskette mit der Plus/4 Umsetzung von obigem Programm.

    Ist i.P. funktionsfähig - aber zäh ... (bei xplus4 (VICE) im Warp Mode wirds erträglich)

  • Tja nun - eigentlich ist jetzt quasi alles beisammen, was so nötig ist.

    Matrizen, ein paar Operationen, Gedanken über gebrochene Zahlen, Rotationen auf einem 2D Feld, eine diffuse Vorstellung einer Welt voller Punkte und deren Abbildung irgendwohin.


    Eröffnen wir also mal die Dritte Dimension !



    Die Punkte in der Punktwolke bisher waren ja nur 2D - hatten also eine X und eine Y Koordinate. Dementsprechend hatten die einfachen 2x2 Matrizen Platz für 4 Parameter, die eigentlich auch schon reichen würden, um damit zum Beispiel zu skalieren, da wir ja aber auch Verschieben wollten, sind da 3x3 Matrizen draus geworden, mit denen auch das ganz prima zusätzlich noch ging.


    Für die 3te Dimension macht man nun einfach nur eine einzige Sache: Man erweitert den Vektor und die Matrizen.


    Bei den Vektoren - also letztlich den Punktwerten - ist das noch relativ einfach. Da wird aus einem [X,Y] Punkt einer mit 3 Werten, nämlich [X,Y,Z], bzw:


    (X)

    (Y)

    (Z)


    Also so daß wir unser kartesisches Koordinatensystem der "Welt" einfach beibehalten, aber nun zusätzlich noch eine Tiefe hinzufügen - eben die Z-Achse.


    Und damit kann man nun wieder die gleichen Rechnereien machen - etwa, daß man zu einer Achse, z.B. dem X-Wert etwas addiert, was bedeutet, daß der Punkt seine Höhe und Tiefe beibehält aber nach rechts auf der X-Achse verschoben wird.

    Prinzipiell kann man auch alle Werte mit irgendwas multiplizieren, dann wandert der Punkt eben weiter vom Ursprung weg.


    Und weil man jetzt auch das wieder schön bequem und v.a. wenig rechenintensiv, bzw. zusammenfassbar, haben möchte, benutzt man auch hier Matrizen.


    Dabei müssen die aber natürlich die zusätzliche Dimension berücksichtigen - und sind daher wieder ein Stückchen größer. Die einfache Form - die der 2x2 Matrix in 2D entsprechen würde - hat hier 3x3 Felder


    ( A B C )

    ( D E F )

    ( G H I )


    Damit kann man nach genau dem geichen Schema wie oben einen Vektor multiplizieren - nur hier mit dem Unterschied, daß die dritte "Stelle" erstmal das "Z" ist.


    Für X würde gelten

    Xneu = A*X + B*Y + C*Z

    und für die beiden anderen

    Yneu = D*X + E*Y + F*Z

    Zneu = G*X + H*Y + I*Z


    Damit lassen sich auch fast alle Operationen erledigen, z.B. Spiegeln, Skalieren, Rotieren. Nur eines geht wieder nicht - man bekommt partout kein Summe zustande, wo man einfach nur einen extra Summanden dazu hat, der nicht mit einem der drei Werte X,Y,Z verknüpft ist - man kann also, genau wie bei 2D mit der 2x2 Matrix, bei 3D und einer 3x3 Matrix keine Translation direkt abbilden.


    Und darum ... macht man das gleiche wie oben; man benutzt homogene Koordinaten und erweitert dafür den Vektor um eine Position, die einen nur zum Teil wirklich interessiert und die außer als zusätzlicher Verschiebeparameter auch keine wirklich sinnstiftende Bedeutung hat.


    Man erweitert daher den Vektor selbst auf


    (X)

    (Y)

    (Z)

    (W)


    und die Matrix auf eine 4x4 große ebensolche


    ( A B C D )

    ( E F G H )

    ( I J K L )

    (M N O P )


    Da wir ja schon wissen, wie man sowas ausmultipliziert, ist jetzt evtl. auch klar, warum es Sinn macht sich das erstmal in 2D anzuschauen. Es werden nämlich gewaltig lange Formeln, nämlich


    Xneu = A*X + B*Y + C*Z + D*W

    Yneu = E*X + F*Y + G*Z + H*W

    Zneu = I*X + J*Y + K*Z + L*W

    Wneu = M*X + N*Y + O*Z + P*W



    Nach dieser "Anleitung" kann man dann große 3D Matrizen benutzen, wie z.B.


    ( 1 0 0 0 )

    ( 0 1 0 0 )

    ( 0 0 1 0 )

    ( 0 0 0 1 )


    und sieht auch hier, daß z.B. für Xneu ja nur der vorherige X Wert mit 1 multipliziert wird uns sonst nichts geschieht mit X. Analog dazu wird Yneu aus 1*Y berechnet und für Z gilt ebenfalls Zneu=1*Z.

    Das Wneu rhält auch einfach den alten Wert, aber auch hier interessiert und das W letztlich fürs Zeichnen nicht.

    Diese Matrix "errechnet" also lediglich eine Kopie der vorherigen Werte des Punktes [X,Y,Z].


    Was macht also z.B. das hier ? :


    ( 5 0 0 0 )

    ( 0 5 0 0 )

    ( 0 0 5 0 )

    ( 0 0 0 1 )



    Genau, es skaliert alle Werte mit dem Faktor 5.


    Wenn man sich das jetzt als echten geometrischen Vektor im Raum vorstellt - also als Pfeil zwischen Koordinatenursprung und dem Punkt [X,Y,Z] , wird dieser Punkt nach der Multiplikation ganz schön weiter weg vom Nullpunkt liegen als vorher.

    Wenn es aber z.B. 500 Punkte sind und alle sind auf einer Kugeloberfläche verteilt, die um den Ursprung liegt, dann wird man aus einer kleinen Kugel eine ziemlich riesige machen. Das sieht dann auch optisch sehr imposant aus, weil das Volumen mit dem Kubikwert ( Radius ^ 3 ) anwächst.


    Ebenso funktionieren die Spiegelungen, Verkleinerungen und - besonders hübsch - die Rotationen.


    Und alles kann man auch wieder Translatieren, indem man hinten die "Verschiebeparameter" dazuschreibt


    ( 1 0 0 12 )

    ( 0 1 0 12 )

    ( 0 0 1 -5 )

    ( 0 0 0 1 )


    Hier wird also der Wert für X,Y und Z quasi beibehalten (die drei "1en" in der oben-links eingebetteten 3x3 Matrix) und der Punkt, bzw. die Punkte und damit das gesamte Objekt, um +12 auf der X-Achse und +12 auf der Y-Achse verschoben - also nach rechts oben und zusätzlich um -5 auf der Z-Achse verschoben.


    Und wenn man nun hier aus den 3 oberen "1en" wieder z.B. je eine 2 macht, wird im gleichen Rechenschritt auch noch skaliert, also das Objekt vergrößert um den Faktor 2. Matrizen lassen sich also auch hier wieder kombinieren, bevor (!) man sie mit dem Vektor multipliziert


    ( 2 0 0 12 )

    ( 0 2 0 12 )

    ( 0 0 2 -5 )

    ( 0 0 0 1 )


    Praktisch, als Programm, sieht das dann einfach so aus, daß man sich ein weiteres Array o.ä. für die z-Werte der Punkte anlegt und die Werte der Matrix entsprechend auf 4x4 erweitert sowie die Programmzeilen anpaßt, die die Multiplikationen vornehmen. Dort kommt nun natürlich noch eine Zeile für das Z dazu und in jeder Zeile werden statt 3 Multiplikationen eben nun 4 stehen müssen.


    Dabei sieht man aber schon am obigen Beispiel, daß sich hier möglicherweise noch Vereinfachungen anbieten - die letzte Matrix besteht ja - trotz gleichzeitigem Skalieren und Verschieben - immer noch zu einem großen Teil aus "0" Werten - und das sind Optimierungsmöglichkeiten, weil man die ja evtl. auch einfach auslassen kann (wenn der Rechner sowas nicht schon automatisch wegläßt).

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    2 Mal editiert, zuletzt von ThoralfAsmussen ()

  • Hier mal die "Pseudo-BASIC" Variante.


    Es ist also eigentlich gar nicht soviel anders als "oben".


    Was evtl. auffällt, daß die Plotroutine erstmal einfach genauso weiterbenutzt wird, wie zuvor. Es werden einfach die X und Y Werte dahingeschickt und dort für das Ausgabegerät umgerechnet. Der Z-Wert wird dabei erstmal einfach ausgelassen - was einfach daran liegt, daß der Bildschirm sowieso nur 2D zeichnen kann.


    Allerdings ist das so ein Teil der gesamten Prozedur, der auch sehr aufwändig werden kann - etwa dann, wenn man zusätzlich eine Perspektive in die Ausgabe "hineinrechnet" oder "verdeckte Linien" (hidden lines) wegrechnet oder eine künstliche Tiefenunschärfe haben möchte oder auch nur die Punkte, die weiter hinten liegen dunkler darstellen will. Das alles hat aber primär mit den Weltkoordinaten erstmal nichts zu tun - ist aber letztlich das, was die Bilder so schick aussehen läßt, wie sie es auf 3D Karten sind.


    Wir geben hier also Punkte aus einem 3D Weltenraum aus - alle in gleicher Farbe und so richtig wahrnehmbar 3D-ig wird es erst im nächsten Schritt, dafür aber heftig - beim Rotieren in 3D.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Das Rotieren in 3D bekommt einen extra Abschnitt, weil es recht mühselig ist.


    Dabei ist die Grundidee eigentlich einfach.



    Man macht genau das, was eigentlich so schön ist, nämlich das kombinerte Matrizen-Vereinen, zunächst mal NICHT. Stattdessen zerlegt man das Drehen in drei unterschiedliche Operationen und bekommt es dadurch halbwegs "übersichtlich".


    Man betrachtet nämlich die Drehbewegung jeweils nur um eine bestimmte Achse - man kann daher entweder Drehen um die X-Achse ODER um die Y-Achse ODER um die Z-Achse.


    Wie das geht, haben wir oben bei 2D schon gesehen, dort hatten wir Drehen mittels Sinus/Cosinus in der Matrix - und gedreht wurde immer um den Koordinatenursprung. So ähnlich ist das jetzt auch, allerdings können sich aber die Punkte natürlich nicht nur um den einen Ursprungspunkt verteilen, sondern entlang einer kompletten Achse - und um diese werden sie dann herumgedreht, daher ist das die Drehachse.


    Da es nun 3 Achsen gibt, kann auch jede davon selbst Drehachse sein. Das bedeutet dann auch, daß die Werte die ein Punkt auf ihr hat, beibehalten werden und nur die beiden anderen Bestandteile sich ändern. Wenn also Z die Drehachse ist, werden die Punktwerte für X und Y sich ändern. Das entspricht quasi dem 2D Drehen - nur daß zusätzlich die Z-Koordinate existiert, aber die wird ja nicht angefaßt - oder anders: die Tiefe des Punktes bleibt dabei unangetastet.

    Analog verhält sich das, wenn X die Drehachse ist - dann werden die Koordinatenwerte für Y und Z geändert. Und bei Y als Drehachse ändern sich X und Z Werte des Punktes.


    Die Matrizen dafür sehen dann so aus, daß wieder der Drehwinkel in Sinus bzw. Cosinusform auftaucht.


    Für die Drehung um die Z-Achse


    ( cosinus(winkel) -sinus(winkel) 0 0 )

    ( sinus(winkel) cosinus(winkel) 0 0 )

    ( ______0_______  __________0_____ 1 0 )

    ( ______0_______  __________0_____ 0 1 )



    für die Drehung um die X-Achse


    ( 1_ ______0_______  __________0______ _0 )

    ( 0_ cosinus(winkel) -sinus(winkel) _0 )

    ( 0_ sinus(winkel) cosinus(winkel) __0 )

    ( 0_ ______0_______  __________0_____ __1 )


    für die Drehung um die Y-Achse


    ( cosinus(winkel) __0__ sinus(winkel) _0 )

    ( ______0______  _____1_________ 0_________0 )

    ( -sinus(winkel) _0  _cosinus(winkel) _0 )

    ( ______0_______  __ 0___________ 0 ________1 )



    Die eigentliche Berechnung ist letztlich wieder einfacher, als man so denkt, wenn man das so sieht. Man legt den Drehwinkel fest, berechnet davon Cosinus und Sinus und setzt diese Werte fix an den richtigen Stellen der Matrix ein - bevor man mit der Punkt-Umrechnerei beginnt.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    4 Mal editiert, zuletzt von ThoralfAsmussen ()

  • Toller Einstieg - Vielen Dank für die Mühe, die Du dir da machst!


    Sowas hab ich schon lange gesucht...jetzt muss ich nur noch Zeit dafür finden ;)

  • Danke !


    Es gibt aber durchaus einige sehr gute Einstiegskurse in das Thema 3D - sowohl in Buchform, wie auch im Web.

    Zum Beispiel da, wo der ganze Zauber herkommt (beim zweiten Link die Lecture Slides klicken)

    https://graphics.stanford.edu/courses/

    https://graphics.stanford.edu/courses/cs248-97-winter/

    oder aus DLand in deutsch allerdings sehr kurz und knallig, aber umfangreich

    http://media2mult.uni-osnabrue…08/index.php?n=Main.Trail


    Und, wahrscheinlich sind die auch umfangreicher und exakter als das hier überhaupt werden soll und kann. Hier sollen eigentlich nur ein paar ganz basale Basics gezeigt werden, mit denen man auch auf kleinen, langsamen und alten Rechnern noch bißchen in das Thema reinschnuppern kann. Dabei fällt natürlich auch bißchen - hoffentlich - nützliche "Einsteigertheorie" bei ab. Wenns gut geht soviel, daß man danach ein Ahnung hat, wie das "gemacht" wird und sich auch seine eigene Routine basteln könnte.

    Lesen sollte man das sowieso stückweise, wahrscheinlich wäre es sogar gut gewesen wieder so einen Index"thread" zu machen, das sorgt dann ein bißchen für Struktur - kommt evtl. noch.

    Ansonsten gern auch Anregungen oder Beiträge zum Thema hier mit reinschreiben.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • Wenn man sich das letzte Programm oben genau ansieht, wird man feststellen, daß da ein sehr übler Programmierfehler drin ist. Prinzipiell - also i.P. ;) - ist alles soweit OK, aber das Erweitern der Matrizen hat einen üblen Nebeneffekt gehabt ... Welchen ? Wo ist der Fehler ?



    Topic: beim Drehen ist die Reihenfolge wichtig !


    Wer sich das Programm in der Art und Weise oder ähnlich zusammengebasstelt hat - natürlich ohne den (Schussel)Fehler mit einzubauen - kann die folgende Überlegung auch direkt probieren.


    Es ist nämlich wichtig, sich mal zu überlegen, was passiert, wenn man die Punkte dreht. Da man die Rotationen ja einzeln für jede Achse ausführt, stellt sich die Frage, ob es da egal ist, welche Drehachse man zuerst benutzt und in welcher Reihenfolge die anderen folgen.


    Die Antwort ist : NEIN, es ist NICHT egal !


    Dazu folgende Überlegung



    Man nimmt einen Punkt her, der irgendwo im Weltenraum liegt. Hier ist der Vektor mal als solcher mit eingezeichnet - der Punkt liegt dann links-oben-vorne. Die grauen Linien sind nur Hilfslinien/Schatten zur besseren Orientierung.


    Zunächst wird der Punkt um die Z-Achse gedreht. Hier um 180 Grad. Nun liegt der Punkt links-unten-vorne.

    In einer weiteren Drehung wird um die Y-Achse rotiert. Ebenfalls um 180 Grad. Danach landet der Punkt rechts-unten-vorne.


    Die Überlegung ist nun: An welcher Stelle findet sicher Punkt, wenn man die Drehungen genau in der anderen Reihenfolge macht ? Also erst um die Y-Achse und danach um die Z-Achse ?



    Das Ergebnis vorweggenommen: Er landet woanders ...

    und das bedeutet natürlich: es ist absolut nicht egal in welcher Reihenfolge man seine Punkte rotiert !


    Da hört ich jetzt krass an, aber so schlimm ist es dann wieder nicht. Man muß halt nur - zumindest, wenn man ein vorhersagbares Ergebnis bei einer Widerholung haben will, mal irgendwann festlegen, in welcher Reihenfolge man die Rotationen macht.

    Der übliche Modus dafür ist: erst X-Achse, dann Y-Achse, danach noch Z-Achse.


    Es ist aber keine sklavisch einzuhaltende Regel, aber innerhalb eines Programmes sollte es schon konsistent sein und daher immer mit der gleichen Reihenfolge geschehen.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Hier noch die BASIC Bastelei von oben - in der Plus/4 Variante. Bereits "bereinigt" und mit einem kleinen Zusatz - die Matrix, die am Anfang festgelegt wird, läßt sich per GOSUB auswählen ( 700 um-Z, 730 um-X, 760 um-Y ), was man in einer Zeile ganz zu Beginn ändern kann.

    Beschleunigen wieder per Speed-Modus der Emulation oder alternativ durch Verringerung der Punktzahl - z.B. PMAX=32 statt 64.

  • Topic: einfache Bilder mit wenigen Punkten - das Drahtgittermodell


    Es ist zwar eine schöne Sache einzelne Punkte im Raum verändern zu können, allerdings wird das SEHR rechenaufwändig, wenn man damit komplette Bilder oder Figuren darstellen will. Man sieht das schon sehr schön auch an dem Plus/4 Progrämmchen, was i.P. mit einer recht kurzen, simplen Linie den Rechner gewaltig in die Knie zwingt, wenn diese komplett aus einzelnen Punkten aufgebaut ist, die alle "umgerechnet" werden müssen.


    Um diesen Vorgang ein wenig zu beschleunigen, gab es relativ schnell die Variante, daß man nur einige wenige wichtige Eckpunkte berechnet und die dazwischenliegenden Verbindungen mit einer normalen Linie füllt, die einfach als 2D Linie gezeichnet wird. Am Ende sind von den angezeigten Punkten dann nur einige Prozent, wenn überhaupt, durch die 3D Berechnung gegangen - der Rest ist normale 2D Grafik. Vorteil: aus geht VIEL schneller. Außerdem kann man evtl. - mit einem Blitter o.ä. - das Linienzeichnen bereits hardwarebeschleunigt machen.


    Dazu benötigt man lediglich einen kleinen Datensatz an "Stützpunkten", die in die 3D Berechnung gehen. Und, da eine Linie ja schließlich einen Anfangs- und einen Endpunkt hat, muß man eine Art Liste führen, wo man die zu zeichnenden Verbindungen notiert.

    Am einfachsten trägt man dort Anfangs-und-Endpunkt hintereinander ein oder macht alternativ eine Struktur, wo man für Anfangs- und Endpunkte jeweils eine extra Liste führt.


    Zum Zeichen benötigt man dann lediglich


    - die jeweils neu berechneten Stützpunkte in der 3D Welt

    - einen Index in die Linienliste(n) mit den Anfangs-/Endpunkten mit dem diese durchlaufen wird, und an den passenden Stellen die Linie gezeichnet wird


    Letzteres bedeutet, daß man in der Bildausgaberoutine etwas ändern muß, da nun Linien statt Punkte dargestellt werden sollen.


    Code
    DEF PROC AbbildungMitLinien (xweltl1, yweltl1, xweltl2, yweltl2)
    var xl1,yl1, xl2, yl2, xbildl1, ybildl1, xbildl2. xbildl2
     xl1 = xweltl1 DIV 4 : yl1 = yweltl1 DIV 4
     xl2 = xweltl2 DIV 4 : yl2 = yweltl2 DIV 4
     xbildl1 = xl1 + (halbe-bildschirmbreite-der-aktuellen-auflösung)
     ybildl1 = yl1 + (halbe-bildschirmhöhe-der-aktuellen-auflösung)
     xbildl2 = xl2 + (halbe-bildschirmbreite-der-aktuellen-auflösung)
     ybildl2 = yl2 + (halbe-bildschirmhöhe-der-aktuellen-auflösung)
     LINE(xbildl1,ybildl1,xbildl2,ybildl2)
    ENDPROC


    Je nach Rechenleistung, kann man hierbei dann schon auch darüber nachdenken, diese Linien unterschiedlich zu gestalten, z.B. nach bestimmten Kriterien einzufärben oder Dickenänderungen vorzunehmen. Spätestens dann ist man aber bei einem kleinen 8Bit Rechner wohl an der Grenze angekommen, die, zumindest in einer Hochsprache, erreichbar ist.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries


  • Und so sieht das dann aus. 8 Eckpunkte und das Linienzeichnen macht der BASIC Interpreter selbst.

    Programm ist angehängt.


    In PseudoBasic liest sich das dann ungefähr so ( Hauptprogramm oben, zwei kleine Prozeduren einzeln ) :





    Die Punkt- und Linien-"Generierung" ist in zwei Prozeduren ausgelagert. Ansonsten ist der Unterschied hauptsächlich nur, daß nun vor dem Zeichnen in einer Datenliste nachgeschaut wird, bei welchem Punkt eine Linie beginnt und bei welchem sie endet - und diese Linie wird dann mit der leicht modifizierten AbbildungMitLinien Prozedur gezeichnet.


    Das eigentliche Umrechnen der Punkte in der "Punktwolke" wird direkt unverändert weiterverwendet.

    Die Linien zeigen mit ihren Einträgen einfach direkt auf die "Nummern" der Punkte in der Punktwolke.



    Wenn man jetzt an der Stelle noch ein bißchen eine schöne Eingabe-/Ausgabe für die Punkte und Liniendaten drumherumbaut und die Größe der Arrays erhöht, ist man schon fast da, wo die ganzen DOS 3D Drahtgittermodellprogramme so sind.


    Schwieriger als reine die Drahtgitter-Darstellung ist dann meist der Editor, um die Daten einzugeben - insbesondere, wenn man das grafisch gestützt direkt am Bildschirm machen können möchte.

  • Ab hier - Drahtgittermodell - eröffnen sich dann auch relativ einfache Varianten, das Ganze "hübscher" zu gestalten. Wie schon angesprochen sind das v.a. Farbe und Linienstärke (oder evtl. auch Linienmuster (z.B. Strichpunkt)).


    Dabei ist die Farbe relativ einfach oder auch in Abhängigkeit von Parametern einstellbar.

    Zunächst mal benötigt man für "fixe" Farben eine weitere Datenstruktur für Linien, z.B. DIM linienfarbe[linienmax] , in die man für jede Linie eine zugehörige Farbe hineinschreibt. Vor dem eigentlichen Linienzeichnen wird damit der Farbwert des Plots festgelegt. So kann man etwa "Rückflächen" in Grün und "Vorderflächen" in Blau zeichnen - oder bei CGA in Magenta vs. Black.


    Interessanter ist allerdings einen Parameter zu benutzen anhand dessen man die Zeichenfarbe bestimmt - das geht auch ganz gut ohne so ein Farbarray, aber es schadet auch nicht.


    Die wohl interessanteste Variante ist, auf diese Art im Vordergrund kontrastreiche und satte Farben zu benutzen und für weiter hinten liegende Linien eine weniger aufdringliche Farbe zu nehmen. Bei Graustufen z.B. vorn sattes Schwarz und nach hinten werdend hellere Grautöne. Funktioniert sehr gut auf 256 Graustufen Karten (z.B. VGA mit 256er Paletten, die man selbst einstellen kann).


    Dazu muß aber natürlich in jedem Durchgang - also nach jeder Umrechnung der Weltkoordinaten - bestimmt werden, in welcher Tiefe die Linien sich aktuell gerade befinden. Und die einfachste Variante dafür ist eine Mittelwertbildung der Tiefenwerte - man rechnet daher für jede Linie

    PZmitte = ( PZ(linienstart) - PZ(linienende) ) DIV 2

    aus und vergibt dann nach einem passend zu den Koordinaten des jeweiligen Modells gewählten Schema Farb- bzw. Graustufenwerte, z.B. mit

    Linienfarbe = ( ( PZmitte + 100 ) * 256 ) / 200

    wenn man eine Z-Achse von -100 bis 100 benutzt. (Dabei muß aber sichergestellt sein, daß kein Punkt diesen Bereich bei einer Drehung o.ä. überschreitet.)

    Oder man vergibt die Werte direkt über selbstgewählte Wertebereiche - z.B.

    IF (PZmitte>20) Linienfarbe=Blau ELSE IF (PZmitte>40) Linienfarbe=Hellblau ELSE Linienfarbe=Schwarz



    Ähnlich funktioniert das mit der Strichdicke, z.B. kann man abfragen

    IF (PZmitte<40) THEN zeichne-Linie-erneut-aber-diesmal-mit-( xbild1+1, ybild1+1 nach xbild2+1, ybild2+1 )


    Besser ist natürlich, wenn man direkt die Linienstärke einstellen kann, das sieht dann deutlich schöner aus - bei der Variante hier, sieht man oft diese Doppellinien, dafür klappt das quasi überall.



    Wichtig ist: all diese Zusätz geschehen quasi in der Zeichenroutine oder kurz davor, also doch schon bereits "außerhalb" der Punktweltberechnung.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • kleine Fehlerkorrektur: man muß natürlich auf den Z-Wert der Mitte der Linie noch den Endpunkt addieren, um wieder eine sinnvolle Koordinate zu erhalten, nämlich die Mitte:

    PZmitte=( punktz(linienstart) - punktz(linienende) ) DIV 2 + punktz(linienende)


    Das Plus/4 Programm zum Ansehen hängt an, mit Doppellinien für doppelte Strichdicke (mangels Farbe oder Graustufen).





    Topic: Schrägprojektion mittels Matrix, für die Displaydarstellung


    Eine Sache muß man aber zu dem Bild sagen: Daß der Wuerfel so schön in den Raum gekippt ist, liegt noch nicht wirklich an der Darstellung. Es sind hier einfach die Koordinaten leicht versetzt für die vordere Fläche des Würfels eingetragen - der Würfel steht also bereits schräg in den Weltkoordinaten drin.


    Da wir ja bisher einfach entlang der Z-Achse auf das Objekt schauen, würden sich z.B. ein vorderer oberer linker Eckpunkt bei (50,50,-50) und ein hinterer oberer linker Eckpunkt bei (50,50,50) bei solch einer geraden Draufsicht ja überlappen und man würde nur einen Punkt an dieser Stelle sehen. Da das Gleiche auch für die anderen 3 Ecken gelten würde, wäre die Darstellung (wenn man nicht vorher um Y-Achse oder X-Achse rotiert) einfach ein Viereck - und das würde bei Rotation um die Z-Achse auch so bleiben.


    Um daher mit einem 2D-Display in einen "Raum" zu sehen, muß man sinnvollerweise irgendeine Art von Projektion wählen, die einem quasi eine Tiefe so aufbereitet, daß das menschliche Auge sie auch als solche wahrnehmen kann.


    Die bekannteste ist wahrscheinlich die Zentralprojektion.


    Die am einfachsten zu berechnende ist wohl die Schrägprojektion.


    Bei der Schrägprojektion passiert eigentlich nicht viel Aufregendes - es wird lediglich das Bild leicht nach X und gleichzeitig nach Y versetzt, und zwar umso weiter je weiter der dargestellte Punkt in der Tiefe des Bildes liegt.

    Es wird also in Abhängigkeit von Z auf X und auf Y ein bestimmter Betrag addiert, der umso größer ist, je größer das Z wird.

    Normalerweise wird für X und Y einfach der gleiche Betrag benutzt, womit das Bild also um 45 Grad versetzt wird - und normalerweise so, daß tiefere Bildteile weiter rechts und höher liegen.


    Effekitv addiert man daher zur X-Koordinate noch ein Z was man mit einem Faktor versieht, z.B. 0.4

    X=punktx + 0.4 * punktz

    und für Y genauso

    Y = punkty + 0.4 * punktz


    Der Faktor bestimmt dabei die Stärke der "Verzerrung".

    Nimmt Z negative Werte an, d.h. i.a. zum Betrachter hin, dann erfolgt ein Versatz nach links-unten, bei positiven Z Werten wandert der abgebildete Punkt ein Stück nach rechts-oben.



    Da man das nur für die Anzeige benötigt, ist sicherlich einsichtig, daß man dabei nicht auf den "Weltkoordinaten" rechnet, d.h. das Ergebnis nicht(!) in diese zurückschreibt, sonst würde man nämlich deren Inhalt massiv verändern. Stattdessen geschieht das zwar mit den Weltkoordinaten, das Ergebnis geht dann aber quasi direkt an die Abbildungsroutine.


    (Prinzipiell ist es natürlich auch möglich, erst die bereits für das Display aufbereiteten Bildschirmkoordinaten auf die Art zu verändern, dafür müßte man aber die Information über die Tiefe "mitschleifen", weshalb es sich anbietet, die Projektion vor dem Berechnen der Displaykoordinaten zu machen.)


    Auf kleinen Rechner kann man das auch einfach dadurch erledigen, daß man zu jedem Punkt den Z Wert abfruft, mit dem gewünschten Faktor multipliziert (am besten geshiftet) und dann auf den X und den Y Wert direkt addiert.


    Wenn man dagegen mit Matrizen weiterarbeiten möchte, ist das quasi die Gelegenheit, diese erstmals "außerhalb" der Punktwolkenrechnerei anzuwenden. Im Normalfall wird das auch so gemacht, insbesondere natürlich dann, wenn eine Grafikkarte vorhanden ist, die das Ganze in Hardware erledigt.

    Es hat aber auch noch ein paar andere Vorteile, z.B. läßt sich die Art der Projktion so relativ leicht wechseln und es erlaubt etwa zusätzliche Verschiebungen des Bildes zu machen oder auch ganz kurz vor dem Eigentlichen Bildaufbau noch eine Skalierung des Gesamtbildes zu machen.



    Man benötigt dafür wieder eine Matrix - da man nuneinmal bei 4x4 gelandet war, benutzt man die Größe auch gleich wieder. Es sollte nur klar sein, daß damit eben nun NICHT in der Punktwolke gerechnet wird, nur mit deren Daten.


    Für eine einfache Schrägprojektion müssen also X und Y erhalten bleiben, und auf jedes wird ein bestimmter Anteil vom Z-Wert addiert, daher sieht also die Matrix ungefähr so aus


    ( 1 0 0.4 0 )

    ( 0 1 0.4 0 )

    ( 0 0 _1 _0 )

    ( 0 0 _0 _1 )


    Der Faktor ( 0.4 ) ist der Wert, den man anpaßt bzw. verändert.

    Wie man sieht, steht ja sonst eigentlich kaum was drin, weshalb es in der Form eben fast nur Sinn macht, wenn die Matrixmultiplikation per Hardware gemacht werden kann.


    Damit ist der Ablauf also jetzt folgender:


    Punkt aus dem Weltmodell -> Matrixmultiplikation für die Umrechnung auf neue, z.B. skalierte oder rotierte, Position -> zurückschreiben ins Weltmodell -> Werte für Linienanfang und Ende anhand der Linienliste heraussuchen -> evtl. eine Tiefenposition der Linie bestimmen und den Farbwert/Grauwert/Dickenwert festlegen -> die beiden Punkte (Linienanfang/-ende) mit der Projektionsmatrix multiplizieren -> die errechneten Werte an die Abbildungsroutine weiterreichen -> Bild der Linie zeichnen -> Endergebnis: gefärbtes, schrägprojeziertes Drahtgittermodell.


    Hier noch der BASIC Pseudocode - neue Projektionsmatrix oben, zusätzliche Berechnung direkt vor dem Aufruf der Abbildungsprozedur



    Plus/4 Programm hängt an,

  • Wer sich jetzt für das Thema "Perspektive" erwärmen konnte, dem sei die WikiP Seite dazu sehr ans Herz gelegt.


    Hier kommt jetzt nur noch eine Form, nämlich eine Kurzvariante der Zentralprojektion.


    Wie der Name sagt, ist hier die Idee, daß man von einem Zentrum aus die Umgebung, die Welt, das Datenobjekt, betrachtet. Dabei wird von diesem Zentrum ausgehend geschaut, wie der Strahlenverlauf von vorgestellten Lichtstrahlen wäre, um auf kürzestem Weg dieses Zentrum und damit das dort befindliche "Auge" des Betrachters zu erreichen. Stellt man nun in diesen Weg eine transparente Leinwand auf, dann kann man dort bestimmte wichtige Punkte der Szenerie markieren - das sind in unserem Fall dann einfach die Punkte aus der Datenwelt (Punktwolke) - und so ein Abbild erstellen. "In natura" erscheinen dabei weit entfernte Objekt verkleinert - und genau diesen Effekt bemüht man sich also ebenfalls zu erreichen.


    Das bedeutet automatisch, daß die zu erzeugenden Bildpunkte in irgendeiner Form von der Tiefe abhängig sein werden - und damit von punktz() für jeden Punkt - und damit, daß wieder keine statische Matrix oder Berechnung möglich sein wird, da die Punkte ja bei jeder Neuberechnung innerhalb der Weltdaten ihren Z-Wert ändern können.


    Desweiteren kann man nicht einfach die Punktkoordinaten X,Y direkt übernehmen, denn dann hätte man ja einen parallelen Strahlenverlauf - es geht aber gerade darum die "Abbildungslinien/-strahlen" auf eine "Zentrale" zulaufen zu lassen.


    Prinzipiell geht es dann dabei um schon relativ komplizierte Mathematik ( projektive Geometrie ), aber diese kann man mit ein paar Voraussetzungen wesentlich vereinfachen.


    Zum Ersten schaut man einfach "per definitionem" direkt vom Nullpunkt (Koordinatenursprung) aus auf die Szene. Damit das klappt, muß man vorher alle seine Punkte, bzw. die, die man sehen möchte, dorthin verschieben, d.h. den Z-Wert entsprechend aufaddieren. Ob man dabei in Richtung der negativen Z-Achse schaut oder in Richtung der positiven Werte auf der Z-Achse ist eigentlich egal, muß aber evtl. später beim Vorzeichen berücksichtigt werden. "Hier" schauen wir erstmal "positiv".

    Und dann legt man noch fest, daß man möchte, daß die Bildebene parallel zur Y-Achse und zur X-Achse aufgespannt ist - sie also nicht quer oder schief im Raum liegt sondern schön ordentlich gerade aufrecht steht und exakt 90Grad zur Z-Achse liegt - wie man das beim Bildmalen ja vermutlich auch machen würde.

    Damit vereinfacht sich das Ganze zu so einer Art einfacher Strahlensatzanalogie, die da heißt , daß bestimmte Abstandsverhältnisse auf einer Achse (Z) genau in der Art auf den anderen Achsen auch wiederzufinden sind - und damit kann man dann die Z-Werte benutzen, um für die 4 Quadranten des Abbildes die Bildpositionen der Punkte zu bestimmen.





    Diese künstlerisch hochwertige Bild, soll das ein bißchen demonstrieren. Die roten Punkte sind die Originale und die blauen auf den Linien bei Z1 sind die Abbilder. Interessant ist jetzt, daß man daraus indirekt die Veränderungen für X und Y bestimmen kann und daher die Punkte auch bei Aufsicht von vorn (BIld links oben) korrekt verzerren kann.


    Allerdings ist dabei wichtig, daß kein Punkt während des Berechnens der Abbildung von seinem Quadranten in eine gegenüberliegenden wandert. Wer mag kann gerne ausprobieren, was da passiert - i.a. zerreißt es das Bild zur Unkenntlichkeit. Kurz: Punkte und ihre Abbilder sollen in ihrem angestammten Quadranten bleiben (X,Y) und daher keine Vorzeichen wechseln. Zudem wandern sie alle gleichsinnig in der Abbildung zum Koordinatenursprung hin bzw. weg, je nachdem, wo an welcher Position sich die Bildebene (Z1) befindet.


    Aus all dem ergibt sich dann die Abbildungsmatrix für das Umrechnen in Bildkoordinaten:


    ( 1/(1+ punktz()/LängedesBereiches) ___0 0 0 )

    ( 0 ___1/(1+ punktz()/LängedesBereiches) 0 0 )

    ( 0 ________________________________________0 1 0 )

    ( 0 ________________________________________0 0 1 )


    wobei LängeDesBereiches der Raum über der Z-Achse ist, in dem alle darzustellenden Punkte liegen, z.B. von 0 bis 200 -> 200.


    Liegen Punkte im Negativen, müssen diese, s.o., vorher in positive Bereiche verschoben werden (Stichwort Translation).


    Diese Matrix ersetzt einfach die für die Schrägprojektion im Programm. Dabei müssen aber zumindest AD und FD für jeden Punkt jeweils vor einer Matrixberechnung aktualisiert werden - am Besten direkt nach der Ermittlung der linienfarbe, d.h. bei Zeile 51 für den ersten Linienpunkt und bei Zeile 57 für den zweiten.


    Da diese Faktoren mit den Werten (X,Y,Z,W) des jeweiligen Vektors (Punktes) multipliziert werden, erhält jede Koordinate aus einem Bereich mit (-X,-Y) Werten automatisch auch zwei Minusvorzeichen in ihren Bildwerten (-BildX,-BildY) und daher verbleibt der Punkt in seinem Quadranten. Sichergestellt wird das dann v.a. durch das ( 1+ ... ) als Divisor unter der 1/ ... ; der Quotient sollte also um 1 herum schwanken, oder auch sehr groß werden können, aber niemals negativ.


    PS: Prinzipiell kann die Leinwand natürlich auch hinter dem Gegenstand aufgebaut sein (Position Z2) und man bekommt dann auch eine Abbildung aller Punkte des Objektes, was dann bei (Z1) stehen muß.


    PS2: Die ( 1/ ... ) kann man natürlich bei Bedarf anpassen und einfach als Vergrößerungsfaktor benutzen, z.B. eine "2" oder "3" daraus machen. Kommt halt drauf an wie die Punkte den vorhandenen Daten"raum" ausnutzen.



    Hier noch ein paar Bildchen aus dem Beispielprogramm, was wieder anhängt.


       






    Zusatz :

    Es gibt natürlich auch noch weitere Perspektiven, manche funktionieren auch nur für bestimmte Ansichten, z.B. Frontal oder überleben bestimmte Rotationen nicht, sehen dafür aber COOL aus und sind daher für Demos extrem geeignet. Bspw. eine Version, wo X und Y von Z abhängen (CD, GD) nach X+Faktor*Z*SGN(X) bzw. Y+Faktor*Z*SGN(Y), also der Z-Wert mit dem jeweils richtigen Vorzeichen für den jeweiligen Quadranten auf X bzw. Y aufaddiert wird.


       

  • Topic: auf die Szene schauen - die (virtuelle) Kamera


    Eine interessante Eigenschaft dieser Art der Berechnung hier (Matrizen), läßt sich ganz fabelhaft "mißbrauchen". Wir haben eben für die letzte Projektion ein paar Vereinfachungen angenommen, insbesondere die Werte auf der Z-Achse alle in positive Wertebreiche verschoben und definiert, daß der Betrachter im Punkt (0,0,0) sitzt und in Richtung Objekt sowie Abbildung schaut, und zwar entlang dieser Z-Achse.

    Wenn man nun von einer anderen Seite oder einem anderen Punkt (etwa von links-schräg-unten) auf die Szenerie schauen möchte, ist das während der Perspektivenberechnung so ohne Weiteres nicht möglich. Aber: Man kann das ja vorher erledigen !


    Die Idee dazu ist nun folgende: der Beobachter - und der heißt ab jetzt auch einfach: die Kamera - kann ja prinzipiell von irgendwo auf den Datensatz schauen wollen. Da aber die Daten am Anfang in irgendeiner Form geordnet und so gestaltet vorliegen, daß man gewissermaßen in Startposition von vorne auf den Punktraum / die Welt schaut, ist es am Einfachsten genau diese Position zu benutzen und evtl. - insbesondere wenn man nur einige wenige Objekte, wie z.B. einen Würfel hat - einfach das Objekt in seinen Startkoordinaten so aufzuschreiben, wie man das gern haben möchte (z.B. linke Ecke nach vorn oder eine Fläche 30Grad schräg nach oben); man selbst ist ja schließlich "Herr über die Daten" ( oder natürlich "Frau", je nachdem - in dem Fall dann eher "Herrin" ).


    Bei großen "Datenmassen" an Objekten etc. wird das aber schnell komplex, und daher möchte man evtl. die Kamera einfach von einer beliebigen Position auf die Szene schauen lassen können



    und das bedeutet natürlich letztlich, daß man die Positionen aller (!) Objekte / Punkte relativ zur Kamera verändert, wenn man den neuen Ort der Kamera quasi als (0,0,0) der Betrachtung der Szene ansieht. Das bedeutet - es müssen alle Punkte einmal umgerechnet werden, um angepaßt zu werden.


    Es gibt nun die interessante Eigenschaften von solchen Matrizenberechnungen, daß man, wenn man irgendetwas mit einer Matrix multipliziert hat, die Ursprungswerte aus dem Ergebnis wieder herstellen kann. Dazu benutzt man die sogenannte "inverse Matrix". Wenn man nun die zuvor mit der normalen Matrix errechneten Ergebniswerte mit der "inversen Matrix" multipliziert, erhält man wieder die Ausgangswerte.

    Das klappt nicht immer, insbesondere kann man nicht für jede Matrix eine "inverse Matrix" bilden, aber i.a. klappt es.


    Was macht man also ? Man "überlegt" sich eine Kameraposition, was im Normalfall einfach die (0,0,0) ist und dann führt man damit alle Matrizenoperationen durch, die die Kamera an die gewünschte Position bringen. Die Kamera wird also genau wie ein Objekt aus der Datenwelt behandelt und dahin gebracht, von wo sie schauen soll. Die dafür nötige Matrix notiert man sich.

    Wenn dabei mehrere Einzelbewegungen auftreten, faßt man diese in einer Matrix zusammen - etwa Translationen zum Verschieben der Position oder man multipliziert beide dafür nötige Matrizen, wenn man zwei Rotationen hintereinander ausführen muß, um an die Wunschposition zu kommen.


    Würde man nun von dieser "Kamerabewegungsmatrix" die "inverse Matrix" bilden und mit den errechneten Kamerakoordinaten multiplizieren, erhielte man den Startwert der Kamera wieder.


    Und jetzt kommt der Trick: Statt sowas Seltsames mit der Kamera zu machen, wird diese inverse Matrix direkt auf die gesamte Punktwelt, d.h. alle anderen Objekte, angewandt. Das ist dann gewissermaßen als stünde die Kamera in ihrer ersten Position fest und die komplette Welt und alle ihre Punkte würden so gedreht und verschoben, daß die Kamera letztlich dort ankommt, wo man sie gern hin haben wollte.


    Wo bekommt man nun die inverse Matrix her ?

    Die Antwort lautet natürlich: aus dem Internet !


    http://www.arndt-bruenner.de/mathe/scripts/inversematrix.htm


    (kann man natürlich auch selber rechnen, ist aber relativ aufwendig und fehlerträchtig; hier zum Zusehen)



    Beispiel: Die Kamera soll um 120 Grad um Y rotiert werden und auf Höhe 40 befestigt werden. Sie schaut dann natürlich in dieser Höhe geradeaus und nicht mehr auf Höhe 0 Richtung Koordinatenursprung.


    Die Matrix für die Kamerabewegung dorthin wäre


    ( ___-0.5 ________0 _____0.866025 ______0 )

    ( _____0 _________1 _________0 _________40 )

    ( -0.866025 _____0 _______-0.5 _________0 )

    ( _____0 _________0 _________0 ___________1 )


    mit -0.5 = cosinus(120 Grad) und 0.866025 = sinus(120 Grad).

    Und die Webseite oben rechnet davon diese inverse Matrix aus


    ( ___-0,5 _____0 __-0,86603 ___0 )

    ( _____0 ______1 _______0 ____-40 )

    ( 0,86603 ________-0,5 _____0 )

    ( _____0 ______0 _______0 ______1 )


    Diese zweite Matrix muß man nun auf alle Punkte in seiner Punktwelt einmal am Anfang anwenden, wenn man die Kamera um 120 um die Y Achse gedreht und in Positon 40 "Höhe" installieren will. Dafür werden einfach alle Punkte einmal damit durchgerechnet - was exakt mit dem gleichen Ablauf gemacht werden kann, mit dem man danach dann die reinen Punktberechnungen ausführt (d.h. ohne die zusätzlichen Verzerrungen für Perspektiven und Bildschirmabbildungen).


    Ab dann schaut die Kamera von dort aus auf die Szenerie - denn die gesamte (Punkt-)Welt hat sich gedreht.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    2 Mal editiert, zuletzt von ThoralfAsmussen ()

  • Topic: wie die Welt entsteht


    Um die Weltkoordinaten zu erstellen, kann man ganz einfach seine Daten (z.B. die Punktkoordinaten des Würfels) in die Punktwelt hineinschreiben. Man hat dann eine fixe Welt mit den eingeschriebenen Punkten und kann darauf etwas tun. Allerdings ist das ein relativ unflexibler Ansatz, da man ja z.B. immer die komplette "Welt" abspeichern müßte, wenn sich nur eine Kleinigkeit ändert.

    Und auch gedoppelte Objekte müßten immer wirklich zweimal in Datenform vorhanden sein. Man stelle sich eine FarCry Wiese vor - mit vielen hunderttausend Gräsern, die alle einzeln abgespeichert werden müßten. Streng theoretisch kann man das natürlich machen (und es würde auch der echten Natur mehr entsprechen) aber praktisch sprengt man damit auch noch heutige Rechner (RAM, HDD etc.).


    Um das Problem einfach handhabbar zu machen, wird den Weltkoordinaten noch ein weiterer Datenraum gewissermaßen "vorgeschaltet". Dieser wird nur zum Aufbau der Szene benutzt und hat außerdem den großen Vorteil, daß man in ihm die Objekte einzeln bearbeiten kann. Die Rede ist von sogenannten: Modellkoordinaten.


    Dazu werden die Einzelobjekte, die später in der Welt erscheinen sollen, in ihrem jeweils eigenen Koordinatensystem abgespeichert und zwar jedes Objekt in einem jeweils eigenen. Damit einher geht i.a., daß man damit für die Objekte auch einzelne Dateien erhält, die man nun in externen Editoren etc. weiterbearbeiten kann.


    Das funktioniert natürlich nur, weil man die Modellkoordinaten beim Einsetzen der Objekte in die Welt in Weltkoordinaten überführen kann. Und das macht man wieder genauso, wie man die Weltkoordinaten in neue Positionen oder Größen umrechnet - man wendet Transformationen auf die Modellkoordinaten an und erhält dann die entsprechenden Weltkoordinaten. Gleiche Mathematik - gleicher Alghorithmus - gleiche Prozedur benutzbar - und einer Hardware ist das auch egal, welche Daten da grad' umgerechnet werden.



    Dazu weiß man dann einfach die Position, an der das Objekt in der Welt sein soll und i.a. benötigt man zumindest noch eine Skalierung, um das Objekt von seinen Modellkoordinaten auf die geplante Größe in der Welt umzurechenen.


    Um das alles ein wenig zu vereinfachen, ist es keine schlechte Idee, den Datenraum des Modells auf positive Koordinaten einzuengen - man entwirft also das Modell nur in (+X,+Y,+Z); das ist aber keine notwendige Bedingung.

    Eine andere relativ übliche Variante ist, den Nullpunkt des Modellkoordinatensystems in die Mitte oder den Schwerpunkt des Modells, also des jeweils einzelnen Objektes, zu legen.


    Natürlich kann man während der Transformation in Richtung Welt das Modell, bzw. seine Koordinaten, auch noch rotieren und spiegeln und scheren und wer weiß was noch alles.


    Seitdem es eine ganze Computerspieleindustrie gibt, entstand dadurch quasi eine komplett neue Tätigkeit - Menschen, die sich damit beschäftigen in liebevoller Kleinarbeit Modelle in Koordinatenform zu entwerfen und optisch mit dem letzten Feinschliff zu versehen, auf daß diese in irgenwelchen Games, die oft von solchen Designern gar nicht gepielt werden (weil die eher ganz real ins Kino oder Restaurant oder an den Badesee gehen), die Herzen aller Drachenjäger etc. erfreuen.



    Die Daten nehmen also jetzt bereits folgenden Weg :


    viele Objektdateien -> Modellkoordinaten -> Weltkoordinaten (Szene) -> Kamera umpositioniert -> umgerechnete und aufbereitete Weltkoordinaten -> Perspektive -> Bildschirmkoordinaten -> Darstellung

    .

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    3 Mal editiert, zuletzt von ThoralfAsmussen ()

  • So, nunmal noch die Bastelei mit den Modellen. Umgestellt auf QBasic und daher auch in DOSBox lauffähig. Mithin dann auch per se ein bißchen flotter.

    ( .exe hängt als ZIP an )


    Objekte legt man hinter einem label: an und liest sie von da aus ein. Einmal vorhanden, kann man sie dann durch die "SUB modellkopie" vervielfältigen und damit rumspielen. Ist momentan noch ein wenig rudimentär, aber tut.



  • und die Prozeduren dazu

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • Topic: Linien sortieren - vor allem in der Tiefe


    Wenn man sich das letzte Bild so ansieht, fällt da zweierlei auf

    • bei mehreren Objekten, die wild durcheinander und übereinander liegen, kommt es zu Überschneidungen, was recht ungewohnt aussieht, zumindest für ein "natürliches" Bild
    • man kann auch bei Einzellinien nicht sagen, welche vorn und welche hinten liegt, das Problem würde sogar noch verschärft werden, wenn da jetzt ein Linienfarbalgorithmus drüberlaufen würde - z.B. mit 16 oder 256 Graustufen für die Tiefeninformation

    und in der bewegten Darstellung

    • bemerkt man ein Flimmern, was nichts mit der Gesamtgeschwindigkeit zu tun hat



    Das Flimmern beseitigt man genauso, wie man es von 2D Animationen her gewohnt ist. Da es einfach durch direktes Zeichnen und anschließendes direktes Löschen des Bildschirmspeichers entsteht, ist die beste Lösung dafür ein sogenanntes Double-Buffering. Dazu wird das Bild zunächst in einem nicht angezeigten Bereich aufgebaut und erst wenn es komplett fertig ist, wird es auf den Bildschirmspeicher "umkopiert".

    Nachteil: Man benötigt zusätzlichen freien Speicher in der Größe des Bildschirmspeichers.



    Die Sache mit den Linien dagegen ist komplexer. Da kommt es sehr darauf an, was man erreichen will. Die einfachste Lösung - und nur die wird hier gezeigt - ist, daß man die Linien nicht in der Reihenfolge zeichnet, wie sie im "Linienspeicher" aufeinander folgen. Stattdessen sortiert man sie vorher noch um und zwar so, daß man später beim Zeichnen mit der hintersten Linie beginnt. Noch korrekter wäre: Innerhalb eines Objektes die Linien nach Tiefe sortieren und zusätzlich die Objekte mit einer Tiefeninformation versehen, das vermeidet Artefakte.


    Zumindest für "gefärbte" Linien verhindert man so schonmal ganz gut, daß eine "für hinten" gefärbte Linie oberhalb einer "für vorne" gefärbten zu liegen kommt.


    Problem dabei: Es erfordert a.) wieder mehr Speicher und b.) v.a. noch mehr Rechenzeit



    Die Lösung selbst ist, relativ einfach. Da wir ja sowieso schon die Linienmitten für die Tiefenwerte berechnen - für das Verdoppeln der Linien im Vordergrund bzw. das Einfärben - kann man die Berechnung auch vor der "Zeichenschleife" machen und die Mitten "merken". Anschließend - und das ist nun zusätzlich - sortiert man eine Liste, die in sortierter Form die Linien von hinten nach vorn angeordnet enthält.


    Also hier z.B. ein weiteres Array für die Linienmitten und eines für die Sortierung mit

    Code
    DIM SHARED linienmitte(linienmaxdim), liniensortiert(linienmaxdim)

    anlegen und dann vor der Zeichenroutine (bei Zeile 92 im Listing) die Linien einsortieren


    Natürlich muß dann die Zeichenroutine der Linien dieses sortierte Array durchlaufen und die Linien der Sortierung nach aufrufen. Direkt vor Zeile 94 müßte also die aktuelle sortierte Linie ausgelesen werden, z.B.

    S=liniensortiert(U)

    und die nachfolgenden Laufvariablen "U" innerhalb der Zeichenschleife durch "S" ersetzt werden, d.h.

    la = linienstart(S)

    le = linienende(S)

    liest jetzt die vorsortierten Linien aus.


    Für die Linienmitten benutzt man direkt die Werte aus dem bereits berechneten Array, linienmitte(S).



    Warum so ein Exkurs ins Linien-Sortieren ?


    Es lohnt sich innerhalb der Liniensortierung ein wenig "herumzuspielen", weil man das Ganze bei Flächen und Objekten und Farben und Lichtern und ... eigentlich ganz ähnlich macht. Allerdings sortiert man dort dann Normalenvektoren oder Z-Werte - und da ist das hier doch deutlich anschaulicher.


    Das Sortieren selbst ist dann wieder ein ganz eigenes Thema, ich sage mal nur "BubbleSort" oder "QuickSort" - und wer das noch nie gehört hat, bemühe bitte Google und YouTube oder gehe gleich (Empfehlung!) nach

    http://www.bleeptrack.de/tutorials/sortieralgorithmen



    Für das Drahtgittermodell gäbe es durchaus z.B. noch die Option, die Linien im Hintergrund zu bestimmen, die von Flächen/Objekten im Vordergrund überdeckt oder teilüberdeckt werden. Das ist aber nicht "einfach" und eigentlich auch obsolet ... das (Such-)Stichwort dazu heißt

    Hiden-Line-Removal


    Obsolet ist es, weil man heute, wenn überhaupt, gleich ganze Flächen entfernt - und auch das macht man nur bedingt, denn eigentlich sortiert man sie i.a. einfach so ein, daß am Ende die vorderste "übrigbleibt" bzw. macht das Gleiche für Pixel von Flächen.


    Aber dazu muß man erstmal Flächen zeichnen.

    .

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()

  • Hier noch "Bastelei" mit den Sachen "included", d.h. Linien sortiert nach Tiefe und hier mal mit einem Coloring System (wenn man das so nennen will) mit Graustufen und einem extrem augenfreundlichen Hintergrund, damit man das besser sehen kann. Sonst wie oben schon. Das Sortieren nimmt gefühlt locker 25% Rechenzeit.
    (Ausführbar unter DOSbox in VGA.)

  • Topic: Das Clipping - am Beispiel von Linien


    Jetzt wirds theoretisch ... und außerdem wird ein Wort eingeführt, was immer wieder auftaucht: Clipping. Für's Erste wahrscheinlich erstmal recht nichtssagend. Auf deutsch ungefähr sowas wie "abschneiden", "zurechtstutzen", "scheren", "ausschneiden".


    Gemeint ist: Jede Szene hat irgendwelche Sachen, die zwar "da" sind, aber nicht mehr auf den Bildschirm bzw. das Anzeigefeld (den Viewport) passen. Und damit man diese Sachen nicht die ganze Zeit noch mitberechnen muß, versucht man sie frühestmöglich loszuwerden, natürlich nur in dem Sinne, daß sie in der aktuellen Berechnungsrunde ausgespart werden.

    Daneben gibt es dann auch noch den handfesteren Grund, daß i.a. irgendwann auch die gutmütigste Plotroutine Fehler meldet, wenn die Koordinaten zu weit außerhalb des Bildschirmes liegen. In "früheren Zeiten" waren da die Grenzen noch enger, da wurden solche Überschreitungen teils gar nicht vom Plotkommando abgefangen (und wenn man es selbst baut, dann natürlich erstmal auch nicht).


    Clippen kann man nun eigentlich alles Mögliche. Etwa Punkte, oder Linien, oder Teile von Objekten oder ganze Objekte (z.B. Bäume in Fortnite, wenn man sie "gehackt" hat) - wobei letzteres vielleicht die rudimentärste Form ist, weil man dabei einfach das Objekt ganz abschaltet, instantan und bevor man irgendwas testet. Sonst es ist eher üblich ein Objekt mit einer "virtuellen" (nur gerechneten) Kugel oder einem Quader zu umhüllen und diesen darauf hin zu testen, ob er im darzustellenden Bild überhaupt vorhanden sein kann - oder weit außerhalb liegt.


    Und wo bzw. wann im Ablauf man das Clipping macht, ist auch wieder eine Sache, die quasi relativ frei wählbar ist. Man unterscheidet zudem Clipping im Bereich der Objektkoordinaten (also dem Weltsystem) von einem Clipping in den Bildschirmkoordinaten, d.h. nachdem das Bild schon aufbereitet für die Anzeige vorliegt.

    Und für beide Varianten gibt es nun für alles was man Clippen kann wieder verschiedenste Algorithmen.



    Am Einfachsten kann man sich das bei einem Punkt vorstellen:

    Wenn das Bild Koordinaten von 1280 in X und 960 in Y-Richtung hat, dann fragt man einfach ab, ob der Punkt in diesem Bereich liegt, also

    IF ( PunktX < 0 ) THEN Clippe-den-Punkt

    IF ( PunktX >1279 ) THEN Clippe-den-Punkt

    was nur bedeutet, daß er - in welcher Form auch immer - als "nicht zu zeichnen" markiert wird.

    Das Gleiche natürlich auch noch die Senkrechte (Y).



    Bei Linien ist das schon schwieriger und dementsprechend gibt es verschiedene Varianten, wie man entscheidet, ob die Linie "bleiben darf" oder "gehen muß". Insbesondere tritt dabei auch der Fall auf, daß eine Linie nur zu einem Teil im Sichtbereich liegt - dann will man diesen Teil natürlich eigentlich anzeigen, aber den darüberhinausgehenden Teil der Linie "abschneiden". Man muß also die Linie teilen und markieren, welches der anzuzeigende Teil sein soll.


    Die Variante hier im Bild, ist das Linienclipping nach dem Algorithmus nach Cohen und Sutherland.

    Die beiden Namen sollte man schonmal irgendwie auch so gehört haben ...


    Der große Vorteil ist, daß man damit alle Linien, die komplett im Anzeigebereich liegen, sofort findet und insbesondere alle, die komplett außerhalb liegen fast ebenso schnell. Daher kann man damit relativ große Linienmassen recht schnell durchmustern und muß dann nur die verbleibenden "schräg" liegenden komplexer umrechnen.



    Das Teil ist zunächst mal schön unübersichtlich - aber eigentlich ganz extrem einfach und ziemlich genial (irgendwie).


    Merken muß man sich dafür eigentlich nur die Abkürzung "ABRL" und(!), daß eine UND Verknüpfung wichtig ist.


    Das Bild zeigt einen Auschnitt mit einigen Punkten (rote,grüne) und ein paar Linien dazu. Wichtig ist der Bereich in der Mitte: Die "0000" ist nämlich der Bereich des Bildschirms bzw. die Anzeige.


    Und damit wird dann auch "ABRL" selbsterklärend - das "A" steht nämlich für "A"bove, das "B" für "B"ottom und die bei anderen für "R"ight und "L"eft; daher in deutsch: Obendrüber, Untendrunter, Rechts davon und Links davon - bezogen auf das Anzeigefeld.


    Und jeder dieser Buchstaben ist eigentlich ein Bit, was gesetzt werden kann. Wenn also "ABRL" auf "1001" gesetzt ist, dann ist das Obendrüber-Links, also die Ecke oben links. Dagegen ist "0100" einfach nur Untendrunter (das "B" ist 1) und da Rechts und Links "0" sind, ist das also der Bereich direkt unterhalb der Anzeige.



    Und nun kommt das Witzige an der Sache: Man schaut nun einfach, wo für eine Linie der Anfang- und der Endpunkt liegen. Etwa für die alleroberste, kurze, hellblaue Linie: die startet oben-mittig, also "1000" und endet oben-rechts, also in "1010".

    Ermittelt wird das z.B. einfach über solche größer, kleiner IF THEN Abfragen oder Subtraktionen.


    Diese beiden Werte verknüpft miteinander mit einer AND Verknüpfung, also


    1000

    1010

    -------

    1000


    und schaut sich an, ob dabei "0" herauskommt, was es hier offensichtlich nicht tut, weshalb diese Linie direkt "verworfen" wird. Über diese Variante fallen alle Linien heraus, die in irgendeiner Form komplett außerhalb des Anzeige"ecks" liegen und dieses nicht schneiden.


    Man interessiert sich also nur für Linien, die bei der AND Verknüpfung "0000" liefern.


    Dabei gibt es einen Sonderfall, den man gleich am Anfang "testen" und finden möchte, nämlich, wenn beide Linienenden in "0000" liegen. Auch dann ist die AND Verknüpfung "0", aber das ist auch gleich der Fall, wo die Linie komplett(!) gültig ist und einfach so benutzt werden kann - da ist man als sofort fertig.


    Jetzt sind alle die Linien geclippt, die komplett außerhalb liegen und alle perfekt mittig liegenden hat man behalten. Bleiben also noch die, hoffentlich wenigen, Sonderfälle, wo man Linien teilen muß.


    (hoffentlich wenig ist WICHTIG(!), denn das ist ein Problem dieses Herangehens: wenn die allermeisten Linien schräg durchs Bild laufen und zudem schön lang sind, wird so eine Routine SEHR viel länger brauchen, als wenn schöne, kleine, kurze Linie direkt im Außen- oder Mittenbereich gefunden werden und sofort entscheidbar ist, ob man sie behält oder verwirft. Die Rechenzeit ist also extrem vom Objekt bzw. Modell abhängig und kann daher nicht vorher gut abgeschätzt werden.)



    Im Bild sind die dunklblau gezeichneten Linien, die zu bearbeitenden "Sonderfälle".


    Bei der kürzeren, oberen von beiden hat man "0000" und "0010" und daher AND verknüpft eine "0", aber keine zwei Punkte im Anzeigefeld. Und darum versucht man nun den Schnittpunkt mit dem Rand zu berechnen, an dem die Linie das mittige Feld verläßt. Da man die beiden Anfangs- bzw. Endpunkte kennt, kann man mit der einfachen Liniengleichung ( y=mx+n ) den Anstieg ermitteln (ya-ye)/(xa-ye) und dann von dem bekannten Punkt im mittigen Feld ausgehend, den Schnittpunkt mit der Seitenlinie berechnen, da man ja weiß wie weit der Abstand (X-Punkt minus X-Anzeigerand) in X-Richtung ist.

    Das klappt auch für alle 4 Seiten in genau gleicher Weise, nur für oben/unten vertauschen sich natürlich y und x bei der Anstiegsberechung der Linie oder wahlweise x und y als "bekannter" Wert bis zur Granzlinie.

    Damit hat man dann einen Punkt auf der Randlinie ermittelt und kann die Linie bis dahin zur Anzeige benutzen.

    Da der Randlinienpunkt nun auch im mittigen Feld "0000" liegt, sind nun wieder beide in dieser Form als "gute Linie" markiert und man sieht hier auch gleich die Abbruchbedingung wieder: nämlich solange bis beide Punkte "0000" sind.


    Was passiert nun bei der langen, dunkelblauen, schrägen Linie ?

    Das gleiche nur eben öfter.

    Punkt A liegt in "0101" (also unten-links) und Punkt B in "0010". Daher ist die AND Verknüpfung insgesamt "0" und daher wird die Linie behalten, aber da nicht beide "0000" sind, muß sie bearbeitet werden. Welche Seite man jetzt zuerst nimmt ist Geschmacksfrage. Der Anstieg der Linie ist zumindest schonmal für beide Varianten der gleiche.

    Damit kann man nun einen Schnittpunkt mit der nächsten Begrenzung eines Feldes berechnen, z.B. Punkt C. Dies liegt dann bei "0100" und wird gegen das immer noch aktuelle andere Ende "B" getestet (wieder "0010") - die AND Vernüpfung ergibt eine "0" als Ergebnis, und beide Punkte sind noch nicht "0000", also nochmal weiter. Diesmal den nächsten Grenzlineschnittpunkt bei Punkt D ermitteln und anschließend D mit "0000" gegen B (mit immer noch "0010") testen - AND-Ergebnis: wieder "0" , aber nicht beide "0000"; immerhin: "C" ist dort angekommen, weshalb nun die andere Seite dran kommt. Also wird nun B entlang der Linie bis zu nächsten Feldgranze verschoben und landet bei E, bzw. E wird natürlich errechnet. Und E liegt bei "0000". Im nächsten Test von D gegen E sind beide "0000" und die AND Verknüpfung gibt auch "0" - und die Linie ist fertig.


    Von der ehemals langen Linie A-B ist nur ein kleines Stück übriggeblieben ( D-E ), aber dieses kann nun komplett dargestellt werden. Die anderen Linienabschnitte, und auch alle die außen liegenden Linen, wurden gnadenlos "ge-clipped".

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • kleine Korrektur: (ya-ye)/(xa-ye) ermittelt natürlich nicht wirklich einen Anstieg, da ist ein Tippfehler passiert; Sinn macht das aber mit (ya-ye)/(xa-xe) mit "a" für Anfang und "e" für Ende der Linie. Multipliziert man diesen Anstieg mit dem bekannten Abstand X bis zur Grenzlinie ( Abstand = Grenzlinie - OriginalpunktX ), ermittelt man so den Y-Wert für den Punkt, der auf der Grenzlinie liegt - und kennt diesen damit komplett.



    Topic: Das Culling - noch so ein Begriff


    "Cull" steht für "ausmerzen", "sammeln", "klauben", "entfernen" - und "Culling" im Kontext Grafik vielleicht am ehesten für sowas wie "Negativ-Auswahl" oder "Auslassen".


    Der Unterschied zum Clipping ist teils recht fließend, insbesondere wird keiner der beiden Begriffe wirklich scharf vom anderen getrennt. Man hat das oben schon gesehen: Das Clipping nach dem vorgestellten Verfahren ist ja nicht nur Clipping, es werden schließlich sämtliche Linien, die komplett außerhalb des "0000" Anzeigebereiches liegen, einfach weggelassen. Das wäre strenggenommen natürlich gar kein Clipping - die Linie wird ja nicht gekürzt - sondern ein Culling.

    OK, ist ein bißchen Haarspalterei, aber es macht evtl. das Lesen von Texten einfacher, wenn man weiß, das sich die Begriffe da immer mal überlappen.


    Culling bedeutet also richtig echtes Weglassen. Wo das geschieht, hängt dann wieder vom Programm ab. Man kann das sowohl in der Weltkoordinatenpunktwolke wie auch später beim Zeichnen mit den Anzeigekoordinaten machen.


    Im Beispielprogramm gibt es bisher gar kein Culling. Da werden einfach alle Objekte dargestellt und auch die Linien alle gezeichnet. Es wird noch nichtmal sowas wie ein "Sichtfenster" in die "Welt" definiert, stattdessen bestimmt sich der Bildschirmausschnitt quasi rückwärts einfach aus den Bildschirmkoordinaten der VGA Auflösung. Da in der SUBroutine "AbbildungMitLinien" noch ein Faktor von 1.5 die Objekte vergrößert, bleibt also nur 2/3 der halben VGA-Auflösung in X bzw. Y Richtung als Auflösung der Anzeige in Weltpunkten übrig, d.h. 213 in X ( (640/2) / 1.5 ) und 160 in Y Richtung ( (480/2) / 1.5 ) , aber natürlich jeweils mit + und - Bereich.


    Da die Objekte sich alle innerhalb dieser Grenzen bewegen, ist das kein Problem. Wöllte man da aber jetzt Objekte außerhalb dieses Bereiches benutzen, wäre es extrem sinnvoll ein "Culling" zu machen, d.h. abzufragen, ob das Objekt außerhalb des Bereiches liegt und wenn ja, es vorerst von allen weiteren Berechnungen auszuschließen - einfach weil sie ja sowieso unnötig wären.


    Und man kann sich an dem Beispiel auch schön die einfachste Form eines Culling auf Anzeigekoordinaten, d.h. vor dem Zeichnen, anschauen.


    Da bisher einfach alle Punkte im Beispielprogramm gezeichnet werden, bedeutet das, daß der Beobachterstandpunkt VOR dem vordersten Punkt der "Welt" liegt. Das muß aber nicht so sein. Wir haben schon beim Abschnitt über die Positionierung der Kamera gesehen, daß diese prinzipiell überall liegen kann - und insbesondere auch, daß es für die Zentralperspektive sinnvoll ist, wenn alle Punkte einen positiven Wert erhalten.

    Beides bedeutet aber automatisch, daß hinter dem Beobachter durchaus noch Objekte liegen können - und die muß man gar nicht berechnen, sondern besser einem "Culling" unterwerfen.


    Das Gleiche macht übrigens auch Sinn für sehr weit entfernte Objekte. Diese würden wahrscheinlich sowieso nur als kleine verzerrte Pixelhaufen erscheinen und haben oft auch keinen unmittelbaren Bildwert. Daher sortiert man sie aus.


    Beides macht man durch Festlegen von zwei Ebenen. Eine vorne und eine hinten. Vorne ist dabei i.a. unmittelbar vor dem Beobachterstandpunkt und daher "nahe" und heißt darum "near" Ebene. Die hintere ist die "weit entfernte" und heißt daher englisch "far".



    Für die Anzeige bleibt nach der Negativ-Auswahl nur das übrig, was sich zwischen "near" und "far" befindet.


    Übrigens: Interessanterweise laufen diese beiden Ebenen durchaus unter dem Begriff "Clipping-Ebenen" ...



    Auf Displayebene läßt sich soetwas ganz einfach ins obige Demo (#24) einbauen; evtl. hilfreich für das Verständnis des generellen Konzeptes: Da die Linienmitten nun eh' einmal vorhanden sind, kann man die Abfrage dazu ein bißchen erweitern und ein zusätzliches


    Prüfen der äußersten erlaubten Werte einfügen, was dann darüber bestimmt, ob die Perspektivenberechnung und das eigentliche Linienzeichnen überhaupt stattfinden dürfen.


    Das ist dann i.P. ein (primitives) Culling auf Anzeigenebene, was nur Linien mit Mitten zwischen -85 und +100 auf der Z-Achse zuläßt. In X und Y Richtung ist aber immer noch alles erlaubt.

    Und: Es ist definitiv KEIN Culling auf Weltkoordinaten. Dafür müßte die Abfrage weiter oben sitzen. Und dort macht man das am Üblichsten mit einem sogenannten Pyramidenstumpf - einem "Frustum".


    Wenn man dieses Auswählen auch noch in X und Y Richtung auf die gleiche Art mittels je zwei Ebenen macht, erhält man einen Würfel (Cube), der durch 3-mal 2 Ebenen begrenzt wird und nur noch die darstellbaren Objekte enthält.

    .

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    4 Mal editiert, zuletzt von ThoralfAsmussen ()

  • Topic: Vektoren multiplizieren - und damit Informationen sammeln ; die Flächennormale ermitteln sowie den Winkel zwischen zwei Vektoren



    Jetzt, zum letzten Tag vor den Sommerferien (zumindest hier), wirds doch nochmal recht "technisch", gewissermaßen "mathe-technisch". Aber: Wer sich einen Vektor bisher vorstellen konnte, kommt auch damit was jetzt kommt zurecht - es ist nämlich strenggenommen nicht schwer, nur ein bißchen hakelig.



    Es geht darum, was man mit zwei (!) Vektoren so machen kann, und wofür sich der entstandene Informationsgewinn dann ausnützen läßt. Insbesondere geht es um die Multiplikation von Vektoren. Und damit es nicht zu einfach wird, gibt es davon schonmal gleich zwei (!) (schon wieder ...) derartige Operationen, die für 3D Grafik eine Rolle spielen.


    Um's gleich vorwegzunehmen: es geht um das Kreuzprodukt und anschließend um das Skalarprodukt.


    Und weil das in der Anwendung dann nützlich ist, klären wir auch vorher noch den Betrag eines Vektors als Begriff.



    Vektoren haben wir ja oben schon genug verwendet. Die haben also irgendwie eine komische Schreibweise, zumindest, wenn man ihre Zahlen übereinander schreibt und im speziellen Fall hier sind sie 2 oder 3 oder gar 4 "Positionen" lang - (X,Y,Z,W) - wobei nur (X,Y,Z) die eigentlichen Raumkoordinaten sind. Diese beschreiben i.P. eine "Linie", die irgendwo im Raum beginnt und dann an einer Stelle endet, die genau soweit weg vom Startpunkt ist, wie es eben im Vektor drinsteht.

    Und so eine Linie im Raum hat quasi auch eine Art Länge - und das ist dann der sogenannte Betrag des Vektors. Errechnen kann man diesen relativ umkompliziert mit


    Betrag = Wurzel aus der Summe der Quadrate der Vektorelemente


    d.h. für (X,Y,Z) gilt: |(X,Y,Z)| = SQR ( X*X + Y*Y + Z*Z )


    mit "|...|" für "Betrag von ..." und SQR als SqareRoot, d.h. Quadratwurzel


    Interessant ist das v.a. weil man den Vektor anschließend noch durch seinen Betrag teilen kann und so einen sogenannten Einheitsvektor ermitteln kann. Den brauchen wir aber hier jetzt nicht unbedingt.



    Komplizierter wird die Sache mit dem Kreuzprodukt. Das ist eine etwas aufwendigere Rechnerei, und die geht so:


    Vektor 1 = V1 = (X, Y, Z)

    Vektor 2 = V2 = (A, B, C)


    sind die beiden Vektoren - wobei A,B,C natürlich auch einfach X,Y,Z Werte sind, aber hier kurz anders heißen, damit die Formel gleich übersichlicher wird - und für das Kreuzprodukt schreibt man einfach ein "x" (kreuz) dazwischen:


    V1 x V2


    und, um es auszurechnen:


    Ergebnis X = ( Y*C - Z*B )

    Ergebnis Y = ( Z*A - X*C )

    Ergebnis Z = ( X*B - Y*A )


    Da wir ja hier Computer benutzen, schreibt man das letztlich mal irgendwo in eine SubRoutine und kümmert sich dann nicht mehr drum, sondern benutzt es nur.

    Dafür ist aber interessant zu wissen, was es "bedeutet".


    Im Ergebnis steht ja wieder ein Vektor - nämlich (ErgebnisX, ErgebnisY, ErgebnisZ). Und der ist genauso eine "Linie im Raum" - aber die hat ein paar besonders spannende Eigenschaften.





    Sie steht nämlich exakt 90 Grad zu der Ebene, die von den beiden Vektoren V1 und V2 definiert wird. Und zwar völlig egal, wie V1 und V2 im Raum liegen - das Kreuzprodukt ist immer so wie im Bild ein "Pfeil", der mit jeder Kippung der Ebene mitwandert, wenn sich beide (V1,V2) oder auch nur einer von beiden sich verändert.


    Das ist nun für 3D extrem interessant, weil man dadurch eine Information über eine Fläche ausrechnen kann ! Und zwar, in welche Richtung die Fläche bzw. ihre Oberfläche hinzeigt; wohin sie "schaut".


    Allerdings muß man dabei ein paar Sachen beachten.

    Zum einen gilt


    ( V1 x V2 ) = - ( V2 x V1 )


    was erstmal albern aussieht, aber nur bedeutet, daß, wenn man die Berechnung mit dem zweiten Vektor an erster Stelle ausführt, also beim Berechnen die Vektoren "verwechselt", sich dadurch auch das Ergebnis "wechselt" und zwar so, daß jeweils ein "mal -1" dazukommt. Oder, grafisch, der Ergebnisvektor in die entgegengesetzte Richtung zeigt.


    Das Gleiche passiert auch, wenn man einen der beiden Vektoren vorher "herumdreht", dann dreht sich auch der Ergebnisvektor.


    Und beides ist relevant, weil es daher darauf ankommt, in welche Richtung und welcher Reihenfolge man seine "Linien" aus dem Rohdatensatz ausliest - zumindest, wenn man so ein Kreuzprodukt ausrechnen will.

    Es ergibt sich ein anderes Ergebnis, wenn man an einer Würfelecke ( Bild links oben ) die Reihenfolge der Linien nicht gut beachtet und dadurch die beiden Vektoren - also die zwei Kanten des Würfels, die zu zweit die violette Fläche definieren - vertauscht.


    Damit das nicht passiert, muß man schon bei der Modellplanung einen Modus finden, der das gut definiert, damit so ein Fehler nicht passiert. Beim Würfel würde dann der ErgebnisVektor - der ja 90 Grad auf der Fläche steht - entweder vom Würfel nach außen zeigen ODER in die Gegenrichtung, d.h. ins Würfelinnere. Benutzen kann man natürlich beides, aber es ist eher üblich, diese Ergebnisvektoren so auszurechnen, daß diese nach außen, vom Würfel weg, zeigen ( wichtig ist dabei v.a., daß man nicht für jede Fläche eine andere Richtungsbeschreibung bekommt, sondern entweder alle nach außen, oder alle nach innen).


    Und mit so einem Ergebnisvektor, der quasi anzeigt, wohin die Fläche orientiert ist, kann man dann Vergleiche anstellen - es läßt sich etwa ermitteln, ob die Fläche in die gleiche Richtung "schaut" wie eine Achse oder wie eine andere (Nachbar)Fläche. Man kann aber auch z.B. bestimmen, ob der Winkel zu einer Lichtquelle im Raum so ist, daß die Fläche beschienen wird, und wenn ja, würde man sie einfach heller zeichnen.


    Wir wollen sie im Beispiel unten stattdessen ganz weglassen ...


    Dieser Ergebnisvektor des Kreuzproduktes hat auch noch den schönen Namen

    Flächenormale oder auch Normalenvektor.




    Die andere interessante Form der Multiplikation heißt Skalarprodukt.


    Dabei erhält man KEINEN Ergebnisvektor - sondern einen einfachen Zahlenwert. Man wird also schonmal keine Richtung von "was auch immer" angegeben bekommen, wenn man das ausrechnet. Dafür ist die Rechnerei aber wesentlich einfacher. Man addiert einfach alle Produkte, die man zwischen den Positionen der beiden Vektoren auf gleicher Höhe bilden kann. Und auch das Symbol ist einfacher - nämlich nur ein "Punkt" (wie bei normaler Zahlenmultiplikation)


    Man benutzt wieder zwei Vektoren


    V1 und V2


    mit


    V1 = (X,Y,Z)

    V2 = (A,B,C)


    und rechnet


    Ergebnis = V1 . V2 = X*A + Y*B + Z*C


    that's it.


    Nichts Komplexes, keine Bruchrechnung, keine Wurzel, noch nichtmal Potenzen oder sowas.

    Interessant ist aber, daß das Ergebnis auch eine Bedeutung hat und zwar nach der Gleichung


    V1 . V2 = |V1| . |V2| . cos (winkel-zwischen-den-vektoren)


    Der Ergebniswert entspricht also auch einem anderen Rechenweg, in dem ein cosinus vorkommt. Und das bedeutet, daß man mit den beiden Vektoren, deren Verhältnis zueinander man untersuchen möchte, einfach ein Skalarprodukt bilden kann und damit auf den Winkel zurückschließen kann.





    Nun weiß man ja, daß der Cosinus von 0 Grad eben genau 1 ist, was das positive Maximum für einen Cosinus ist. Dagegen ist ein Cosinus von 90 Grad gerade eben 0. Und das kann man schon für eine einfache Abfrage benutzen:


    Da mit dem Erreichen der 90 Grad Marke die 0 "erscheint", wird an dieser Stelle auch das Gesamtergebnis 0 sein (der Cosinus wird ja mit dem ganzen Rest multipliziert). Wird der Winkel dann noch größer, wird der Cosinuswert negativ - und damit auch das Gesamtergebnis, also in dem Fall das Skalarprodukt.


    Wenn die zwei Vektoren in genau die gleiche Richtung zeigen, wird ihr Skalarprodukt maximal positiv sein. Wenn sie dagegen so gegeneinander gedreht sind, daß dazwischen 90 Grad als Winkel entstehen, wird sich im Skalarprodukt eine 0 ergeben ( wegen cos(90)=0 ) und wenn gar der eine in die entgegengesetzte Richtung zeigt, mithin der Winkel > 90 wird, dann wird das Ergebnis sogar negativ.


    Und das kann man schön per IF THEN abfragen

    IF skalarprodukt > 0 THEN irgendwie in die gleiche Richtung bei <90 Grad Winkel

    IF skalarprodukt < 0 THEN sind die Vektoren entgegengesetzt mit >90 Grad Winkel

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

    Einmal editiert, zuletzt von ThoralfAsmussen ()