Simulation
Motivation
Die Simulationsgruppe des Tiefseeprojekts hat es ermöglicht, computeranimierte Simulationen von diffusen Tiefseeausflüssen zu erzeugen. Diese Simulationen können als Input für die Detektionssoftware genutzt werden. Der Vorteil an computeranmierten Simulationen besteht dabei darin, dass es keine Störfaktoren gibt. So konnte die Detektionssoftware beispielsweise schon in einer frühen Entwicklungsphase getestet werden, als Störfaktoren noch nicht in der Programmierung berücksichtigt wurden. Durch den Vergleich mit den Experimenten konnten die Ergebnisse der Simulation außerdem verifiziert werden.
Für den erfolgreichen Abschluss des Simulationsprojektes mussten zwei Hauptprobleme gelöst werden. Zum einen galt es, die Bewegung des austretenden Wassers aus dem Tiefseeboden realistisch zu animieren. Dabei ist der wichtigste Aspekt das Verhalten der Brechungsindizes an den verschiedenen Punkten des ausfließenden Wassers. Sie bestimmen später, wie die Schlieren vor dem Hintergrundbild aussehen. Zum Anderen musste ein Raytracer programmiert werden, der es ermöglicht, diese Animationen von fließendem Wasser mit unterschiedlichen Brechungsindizes als Schlieren vor einem Hintergrund darzustellen.
CFD-Tool Ansys
Das erste Problem wurde durch die Nutzung eines CFD-Tools (Computed Fluid Dynamics) gelöst. Das Programm Ansys Fluent ermöglicht es, eine Geometrie zu erstellen und mit Öffnungen zu versehen, aus denen Flüssigkeiten ein- oder austreten können. Nachdem alle Parameter eingestellt wurden, kann eine Simulation über eine bestimmte Anzahl von Zeitpunkten gestartet werden und das Programm berechnet das Verhalten der Flüssigkeiten in der geometrischen Form. Dabei kann für jeden Zeitpunkt in der Berechnung eine Datei ausgegeben und gespeichert werden, die ausgewählte physikalische Eigenschaften für jede Zelle der Geometrie beinhaltet.
Für die Simulation von diffusen Tiefseeausflüssen wurden mehrere verschiedene Einstellungen in Ansys Fluent vorgenommen, bis das Ergebnis passend aussah. Im Großen und Ganzen wurde eine rechteckige Box, die mit kaltem Wasser gefüllt ist, mit mehreren kleinen Öffnungen am Boden versehen, aus denen heißes Wasser in die Box fließt. Unterschiedliche Änderungen der Parameter führten zu verschiedenen Ergebnissen der Berechnung, sodass im Laufe des Projekts mehrere CFD-Simulationen entstanden.
Raytracer
Das zweite Problem wurde gelöst, indem ein eigener Raytracer geschrieben wurde. Ein Raytracer ermöglicht es prinzipiell, eine dreidimensionale Szene mit verschiedenen Objekten und Lichtquellen zu erstellen, einen Blickwinkel auf diese Szene zu bestimmen und das daraus entstehende zweidimensionale Bild zu rendern und abzuspeichern. Wie der Basis-Raytracer für dieses Projekt entstanden ist und welche Aspekte dabei beachtet werden mussten, kann man in der Bilderreihe Raytracing sehen.
Neben den grundsätzlichen Objekten wie Bilddateien, Kugeln und anderen geometrischen Figuren ist es in diesem Raytracer auch möglich, einer Szene Schlierenobjekte hinzuzufügen. Das Hinzufügen von Schlieren funktioniert, indem vom Benutzer ein Pfad übergeben wird, der zu dem Ordner führt, in dem die gewünschten Ansys-Dateien für alle Zeitpunkte aus der Ansys-Berechnung gespeichert sind. Aus einer Ansys-Datei kann dann für jeden Punkt des Schlierenobjektes der Brechungsindex berechnet werden, indem die Punkte der Ansys-Geometrie auf die des Schlierenobjektes übertragen werden.
Um also eine Simulation von Tiefseeausflüssen zu erzeugen, muss zunächst eine Szene erstellt werden, die mindestens ein Bild enthält, das den Hintergrund befüllt und ein Schlierenobjekt, das sich davor befindet.
Die Anzahl der Ansys-Dateien in dem Ordner bestimmt die Menge der Bilder, die gerendert werden - je mehr Zeitpunkte also in Ansys berechnet wurden, desto mehr Bilder werden gerendert. Dabei verändert sich das Schlierenobjekt in jedem Bild entsprechend der jeweiligen Datei für den Zeitpunkt. Die Simulation entsteht, indem man diese gerenderten Bilder zu einem Video zusammenfügt.
Schichtung als Lösungsansatz
Um das Aussehen eines diffusen Ausflusses zu simulieren wurden mehrere Ansätze probiert. Das Problem des rein zwei dimensionalen Ansatzes war, dass die simulierten Ausflüsse im Gegensatz zu den realen Ausflüssen nicht “diffus” genug aussehen und die Veränderung des Brechungsindexes in den Videos nicht schnell genug ist. Betrachtet man die Veränderung des Brechungsindex als Schwingung, so war diese nicht hochfrequent genug. Aus diesem Grund haben wir versucht mehrere diffuse Ausflüsse hintereinander zu platzieren, sodass die von der Kamera ausgesendeten Strahlen von mehreren Ausflüssen gebrochen werden, bevor diese auf den Hintergrund treffen.
Allerdings stellte sich heraus, dass die Berechnungszeit mit dem Hinzufügen weiterer AnsysObjects rapide anstieg und die Berechnungsdauer eines Frames weit mehr als eine Stunde betrug, bei nur drei hintereinander platzierten 2D AnsysObjects. Ein Großteil der Berechnungsdauer wird beim Raytracen mit der Berechnung der Schnittpunkte eines gegebenen Strahls mit den Objekten der Szene verursacht. Um dies zu umgehen implementierten wir ein sogenanntes AnsysMultObject, das prinzipiell nur eine Liste von hintereinander platzierten AnsysObjects darstellt.
Die Besonderheit in der Implementierung ist aber, dass nur das vorderste AnsysObject bei der Berechnung des Schnittpunkts eines primary rays, also eines Rays, der von der Kamera ausgeht, in Betrachtung gezogen wird. Wird ein Schnittpunkt errechnet, so wird der Strahl gebrochen und der Schnittpunkt mit dem nächsten, also dahinterliegenden AnsysObject berechnet. Dies wird so lange fortgeführt, bis der Strahl entweder seitlich oder hinten aus dem AnsysMultObject austritt.
Der Vorteil der verkürzten Rechenzeit ist offensichtlich, jedoch funktioniert diese Methode nur, wenn das AnsysMultObject mittig vor der Kamera platziert wird. Der Grund dafür ist, dass primary rays, die keinen Schnittpunkt mit dem vordersten AnsysObject, aber dafür mit dahinterliegenden haben, von diesem ignoriert und nicht gebrochen werden.
3D-Ansys-Objekt als Lösungsansatz
Neben der Möglichkeit, mehrere zweidimensionale AnsysObjects übereinander zu schichten, wurde auch ausprobiert, mithilfe von Ansys direkt eine dreidimensionale Geometrie zu erstellen. Die durch die Berechnung erstellten Dateien enthalten dann dementsprechend die physikalischen Daten für alle Koordinaten innerhalb dieser 3D-Geometrie. Dazu musste ein weiteres Objekt für den Raytracer erstellt werden - das Ansys3DObject, das diese dreidimensionalen Daten übernimmt.
Die Datenstruktur kann man sich als dreidimensionales Gitter vorstellen, wobei an jedem Knoten des Gitters eine Temperatur gespeichert ist. Durch lineare Interpolation kann nun an jedem Punkt im Volumen, welches durch das Gitter dargestellt wird, die Temperatur und somit auch der Brechungsindex berechnet werden. Da dieses Volumen also inhomogen ist, was die Temperatur und den Brechungsindex betrifft, biegt sich ein “Lichtstrahl” innerhalb des Volumens, anstatt nur an der Oberfläche gebrochen zu werden. Dieser Vorgang wird im Raytracing-Verfahren dadurch angenähert, dass der Strahl für eine bestimmte Strecke verfolgt wird, dann die aktuelle Position im Volumen berechnet wird und mit dem Brechungsindex an genau diesem Punkt gebrochen wird. Dies geschieht so lange, bis der Strahl wieder aus dem Volumen austritt. Je kürzer die Strecke ist, die der Strahl innerhalb des Volumens verfolgt wird, desto genauer wird die Kurve approximiert, welche der Strahl in der Realität nehmen würde.
Problematisch an diesem Ansatz ist vor allem die Rechenzeit. Durch die erheblich höhere Anzahl an zu berücksichtigenden Punkten bei einem Ansys3DObject dauert der Renderprozess des Raytracers bedeutend länger als bei zweidimensionalen AnsysObjects oder beim Lösungsansatz AnsysMultObject. Darüber hinaus dauert vor allem auch der Berechnungsprozess innerhalb von Ansys weit länger.
Weiterhin hat auch der Aufbau der von Ansys erstellten Dateien einige Probleme bereitet. Bei der Berechnung dreidimensionaler Geometrien hat sich gezeigt, dass die Koordinaten in diesen Dateien scheinbar keiner nachvollziehbaren Sortierung folgen und darüber hinaus einige Koordinaten zu fehlen scheinen. So kam es zu fehlerhaften Abbildungen der Schlieren. Mithilfe einiger Tricks ist es gelungen, diese Schwierigkeiten zu umgehen, trotzdem scheint die Schichtung mehrerer zweidimensionaler AnsysObjects die einfachere Lösung zu sein, da er Arbeitsaufwand für echte 3D-Simulationen erheblich größer ist.
Ergebnisse
Das Resultat der Simulationsgruppe sind einerseits die Simulationen, die im Laufe der Projektarbeit entstanden, und andererseits der Raytracer, der es auch zukünftig ermöglicht, andere Ansys-Berechnungen zu nutzen und damit weitere Simulationen zu erzeugen. Folgende Simulationen sind Beispiele für die Arbeit mit dem Lösungsansatz Schichtung:
Die nächste Simulation wurde mithilfe eines dreidimensionalen Ansys-Objekts erstellt.
Zusammenfassend sind die Ergebnisse durchaus zufriedenstellend, auch wenn Verbesserungen noch möglich sind. Vor allem die Renderzeit des Raytracers kann noch optimiert werden, sodass ein schnelleres Rendern und somit auch ein flexibleres Arbeiten möglich ist. Die Auswertung der Simulationen durch die Detektionssoftware ist auf der Seite der Detektion beschrieben - die simulierten Schlieren werden von der Software wie erwartet erkannt.
Laufzeit
Raytracing ist ein sehr rechenintensiver Prozess, daher kann die Berechnungszeit von Frames mit steigender Pixel- und Objektanzahl rapide ansteigen.
Da der von uns implementierte Raytracer in seiner Grundfunktionalität eher simpel ist und noch nicht auf Geschwindigkeit optimiert ist, haben wir auch mit langen Berechnungsdauern zu kämpfen.
Um zu testen, wie sich die Berechnungsdauer in Abhängigkeit zu den Objekten in der Szene verhält, haben wir einige Tests durchgeführt. Alle Tests wurden auf einem Laptop mit einem i7-8550U (4x 1,8 - 4,0GHz) durchgeführt.
Zunächst haben wir lediglich ein Objekt mittig in der Szene platziert und mit fester Bildauflösung gerendert und die Ergebnisse tabellarisch festgehalten.
- Bildauflösung: 1280x720 (720p)
- Ein SceneObject pro Szene
- Das SceneObjekt bedeckt ca. 1/3 des gesamten Frames
SceneObject | Dauer | Anmerkungen |
---|---|---|
Rectangle | 2s | |
Triangle | 2s | |
Sphere | 8s | |
Image | 4s | |
AnsysObject | 4m 6s | - 90.000 (300x300) Temperaturpunkte |
AnsysMultObject | 1h 20m | - bestehend aus 9 AnsysObjects mit je 90.000 Temperaturpunkten |
Ansys3DObject | 2h 16m | - ~11,4 Millionen (225x225x225) Temperaturpunkte |
Bei diesen Daten ist zu erkennen, dass die Laufzeit des Programms für einen Frame beim AnsysMultObject extrem groß ist, obwohl dies aus neun hintereinander platzierten AnsysObjects besteht. Außerdem wurde in der Implementierung ein Trick verwendet, der die Rechenzeit bereits stark verringert. So werden für die von der Kamera ausgesendeten Strahlen nicht alle neun AnsysObjects auf Schnitt überprüft, sondern lediglich das Vorderste. Erst wenn ein Schnittpunkt mit dem vordersten AnsysObject gefunden wird, wird das dahinterliegende AnsysObject auf Schnitt überprüft, was die Ausführung der Methode, die Strahlen und Objekte auf Schnitt überprüft, verringert.
Würde ein linearer Zusammenhang zwischen der Anzahl der AnsysObjects und der Laufzeit existieren, so könnte man für den Frame mit dem AnsysMultObject eine neunfach so lange Laufzeit erwarten, die also ungefähr 36 Minuten (9 * 4 Minuten) betragen würde.
AnsysObject3D
Wie bereits beschrieben, verursacht die extrem große Anzahl an Datenpunkten im AnsysObject3D die langen beobachteten Renderzeiten für entsprechende Frames.
Da mit Renderzeiten von über zwei Stunden bereits das Erzeugen von wenigen Sekunden Videomaterial mehrere Tage dauert, wurden einige Anpassungen vorgenommen um die Berechnungszeiten zu verringern. Zunächst wurde die Auflösung der 3-dimensionalen Datenpunkte reduziert. Dadurch wurden die ursprünglich knapp 11 Millionen Datenpunkte auf ca. 185.000 Datenpunkte runtergerechnet, was erreicht wurde, indem der Würfel der Länge 225x225x225 auf 57x57x57 verkleinert wurde.
Mit Hilfe dieser Reduzierung wurden visuell vergleichbare Ergebnisse produziert und die Renderzeiten auf knapp 10 Minuten pro Frame verkürzt.
Ein weiterer wichtiger Faktor ist die Tiefe des Objekts, da die Kurve, die der Strahl im AnsysObject3D zurücklegt, anhand einer festgelegten Schrittdistanz berechnet wird. Ist das Objekt tiefer, so müssen mehr Schritte berechnet werden, bis der Strahl wieder aus dem Objekt austritt. Bei einer Verdopplung der Tiefe des AnsysObject3D konnten wir eine Verlängerung der Berechnungszeit um 50% auf 15 Minuten für den vorher in 10 Minuten berechneten Frame, feststellen.
Mögliche Verbesserungen
Es gibt eine Vielzahl von Möglichkeiten, die Geschwindigkeit von Raytracing zu verbessern, die teilweise intensiv in der Fachliteratur behandelt werden.
Da beim Raytracing prinzipiell die Farbe eines jeden Pixels unabhängig von den anderen Pixeln im Bild berechnet wird, eignet sich diese Rechenoperation sehr gut für Parallelisierung. Da der Raytracer momentan noch auf der CPU ausgeführt wird, könnte man entweder hier die Fähigkeiten moderner Prozessoren mit mehreren Kernen und Threads ausnutzen oder die Berechnung direkt auf der Grafikkarte ausführen, die grundsätzlich für parallele Berechnungen ausgelegt sind und somit einen Geschwindigkeitsvorteil gegenüber Prozessoren haben sollten.
Unsere Versuche die Berechnung auf dem Prozessor mit der “OpenMP” Programmierschnittstelle zu parallelisieren, führten in unserem Fall leider zu keiner Beschleunigung, wobei wir uns sicher sind, dass dies mit optimiertem Code möglich wäre.
Die Portierung des Codes auf die CUDA Grafikkarten Schnittstelle stellte sich als äußerst schwierig heraus, könnte aber für zukünftige Projekte ein interessantes Arbeitsfeld sein, das die Geschwindigkeit extrem verbessern könnte.
Bilderreihe Raytracing
Im Folgenden wird in einer Bilderstrecke beschrieben, wie wir Schritt für Schritt den Raytracer aufgebaut haben. Die grundliegende Idee ist es, Strahlen von der Kamera aus durch jeden Pixel eines Schirms zu schießen. Der Schirm wird durch ein Array[Höhe*Breite] repräsentiert, an jeder Stelle wird die Farbe in Form eines RGB-Wertes gespeichert. Die Farbe ergibt sich aus der Farbe der Oberfläche, die vom Strahl getroffen wurde.
Grob kann man es sich so vorstellen:
Gezeigt werden nun sichtbare Erfolge, die wir während der Programmierung des Raytracers chronologisch erreicht haben.
Zunächst werden die Strahlen am Schirm abgefangen. Hier ergibt sich der RGB-Farbwert an jeweiliger Stelle durch:
- Rotanteil: (aktuell getroffener Breitenpixel / Gesamtbreite) * 255
- Gelbanteil: (aktuell getroffener Höhenpixel / Gesamthöhe) * 255
- Blauanteil: Konstant 0.5 * 255
Nun sollten erste Szenen gebaut werden können. Dazu müssen die Strahlen durch den Schirm durchschießen. Dann soll für jeden Strahl berechnet werden, ob er ein Objekt der gebauten Szene schneidet.
- Wenn ja: Farbe des geschnitten Objekts wird an entsprechender Stelle im Array gespeichert
- Sonst: Im Array wird an entsprechender Stelle die vorher bestimmte Hintergrundfarbe gespeichert
Nachdem wir erfolgreich die ersten Objekte rendern konnten, sollten diese nun auch 3-dimensional erscheinen. Dazu haben wir Lichtquellen implementiert. Die Lichtintensität an der getroffenen Oberfläche wird über den Winkel zu vorhandenen Lichtquellen berechnet.
Um Oberflächen komplexer zu gestalten und Phong Reflektionen zu ermöglichen, haben wir Materialien implementiert. Objekte wie Kugeln können diese annehmen und geben dessen Werte zurück, wenn sie getoffen werden.
Wo es Licht gibt, müssen natürlich auch Schatten fallen. Das funktioniert folgendermaßen:
Trifft ein Strahl auf eine Oberfläche, wird der Weg von diesem Punkt aus zu allen Lichtquellen berechnet. Dabei wird geschaut, ob auf dem Weg andere Objekte liegen, die Schatten auf den getroffenen Punkt werfen.
Als nächstes haben wir die Materialien um einen reflektiven Index ergänzt. Dieser besagt, zu welchem Grad eine Oberfläche spiegelnd ist.
Des Weiteren haben Materialien noch einen Brechungsindex erhalten, um Lichtbrechungen zu ermöglichen - ein sehr wichtiges Feature für unsere späteren Zwecke.
Jetzt mussten hauptsächlich noch weitere Objekttypen implementiert werden. Dazu gehören beispielsweise Dreiecke, Hintergrundbilder, die aus PNG-Dateien gelesen werden können, Ansys-Objekte und viele weitere.
Werkzeuge
Für Interessierte folgt hier eine Liste der Tools, die wir im Rahmen der Arbeit in der Simulationsgruppe genutzt haben:
HackMD bietet die Möglichkeit, gemeinsam online an Markdown-Dokumenten zu arbeiten.
Ansys Fluent ist eine Softwarelösung, mit der CFD-Simulationen generiert werden können. Unsere Simulationen basieren auf den Berechnungen aus dieser Software. OpenFoam ist ein alternatives CFD-Tool, mit dem wir ebenfalls gearbeitet haben. Wegen der einfacheren Bedienbarkeit haben wir uns final für Ansys Fluent entschieden.
CLion ist eine plattformübergreifende IDE für C und C++ und wurde für das Programmieren des Raytracers benutzt.
FFmpeg wurde genutzt, um aus den gerenderten Bildern des Raytracers Videos und Gifs zu generieren. IrfanView und Virtual Dub sind alternative Lösungen, die wir ebenfalls ausprobiert haben, um Bilder zu konvertieren und zu Videos zusammenzusetzen.
Titelhintergrund Einflusssimulation (Eigenes Bild)