Tags: Artikel mit dem Tag «CG» durchstöbern
3D-Perspektive mit einer kurzen Formel

3D-Effekt mit Javascript
Für die Gestaltung von 3D-Effekten, sind zwar duzende Hardware-beschleunigte Toolkits parat, doch im Fall von HTML-Canvas bzw. auf Webseiten generell sieht das Angebot nicht so reich aus. Das hat mich dazu bewegt, selbst einen kleinen Blick in die Mathematik hinter 3D-Projektionen/3D-Perspektive zu werfen. Meine Erkenntnisse konnte ich in einer kurzen Formel zusammenfassen.
Wer sich nicht für die Mathematik interessiert, kann natürlich flash oder eine der vielen Canvas-3D-Librarys verwenden.
Das Prinzip hinter der 3D-Projektion ist meist das einer Lochkamera. Lichtstrahlen fallen vom abzubildenden Punkt im Raum durch ein Loch und auf einen Schirm. Diese Art der Projektion war eine der ersten bekannten und mit ihrer alten Bezeichnung „camera obscura“ (lat. für dunkle Kammer, die in der der Schirm angebracht war) namensgebend für die heute auf Linsenoptik basierenden Kameras.
Die Lochkamera ist leider nicht so lichtstark wie die Linsenoptik und ihre Schärfe ändert sich nicht mit der Entfernung, sondern mit der Größe des Lochs.
Im Gegensatz zur Linsenoptik ist die Lochkamera aber durch den einfachen Strahlensatz zu berechnen. Mit dem Strahlensatz kann man ganz einfach die Koordinaten eines Punktes im Raum umrechnen in Koordinaten auf dem Schirm. Dazu genügt folgende Funktion:
F:ℝ³→ℝ², F(x,y,z)= P( x*d / z | y*d / z ) ; d: distance to screen
Dass diese Funktion klappt, zeigt dieses Beispiel. Hier eine Graphische Erläuterung:

Strahlensatz für Perspektive. Schwarz: Abzubildender Punkt/Lichtstrahl, Blau: Kamera, Grün: Kameraparameter Bildweite, Rot: Koordinate auf dem Schirm, Lila: Ursprungskoordinaten
Wegen dem Strahlensatz gilt x' / x = d / z, aufgelöst x' = d*x / z, analog für die y-Koordinate.
Etwas logischer wäre vielleicht der Schirm hinter dem Loch, vor allem da Objekte näher an die Kamera heran kommen können, doch so kann man sich den Schirm wie den Computerbildschirm vorstellen, durch den man in die Raumillusion hineinschaut.
Diese Formel erledigt die Abbildung in der Kamera. Jedoch muss der Punkt bereits in Koordinaten relativ zur Kamera gegeben sein. Da man normalerweise Punkte zunächst durch ein Weltkoordinatensystem definiert, muss man sie erst transformieren.
Bei meinem einfachen Beispiel beschränkt sich die Transformation zunächst auf eine Verschiebung entlang der z-Achse und später habe ich noch eine Rotation um die y-Achse hinzugefügt. Diese Koordinatenumrechnungen lassen sich in Transformationsmatrizen beschreiben.
Dann fehlt eigentlich nur noch die Darstellung. Im Beispiel wird der Text durch einfache CSS-Manipulationen an die richtige Position gebracht, und die Kreise werden mit dem canvas-Element gerendert.
Mit einem solchen Modell lassen sich schon einfache Drahtgitter problemlos darstellen (Demo), da eine Kante zwischen zwei Punkten im Raum auch eine Strecke zwischen zwei Punkten auf dem Schirm darstellt, und uns somit die Berechnung dieser Bildpunkte erspart. Für eine ausgefeiltere Darstellung kann man Dreiecke oder Polygone verwenden (Demo), für die ungefähr das selbe gilt, nur dass z.B. ihr Winkel zu einer Lichtquelle für die Kolorierung verwendet werden kann
Für nahezu realistische Lichteffekte bedarf es allerdings eines anderen Modells, genannt Raytracing, bei dem man den Weg rückwärts geht und bei der Kamera anfängt. Das wurde übrigens auch schon in Javascript umgesetzt, ist aber für Echtzeit-Anwendungen eher ungeeignet (vielleicht kommen bald die ersten Computerspiele mit der - in Realtime - noch neuen Technik).
Processing und Blender
Processing ist gut geeignet um Daten und Berechnungen zu visualisieren, Blender übernimmt dafür mehr Material- und Rendering-Aufgaben. Dennoch gibt es Bereiche, in denen sich die Verwendungszwecke überschneiden, zum Beispiel die hier gezeigten Formen.
Das Bild oben ist eine runde zweifache Spirale - Auf der innersten Ebene jeweils zwei gestreckte und zu Spiralen verdrehte Kreise und Quadrate, deren Dehnprodukt sich dann spiralförmig schraubend um einen Kreis windet. Zwar kann Blender auch mit Python gescriptet werden, das habe ich mir aber bisher noch nicht angeschaut, weshalb alles eher in „Handarbeit“ entstanden ist. Die Farben habe ich nachträglich noch ein bisschen mit GIMP verändert. Das ist ein Rendering in Original-Farben und mit einer etwas angehobenen Ebene:
Wenn sich diese Doppelspiralkreise dann wiederum um einen Kreis legen, entsteht das:
Mit Processing geht dieses verdoppeln und drehen alles viel Einfacher. Hier mal ein Bild aus einem nicht wirklich fertigem Projekt:
Es ist ein Versuch eine Art Haarbüschel nach gewissen Expansionsregeln zu erstellen. Die „Haare“ können in der Sketch interaktiv nach oben und unten gebogen werden, was ein recht lustiges Bild ergibt. Vielleicht hier nochmal ein Bild mit besserem Überblick und anderer rendering-Methode:
Ich habe mich auch schon in Processing etwas mit Spiralen auseinandergesetzt, ist aber leider nicht in 3D, aber dank farblicher Überarbeitung doch noch recht interessant geworden:
Die Bildungsvorschrift ist übrigens eine Linie von einem Punkt aus im rechten Winkel zum Ursprung bis zu einem bestimmten Winkel und das sehr oft wiederholt. Der Sourcecode beschränkt sich auf wenige Zeilen:
// Initialisieren
size (1800,1100);
smooth();
background(255);
// Setzt den Ursprung des Koordinatensystems auf die Mitte
translate(width/2,height/2);
// 20 Spiralen
for (int k=0; k<20; k++) {
// Weiterdrehung pro Spiralsegment
float rotateStep=PI/11.465;
// Anzahl der Spiralsegmente
int rotateAnz=200;
// Startposition
float len=k; // wird ei jeder Spirale leicht vaiiert
// Startposition 2
float prelen=cos(rotateStep)*len;
// Die Spiralensegmente erstellen
for (int i=0; i<rotateAnz; i++) {
// für jedes Segment ein Stück weiter drehen
rotate(rotateStep);
// Entfernung des Endes des letzten Segments zum Ursrung
// Verwendet als Entfernung des Anfangs des aktuellen Segments zum U.
len=prelen/cos(rotateStep);
// eine Linie vom Ursprung zum Anfang des Segments
stroke(0,40);
line(0,0,len,0);
/* Damit das letzte, nicht abgeschlossene Segment
* nicht so hässlich aus der Spirale hängt */
if (i!=rotateAnz-1) {
// Eine Linie vom Anfang zum Ende des Segments
// Das wird die Spirale
stroke(0,200);
line(len,0,len,len*tan(rotateStep));
}
prelen=len;
}
}
Mit den Kommentaren ist es doch ganz ordentlich länger geworden. Vom rechten Winkel sind übrigens nurnoch die Trigonometrischen Funktionen cos und tan übrig: Die Strecke Ursprung-Segmentbeginn ist die Hypotenuse des Segments und wird die (zu unserem Schritt-Rotationswinkel) Ankathede des nächsten.
Selbstorganisierendes 3D-Clustering
Und wieder einmal ein bisschen mit Processing herum gespielt: Da meine 3D-Tagcloud ja noch recht wechselhafte Ergebnisse geliefert hat, habe ich nun die Erkenntnisse aus der Magnetismus-Implementierung in Processing verwendet, um die Blogposts noch besser anzuordnen. Sie ziehen sich zusammen, wenn sie ähnliche Themen behandeln, was wiederum anhand der gemeinsamen Tags festgemacht wird. Video:
Selbstorganisierender 3D-Cluster aus Blogposts basierend auf gemeinsamen Tags
Zunächst habe ich den Code aus den beiden erwähnten alten Sketches zusammen kopiert. Leider musste noch einiges geändert werden: Die Tags führen nicht länger ein Eigenleben, sondern dienen nur als Berechnungsgrundlage für die gegenseitige Anziehung der Blogeinträge. Zwar läd die Routine wieder die selbe .csv-Datei, doch diesmal legt sie nur einen Array von Blogeinträgen an, die jeweils eine Liste ihrer Tags speichern.
Daraufhin folgt eine kleine Erweiterung meiner „Magnetismus“-Partikel. die Anziehung wird nicht mehr durch eine Ladung bestimmt, sondern durch die Anzahl der gemeinsamen Tags. Sollte diese Anzahl 0 sein, erfahren die Blogeinträge eine kleine Abstoßung. Sollte die Anzahl 1 sein, ist die „Ladung“ 1, sollte sie 5 sein, ist die Ladung 5100, also vergleichsweise groß. Das sorgt dafür, dass sich die Partikel gerne „durchkämpfen“. Dann haben die Partikel an sich immer die Ladung 1, weil es ja eine symmetriche Bindung ist. Es gibt wieder eine „Pauli-Kraft“, sodass die Partikel nicht wie ein Zelt zusammenfallen. Damit sie sich aber nicht unendlich abstoßen, habe ich auch noch eine weitere Kraft zum Zentrum hin hinzugefügt, die sich kontrollieren lässt, sodass manchmal die Blogeinträge etwas lockerer auseinander fallen und sich aber auch in der Mitte zu einem Ball zusammenfinden. Sehr eindrucksvoll finde ich wie bestimmte Themenkomplexe bei geringer „Zentripetalkraft“ in Kristall-artigen Strukturen zusammen bleiben.
Beim Rendering habe ich wieder ein bisschen neuen Code ausprobiert. Die Linien repräsentieren nun die Stärke der gegenseitigen Anziehung (nicht wie vorher die Entfernung) und die Partikel haben einen DOF-Effekt bekommen: Eine Textur, die sich je nach Entfernung zur Kamera verändert, zu einer weich gezeichneten Version der Textur.
Magnetismus und Processing
Wenn man ein bisschen interessantere Partikel-Effekte haben will, als konstante Geschwindigkeit oder Beschleunigung, so wie Selbstorganisation, dann kann man ziemlich gut die Prinzipien von Feldern, wie Magnetismus (so heißt das irgendwie bei Processing, läuft aber auf Elektrostatik hinaus) oder Gravitation benutzen. Hier mal ein Video von meinen Magnetismus-Partikeln:
504 Magnetische Partikel. Die großen Gelben habe ich für das Video „manuell“ bewegt.
Die großen gelben Partikel haben eine recht große positive Ladung und auch eine große Masse, sodass sie sich nicht so schnell abstoßen. Die kleinen Partikel haben eine kleine Masse und kleinere negative Ladung, sodass sie schön in der Gegend herum fliegen und sich nicht so stark abstoßen.
Bei Magnetismus und Gravitation sieht die Grundgleichung so aus:
E = Q * ( D / |D|^3 )
Mit Ladung des anderen Partikels Q und Differenz der Ortsvektoren D. Hat man mehrere Partnerpartikel, addiert man diesen Feldvektor E von jedem Einzelpartikel und teilt dann durch die Anzahl. Bei der Gravitation wäre Q die Masse, es beschriebt also so etwas wie den Partikelspezifischen Wirkungsgrad.
Dann berechnet man die Coulomb-Kraft, die dieses Feld auf den Partikel auswirkt mit:
F = ε * q * E
Wobei q die Ladung des Partikels ist und ε eine Konstante, die bewirkt, dass die Feldkraft in ordentlicher Proportion ist mit anderen Kräften, die auf den Partikel wirken. Denn jetzt benötigt man noch ein paar mehr Felder oder Kräfte, damit die Partikel nicht unendlich weit abgestoßen werden bzw. aufeinander prallen. Da würde sich die Gravitation als Ergänzung zum Magnetismus eignen oder eine sog. „Pauli-Kraft“ (12er Teil vom Lennard-Jones-(12,6)-Potential), die statt dem ^3 ein ^12 hat. Das ^12 bewirkt eine große Erhöhung der Kraft, wenn die Partikel sich näher kommen, sind sie jedoch weiter entfernt, wirkt die Kraft kaum noch. Für die Pauli-Kraft musste ich ε recht hoch wählen, damit sie in Konkurrenz mit dem Magnetismus treten kann. Für Flüssigkeiten wäre statt einem weiteren Feld aber z.B. auch eine Konstante Beschleunigung nach unten in Kombination mit einem Eimer o.ä. möglich.
a = ΣF / m
Hat man alle Felder berechnet, addiert man die Kräfte, und dividiert die Kraft durch die Masse, damit hat man die Beschleunigung. Die Beschleunigung addiert man zur Geschwindigkeit und die Geschwindigkeit multipliziert man mit einer Reibungszahl und dann addiert man sie zum Ort. (Euler-Verfahren/Methode der kleinen Schritte) Damit hat man eine halbwegs brauchbares Partikelsystem.
Kleine Ergänzungen könnten noch sein ein Timer, sodass die fps keine Einwirkung auf die Geschwindigkeit haben, erfahrungsgemäß bleiben sie aber mehr oder weniger konstant. Was extrem zur Geschwindigkeit beiträgt ist das Verwerfen von kleinen Feldkräften. Man kann mit Ladung, Masse und Entfernung schon grob bestimmen, ob sich die Partikel überhaupt groß beeinflussen werden und sich dann die Berechnung der genauen Feldkraft sparen, was schon sehr viel Geschwindigkeit einspart. Hier z.B. mal ein Screenshot aus meinem für Echtzeit optimiertem Algorithmus, wo nur die auch wirklich berechneten Beeinflussungen zwischen den kleinen Partikeln als rote Linien eingezeichnet sind:
In diesem high-fps-Modus habe ich auch das Text-Rendering deaktiviert, da das doch alles deutlich verlangsamt hat. Nötig wäre auch eine bessere Methode also das simple Geschwindigkeit addieren. Ich habe gelesen man kann Integrieren. Oder man addiert einfach nur z.B. ein viertel der Geschwindigkeit und berechnet alles viermal pro gerendertem Frame, was eigentlich ganz gut funktioniert, aber eben nicht immer, es fliegen doch hin und wieder Teilchen in den Äther. Ein Geschwindigkeitslimit wäre vielleicht auch hilfreich.
Was auch noch interessant werden könnte, das ganze mit verschiedenen Ladungsarten? Das Prinzip lässt sich auf jeden Fall erweitern. Schließlich gibt es ein Wikibook über Teilchenphysik aus dem man sich einiges an Inspiration holen kann, oder man experimentiert, so wie ich bei der 3D-Tagcloud mit Processing. Hat man das ganze Buch implementiert und den Algorithmus 100% perfektioniert, ist man wohl bei so etwas wie der „Weltformel“, was definitiv interessant wäre.
3D-Tagcloud mit Processing
Ich habe ja schon etwas mathematisches und recht interaktives mit Processing gemacht. Jetzt wollte ich noch eine kleine Daten-Visualisierung basteln. Da ich wenig Lust habe tagelang irgendwelche tweets zu analysieren und ich mich nicht in die Musik-Librarys von Processing einarbeiten wollte, habe ich meine Blogeinträge und die dazugehörigen Tags als Datenset hergenommen, was auch noch eine recht überschaubare Datenmenge ist, und sie in einer Art 3D-Netz angeordnet. Video:
Zunächst einmal habe ich die Daten als .csv aus der Datenbank exportiert. Dieses Dateiformat ist zum Glück einfach mit Processing einzulesen. Daraus habe ich ein paar Objekte erstellt. Hier war ich mir ein bisschen unsicher, wie das zu realisieren sei. Im Endeffekt habe ich 3 Arrays von Objekten, einer enthält alle Tags, einer alle Blogeinträge und ein dritter alle Verbindungen. Zusätzlich bekommen alle Tags noch eine ArrayList mit ihren zugeordneten Blogeinträgen, um das aktivieren (röten) einfacher zu machen.
Dann haben die 3 Objekte (Eintrag, Tag, Verbindung) erstmal jeweils eine rendering-Methode bekommen. Die Einträge und Tags zeigen einfach ihr Label an, und beginnen auf einer Ebene mit (mehr oder weniger) zufälliger Position. die Verbindungen, die jeweils einen Pointer auf einen Eintrag und einen Tag speichern, rendern sich als Linie zwischen den Positionen der beiden. Zusätzlich habe ich noch eine Szene implementiert, die von der aktuellen Framezahl die Kameraposition und das aktivierte Tag bestimmt.
Eine rein zufällige Anordnung ist allerdings nicht wirklich zufriedenstellend. Deshalb beginnen nun die Tags als nächstes sich dem Schwerpunkt ihrer zugehörigen Blogeinträge zu nähern und die Blögeinträge nähern sich dem Schwerpunkt ihrer Tags. Aus Spaß habe ich das einmal laufen lassen, natürlich kollidiert das ganze recht zielstrebig. Als zweiter Bewegungsparameter kommt hinzu, dass, wenn ein gewisser Mindestabstand unterschritten wird, sich die Blogeinträge und Tabs auch gegenseitig abstoßen. Hier musste ich die Parameter recht fein abstimmen.
Damit nicht der Expansionscharakter die Überhand bekommt (wie bei unserem Universum?), habe ich auch noch eine Arena eingeführt: Wenn die Schriftposition aus einem Zylinder in der Mitte des Bildschirms heraus wandert, greift ein „Sheepdog“, der das Bewegungsverhalten überschreibt und die Tags bzw. Einträge zurück zum Mittelpunkt zieht, bis sie wieder im Rahmen sind. Da die Blogeinträge sowieso etwas unter Druck stehen, sorgt das dafür, dass alles schön zentriert bleibt und ein bisschen in ein Raster gepresst wird.
Je nach Anfangsposition funktioniert der Algorithmus mal besser und mal schlechter, vielleicht müsste ich noch etwas an den Parametern schrauben. Es ist auch schwer zu sagen, was gut und schlecht ist, aber hin und wieder gehen Tag-Eintrag-Verbindungen über den halben Zylinder („mangelnder Ellbogen-Einsatz“?) und bei manchen Durchläufen werden z.B. einige einfache Verbindungen recht schön kurz.
Das ganze lief mit dem P3D-Renderer eigentlich ganz ordentlich, doch dank diesem Tutorial zu Additive Blending mit OpenGL wird jetzt auch mit dem OpenGL-Renderer der Text ohne die weißen Flächen in den Buchstaben gerendert. Ich musste nur alles auf schwarz umstellen, was allerdings auch nicht schlecht aussieht. Jetzt läuft die Animation mit Kantenglättung (smooht() und P3D läuft bei mir sehr sehr langsam) weich wie Butter. Und so sieht alles in schwarz aus:
Mit einer netten Textur und frei drehbar im neuen Style:
Irgendwie interessiert mich als nächstes „Magnetismus“. Man liest recht viel darüber, mal sehen was genau man sich darunter vorzustellen hat.














