blog · git · desktop · images · contact


Art of Illusion: Einführung in die Fluid-Simulation

2007-10-26

Als Einführung ins Arbeiten mit delt0r's Fluid-Plugin soll nun dieses Tutorial dienen. Es ist an das originale How-To angelehnt, geht aber mehr auf die "Kleinigkeiten" in der Benutzung und Hintergründe ein - und ist vorallem auf die aktuelle Beta 0.6 abgestimmt.

Die folgende Einleitung kann bei Desinteresse oder Zeitmangel übersprungen werden, ist für besseres Verständnis aber bestimmt ganz gut geeignet. ;)

Dieser Text kann hier auch als PDF heruntergeladen werden.

Wie funktioniert das überhaupt?

Zuerst einmal: Woran orientiert sich die Simulation? Was wird zu "Wasser" und "wie"? Normalerweise hat man es ja nur mit festen, soliden Objekten zu tun, die nicht brechen oder bröckeln - sie haben eine feste räumliche Begrenzung. Flüssigkeiten hingegen sind eben nicht fest. Wirft man ein Objekt in ein Glas voller Wasser, so spritzt es - und zwar unregelmäßig, kein Tropfen ist genauso groß wie ein anderer. Die Fluid-Simulation krempelt AoI allerdings nicht von grundauf um, es stehen nach wie vor zum Beschreiben der Szene nur Punkte, Kanten und Flächen zur Verfügung. Mit diesen "Werkzeugen" müssen nun Flüssigkeiten und auch weiterhin feste Objekte beschrieben werden.

Die Problematik ist also: Wie beschreibt man mit Punkten, Kanten und sich daraus ergebenden Flächen einen Bottich voller Wasser?

Natürlich bietet es sich an (und diesen Weg ist delt0r auch gegangen), "Punkte" zu kleinen "Partikeln" zu machen - die bisher bekannten mathematischen Punkte bekommen eine Ausdehnung geschenkt und werden somit eigentlich zu kleinen Kugeln. Man kann sich einen Wasserstrahl in der Simulation also wie einen Strom von lauter kleinen Kügelchen vorstellen, die nur zusammen im Großen so aussehen, als wären sie ein zusammengehöriger Strahl (genauso ist es ja auch in der Realität). Klar ist dabei, dass sich ein umso realistischeres Bild ergibt, je kleiner diese Kugeln sind. Man betrachte gedanklich einfach einmal einen "Strahl" von lauter Basketbällen aus einem Meter Entfernung: Wirklich "flüssig" sieht das nicht aus. Nimmt man dagegen viele kleine Murmeln, kommt das schon eher an das Aussehen einer Flüssigkeit heran.

Bedenken muss man allerdings, dass es bei kleineren Partikeln wesentlich länger dauert, diese zu simulieren, weil es einfach auch viel mehr Partikel werden. Das zeigt dann schnell die Grenzen auf: 100% realistische Fluids wird man am Computer (egal mit welcher Software) zur heutigen Zeit nicht berechnen können. Ein kurzer Blick in die Wikipedia zum Artikel über das Wassermolekül zeigt, wie klein die simulierten Partikel sein müssten - im Bereich von Picometern, das ist technisch im Moment absolut nicht drin. Vielleicht wird das einmal möglich sein, aber heute kann man sich nur mit mathematischen Annäherungen weiterhelfen.

Nichtsdestotrotz sind diese Annäherungen ziemlich genau und benötigen immernoch recht lange Berechnungszeiträume. Daher muss einem klar sein: Einen Ozean oder auch nur einen Teich kann man nicht simulieren. Schon bei einer Badewanne wird man an die Grenzen der Geduld stoßen. Das hat jedoch nichts mit delt0r's Plugin oder Java zu tun, sondern ist ein generelles Problem.

Das waren nun die Flüssigkeiten - doch wie werden Hindernisse, also z.B. eine solide Rampe, beschrieben? delt0r ging hier bisher den einfachsten Weg: Wie die Fluids werden auch Hindernisse mit Punkten bzw. Partikeln beschrieben. Auf den ersten Blick wirkt das etwas seltsam, denn man hat mit Flächen/Faces doch bereits völlig solide Objekte, wieso sollten auch diese in kleine Kügelchen aufgeteilt werden? Die Antwort wird vermutlich sein, dass es sich so leichter rechnen lässt. ;) Diese Tatsache muss man jedoch unbedingt im Hinterkopf behalten, denn unsere Rampe sieht intern für die Simulation etwa so aus:

Rampe: Vertices werden zu Hindernissen

Betrachtet man dieses Bild, fallen einem wohl sofort die "Lücken" auf - ein solides Objekt ist das jetzt nicht mehr. In der Tat ist es so, dass für solide Hindernisse jeder Punkt des Meshes zu einem "Hindernis-Partikel" wird: Der Punkt bekommt eine Ausdehnung (wird also zu einer Kugel) und ist als "fest" markiert, sodass die beweglichen Flüssigkeitspartikel ihn auch wirklich als Hindernis betrachten.

Okay, fassen wir einmal kurz zusammen:

Konkret am Beispiel - Vorbereitung der Szene

Genug der grauen Theorie, setzen wir's mal um. Ich werde hier im Gegensatz zum originalen Tutorial schon ein bisschen praxisnäher sein und direkt mit PolyMeshes arbeiten. Allerdings kommt das Plugin noch nicht so ganz mit PM's zurecht, weswegen diese später in (nicht sichtbare) TriMeshes zurückverwandelt werden. Dargestellt werden soll eine Tasse, die gerade mit Milch gefüllt wird.

Beginnen wir also mit besagter Tasse. Darauf werde ich jetzt nicht weiter eingehen, da es einerseits nicht besonders schwer ist und Vidiot das andererseits selbst einmal in einem Tutorial erklärt hat. Das Ergebnis sollte dann etwa so aussehen:

Fertiger Becher

Man beachte hier unbedingt die Größenverhältnisse - kleiner darf der Becher nicht ausfallen!

Als nächstes erstellen wir eine Quelle für die Flüssigkeit. Es ist so, dass prinzipiell jedes beliebige Objekt eine Quelle sein kann - "aus" den Punkten des Objekts wird ein "Strahl" der Flüssigkeit fließen. Das klingt jetzt wohl recht unverständlich, wird aber gleich klar werden. Wir erstellen ein einfaches Face und nichts weiter. Dieses Face wird dann ein bisschen geneigt sein, denn der Strahl soll nicht einfach gerade nach unten schießen, und über der Tasse schweben. Da wie gesagt jeder Punkt eine eigenständige Partikelquelle sein wird, muss das Face entsprechend unterteilt werden, um einen "geschlossenen" Strahl und nicht nur vier kleine davon an jeder Ecke des Faces zu erhalten. Insgesamt also etwa so:

Fluid-Quelle

Achtung: Es ist wichtig, dass das Plugin weiß, wo "oben" und "unten" ist. Das bestimmt es über die Normalen, die auf den Faces stehen (welche man meines Wissens nach aber leider in AoI nicht sichtbar machen kann). Man wird später die Möglichkeit haben, der austretenden Flüssigkeit entlang dieser Normalen eine gewisse Geschwindkeit zu geben, sodass unsere Milch nicht alleine durch die Gravitation beschleunigt wird.

Da man die Normale aber nicht sieht, muss man aufpassen, dass das lokale Koordinatensystem des Objekts (= der Quelle) und das globale System der Szene übereinstimmen. Im Klartext heißt das: Die Quelle sollte keinen Rotationsmodifikator haben (Quelle rechts anklicken, "Object Layout", dort sollte dann bei "Orientation" überall eine 0 stehen).

Der Einfachheit halber sollte man die Quelle also in der Frontansicht erstellen (einfach ein PM-Object und zwar "Single Face"), dann editieren, alle Punkte markieren und um 90° in der X-Achse drehen ("Vertex" -> "Transform points").

Unterteilen kann man die Quelle nun, bis man etwa 2-4 Punkte auf einer Längeneinheit hat. Ein Bild kann das sicherlich besser erklären als ich, obwohl ich hier fast schon ein bisschen zu fein unterteilt habe:

Fluid-Quelle: Größenverhältnisse

Nachdem nun sichergestellt wurde, dass die Normalen stimmen, kann man die Quelle nachträglich noch ein bisschen kippen - wie man sieht, habe ich sie hier um 20° um die Z-Achse gekippt:

Fluid-Quelle: Positionierung

Da wir die "Quelle", also das einzelne Face, nachher in der Szene nicht sehen wollen, verstecken wir es.

Für unsere Quelle soll das nun genügen. Jetzt muss der Becher so bearbeitet werden, dass er für die Simulation ein sinnvolles Hindernis darstellt. Wie auf dem obigen Screenshot zu sehen, war ich sehr sparsam bei den Punkten, die den Becher beschreiben. Würde man ihn unverändert lassen, würde die Milch an vielen Stellen einfach so durch die Tasse durchfließen. Es müssen nun zwei Schritte ausgeführt werden:

Der Becher muss in ein TriMesh konvertiert werden. Dazu erstellt man einfach eine Kopie von ihm, nennt diese um nach "Becher Tri" (Es ist wichtig, dass alle Objekte, die man in der Simulation verwenden will, eindeutige Namen haben! Diese Beschränkung fällt in der nächsten Version weg.), klickt ihn dann rechts an und wählt "Convert to triangle mesh." Editiert man den Becher dann, sieht er so aus:

Triangulierter Becher

Damit bleibt uns der zweite Schritt hier erspart: Denn der Becher ist bereits fein genug unterteilt. Falls man größere Bereiche auf seinen Objekten entdeckt, in denen sich kaum Punkte befinden, so müsste man wie folgt vorgehen: Alle Kanten/Edges auswählen und dann "Mesh" -> "Subdivide selected edges" auswählen. In jedem Fall sollte sichergestellt werden, dass das Mesh auf "Approximated smoothing" geschaltet ist ("Mesh" -> "Smoothing method").

Achtung: Bei eckigen Objekten wie z.B. Kisten empfiehlt es sich, Smoothing (sowohl im PME vorher, als auch im TME jetzt beim Unterteilen) die ganze Zeit über zu deaktivieren und erst ganz am Ende, wenn das Objekt in ein TriMesh umgewandelt und unterteilt wurde, wieder zu aktivieren. Damit vermeidet man unnötige Punkte auf der Oberfläche. Da unser Becher sehr rund ist, spielt das für uns (noch) keine Rolle.

Weil wir den TriMesh-Becher nur zur Berechnung brauchen und nicht auf dem Bild haben wollen, können wir ihn getrost als "versteckt" markieren. Durch diese Trennung von fein unterteilten Meshes nur zur Berechnung und "groben" Meshes zur Darstellung wird auch das Rendern stark beschleunigt!

Die Physik kommt ins Spiel

Da wir nun unsere Szene fertig aufgesetzt haben, kann delt0r's Plugin beginnen. Dazu wählen wir links den Knopf mit den Tropfen darauf aus und ziehen in der Frontansicht (siehe oben, die Koordinatensysteme sollten übereinstimmen!) einen Kasten rund um Quelle und Tasse herum auf. Das kann eventuell ein bisschen frickelig werden, weil man in der Frontansicht die Quelle nicht sieht - aber daran sollte es nicht scheitern. ;) Auf jeden Fall sollte nach allen Seiten hin ein bisschen Platz nach Innen gelassen werden, sprich: den Kasten nicht unbedingt haarscharf am Rand zu allen Objekten aufziehen. Bildlich gesprochen:

Pre-Bake-Zustand

Nun das neue "PhysicsObject 1" in der Objektliste doppelt anklicken. Hier findet sich eine ganze Menge Optionen, aber der Reihe nach. Zuerst markieren wir die Objekte als das, was sie sind: "Becher Tri" ist ein Hindernis und "Quelle" eine Quelle. Der Rest der Szene ist uns egal.

Im Tab "Object Management" also in der Liste den Eintrag "Quelle" auswählen und dann in der Drop-Down-Box "EMITTER". Die Optionen im Einzelnen:

Der "Becher Tri" soll nun als Hindernis markiert werden, dazu "BOUNDRY" auswählen ("FLUID", "SOFT" und "EFFECTOR" haben übrigens noch keinerlei Funktion). Hier gibt es nur die Option "is static" - da wir keine Animation machen, bleibt der Haken drin.

Im nächsten Tab "Previews & veiws" (:D) gibt es eine ganze Reihe Optionen, die jetzt noch keine Rolle spielen - dazu komme ich später.

Der Tab "Fluid params" ist nun schon relevanter: Hier findet man vorallem den Abstand zwischen zwei Berechnungsschritten ("step size"), die Größe eines Partikels und die Gravitationsverhältnisse ("body acceleration"). Die Zeitschritte lassen wir mal so, ebenso die Partikelgröße - die Gravitation zeigt in negative Y-Richtung, was genau richtig für uns ist.

"Advanced" ist erstmal völlig egal und dann sind wir auch schon beim "Baking" - damit wird das eigentliche Simulieren der Flüssigkeiten bezeichnet. Allerdings führt mich das jetzt auf den ersten Blick in einen kleinen Widerspruch, denn ich habe die ganze Zeit gesagt, dass wir keine Animation machen wollen. Doch wie soll ein Vorgang, der sich über eine gewisse Zeit erstreckt, simuliert werden, wenn keine Zeitdimension hinzugenommen wird? Also haben wir eben doch eine Animation. Zumindest benutzt das Fluid-Plugin die in AoI ohnehin vorhandene Zeitskala - ob man nun die ganze Simulation in einem kleinen Film rendert oder nur ein Einzelbild daraus, ist natürlich egal. Man kann die Zeitskala übrigens ganz normal nutzen, so wird man auch sehen können, wie sich der Partikelfluss entwickelt.

Auch ist es möglich, z.B. erst eine Sekunde simulieren zu lassen und dann den "Fluss" abzustellen, sprich: Man markiert die Quelle schlichtweg nicht mehr als "EMITTER", setzt den Zeitschieber genau auf "1.0" Sekunden und lässt dann bspw. 2 weitere Sekunden berechnen. Hier muss allerdings vorher auf "Clear object cache" geklickt werden (keinesfalls aber auf "Reset simulation"!), sodass das Plugin mitkriegt, dass wir den Hahn zugedreht haben. Die "Bake end time" ist auch absolut, "2.0" würde also bedeuten, dass er auch bei "2.0" aufhört und nicht etwa zwei weitere Sekunden simuliert.

Für uns ist das aber erstmal egal, wir schreiben bei "Bake end time" ein "5.0" rein, wählen eventuell eine andere Ausgabedatei (diese ist nur für die Simulationsdaten zuständig und kann bis über 100MB groß werden!) und klicken dann erstmal auf "Bake". Je nach Rechner kann das jetzt einen kleinen Moment dauern, bei mir war es nach etwa 30 Sekunden fertig. Im Prinzip ist die Simulation an dieser Stelle fertig, man kann sich per Zeitschieber den gewünschten Moment heraussuchen und dann das Bild rendern - oder falls gewünscht auch den ganzen Bereich von 0 bis 5 Sekunden. Ein Beispiel wie es aussehen könnte:

Simulierte Szene in der Vorschau

Ich habe hier bewusst diesen Moment gewählt, da man in der Frontansicht oben links etwas erkennen kann: Die Partikel prallen zu früh von der Tasse ab, bevor sie sie überhaupt erreichen. Das ist ein Problem, auf das ich im Abschnitt "Kritikpunkte und Optimierung" noch genauer eingehen werde.

Preview

Aktiviert man im PhysicsObject unter "Previews & views" den Punkt "Mesh preview", dann erhält man ohne einen Raytracer-Durchgang direkt im Szenenfenster schon ein ungefähres Bild. Die "MeshSize" gibt dabei an, wie genau berechnet werden soll: Je kleiner der Wert, desto genauer das Ergebnis. Wir tragen jetzt einmal "0.5" ein und klicken auf "Ok". Was sieht man? Der Preview ist extrem grob. Man kann den Wert hinter "MeshSize" jetzt in 0.25er Schritten verkleinern, bis man annäherungsweise sieht, ob es passt oder nicht. Doch Vorsicht: Die Berechnung kann sehr lange dauern!

Mesh-Preview

Rendern

Rendern mit dem Raytracer ist im Moment noch sehr langsam - mit dem Raster-Renderer jedoch sehr schnell. Für Animationen ist also letzerer zu bevorzugen, allerdings sollte man bedenken, dass dann die Flüssigkeiten nicht "rund" dargestellt werden, sondern als kleine eckige Kästchen. Für Testdurchgänge ist das aber ok.

In jedem Fall sollte aber das PhysicsObject noch einmal geöffnet werden und dort der Tab "Previews & views". Dort gibt es die Option "Use sample grid for raytracing." Diese verringert die Auflösung der Flüssigkeit ein bisschen, die Tropfen werden also ein bisschen kantiger. Dadurch wird der Renderprozess aber auch erheblich beschleunigt - gerade bei transparenten Flüssigkeiten und Antialiasing ist diese Option unbedingt zu aktivieren!

Ein vorläufiges Endergebnis des Bechers mit Milch könnte erst einmal so aussehen:

Endergebnis

Texturierung und Materialien

Hier gibt es eigentlich nicht viel zu sagen: Das PhysicsObject bekommt alle Texturen und Materialien zugewiesen, die die Flüssigkeit haben soll. Rendern mit dem Raytracer von transparenten Flüssigkeiten ist im Moment (vorallem im Zusammenhang mit Antialiasing) aber noch wirklich sehr langsam. Von Global Illumination und solchen Späßen ganz zu schweigen.

Kritikpunkte und Optimierung

Okay, besonders schön ist das Zwischenergebnis von oben nicht. Folgende Dinge gibt es zu kritisieren:

Punkt eins ist "einfach" zu beheben: "Particle size" reduzieren und sicherstellen, dass die Tasse fein genug unterteilt ist! Da die Partikel nun kleiner sind, können sie natürlich auch durch kleinere Löcher hindurchlecken, ergo muss man das Objekt "Becher Tri" eventuell noch einmal zusätzlich unterteilen. Auch kann es sein, dass man zusätzlich sogar noch die Quelle neu unterteilen muss, um auch weiterhin einen "zusammenhängenden" Strom zu haben!

Gekoppelt ist eine Reduzierung der Partikelgröße auch oft an eine Reduzierung der "Step size". Grob lässt sich sagen: Werden die Partikel halbiert, muss auch die "Step size" halbiert werden. Hat man genug Zeit übrig, kann man aber auch erst einmal die "Step size" so lassen, wie sie ist, und es ausprobieren - manchmal klappt es auch so. Ganz sicher muss diese reduziert werden, wenn man auf einmal "wild" durch die Gegend fliegende Partikel erhält:

Wilde Partikel

Punkt zwei ist nicht mehr ganz so einfach. Wie eingangs erwähnt, arbeitet das gesamte Plugin auf der Basis von Punkten/Vertices. In unserem Fall befinden sich am oberen Rand der Tasse zu viele Punkte, welche eben alle keine Punkte mehr sind, sondern eine Ausdehnung haben! Dadurch wird der Rand nach oben hin dicker als er eigentlich ist. Hier hilft nur manuelles Handanlegen und Ausprobieren, sprich: viele Testläufe und viel Zeit. Man könnte entweder überschüssige Punkte/Vertices entfernen oder z.B. den oberen Rand nach unten hin verschieben.

Manchmal ist es auch nötig, ganz eigene TriMeshes, völlig unabhängig vom ursprünglichen Objekt zu erstellen, die dann als Hindernisse dienen. Das ist aber ein Punkt, der sich in Zukunft hoffentlich erübrigen wird, da es dann eine "Auto subdivision" geben wird.

Zum Abschluss

Da dieser Text hier schon wieder viel länger geworden ist, als ich ursprünglich dachte, wird auch direkt eines klar: Der Umgang mit der Fluid-Simulation ist kein Hexenwerk, aber auch an manchen Stellen nicht trivial. Man sollte anfangs auf jeden Fall viel damit rumspielen, um ein Gefühl für die Sache zu bekommen.

Ich habe viele Optionen gar nicht erklärt? Nun, das ist einfach: Ich weiß noch nicht, was sie bedeuten. ;) Die ganze Simulation ist noch tief im Beta-Stadium und was zum Beispiel noch fehlt, ist eine ausführliche Dokumentation! Bedenkt man aber den frühen Entwicklungsstand, dann kann man schon erstaunlich viel damit anstellen. Beispiele dafür habe ich ja schon genug geliefert. :)

Comments?