28 KiB
TODO:
- Was ist eine Szene
- Was sind ViewLayer (und wie kann man sie verwenden, wie verwenden wir sie) https://docs.blender.org/manual/en/2.81/render/layers/layers.html
- Was sind AOVs https://docs.blender.org/manual/en/latest/render/shader_nodes/output/aov.html
Compositor
Blenders hat einen mächtiges Compositor-System, welches die Möglichkeit bietet für die Filmproduktion oder Grafikerstellung verschiedenste Effekte auf die Render-Ergebnisse anzuwenden und diese als Bilder abzuspeichern.
Wie auch für Materialien und Geometrie-Erzeugung bietet Blender dazu einen Node-basierten Editor. In diesem können Eingangs-Nodes definiert werden, welche bestimmte Bilder oder Werte für die Nachbearbeitung bereitstellen. Diese können dann nach Bedarf mit weiteren Nodes - z. B. Farbe zu Graustufen umwandeln - nachbearbeitet werden und anschließend abgespeichert werden. Es ist auch möglich, mehrere Bilder zu einem zu vereinen.
Es gibt grundsätzlich zwei Input-Nodes, die verwendet werden können, um auf verschiedene Render-Ergebnisse zuzugreifen:
Zunächst sind das die Render Layer-Nodes. Diese erlauben es auf die Render-(Zwischen-)Ergebnisse eines ViewLayers zuzugreifen. Dazu kann man im Node die Szene und den ViewLayer aus der Szene auswählen. Die Ausgänge Image und Alpha sind immer vorhanden. Image ist das "fertig" gerenderte Bild des ViewLayers, mit Farben, Schatten, Glanzlicht, etc. angewandt. Der Alpha-Ausgang gibt Zugriff auf eine Textur, die für jeden Pixel angibt, wie durchsichtig dieser ist. Ein Alpha-Wert von 1 gibt an, dass der Pixel vollständig undurchsichtig (opaque) ist. Ein Wert von 0 gibt an, dass der Pixel unsichtbar ist.
In den Einstellungen des ViewLayers können weitere Ausgänge aktiviert werden. Dies sind einerseits Zwischenergebnisse aus dem Render-Prozess, die für die Berechnung weiterer Effekte wie Beleuchtung nötig sind. Diese Zwischenergebnisse werden oft unter dem Begriff des G-Buffer (Graphicsbuffer) zusammen gefasst und enthalten Daten, die geometrische Informationen der Szene enthalten. Ein Beispiel dafür ist z. B. der Depth-Buffer, welcher für jeden Pixel angibt, wie weit dieser von der Kamera entfernt ist. Weitere übliche sind der Normal-Buffer und Positions-Buffer, wobei ersterer die Orientierung der Oberflächen in der Welt und zweiterer die Position jedes Pixels im 3D-Raum speichert. Zwischenergebnisse der Beleuchtungsberechnung können ebenfalls ausgegeben werden. Die Mathematik, Bestandteile und Zwischenergebnisse der Beleuchtungsberechnung sind bedauerlicherweise jenseits des Umfangs dieser Arbeit und werden daher nicht weiter ausgeführt.
Abbildung XYZ zeigt Beispielhaft verschiedene Render-(Zwischen-)Ergebnisse einer Szene, auf die man mithilfe der Render Layer-Node zugreifen kann. Bei der gezeigten Szene handelt es sich um den Splash-Screen, der beim Starten von Blender 4.5 angezeigt wird. Die Abbildung zeigt das fertige Render-Ergebnis, den Depth-, Normal- und Position-Buffer, Diffuse-Beleuchtung (Direkte Beleuchtung / Streulicht), Diffuse-Color (Unbeleuchtete Oberflächenfarben), Glossy-Beleuchtung (Glanzlicht und Reflektionen) sowie Umgebungsverdeckung.
Außerdem ist es möglich für Szene sogenannte Cryptomatte-Masken zu erzeugen. Cryptomatte ist eine open-source Software, die von Jonah Friedman und Andy Jones von Psyop, einem Film-Produktions-Studio, entwickelt wurde. Es erzeugt für eine 3D-Szene ein Bild, das je nach Konfiguration jedem Objekt, Material oder Asset in der Szene eine Id in Form einer zufälligen Farbe zuweist und ein Bild erzeugt, in dem alle Pixel diese ID-Farbe haben. Abbildung XXYX zeigt Beispielhaft den Splash-Screen von Blender 2.81 sowie die dazugehörige Cryptomatte. Anhand dieser Cryptomatte können dann Segmentierungsmasken für einzelne Objekte extrahiert und verwendet werden. Das besondere an Cryptomatte ist, dass diese Segmentierungsmasken in der Lage sind filmische Effekte wie Bewegungsunschärfe sowie Tiefenschärfe zu berücksichtigen und auch mit transparenten Objekten funktionieren.
---
compareMode: true
---
![[Blender_2.81_splash_screen-render.jpg||1-1]]
![[Blender_2.81_splash_screen-cryptomatte.jpg||1-2]]
Eigentlich wurde Cryptomatte entwickelt, um das Nachbearbeiten und kombinieren von einzelnen Elementen einer Szene zu ermöglichen. Für unseren Anwendungsfall, nämlich die Generierung von Segmentierungsmasken erweist es sich allerdings aus ausgesprochen nützlich, da es damit möglich ist die gewünschten Ground-Truth-Segmentierungsmasken zu erzeugen und abzuspeichern.
Um Cryptomatte verwenden zu können, muss in den Einstellungen der ViewLayer mindestens eine der Cryptomatte-Optionen aktiviert werden: Object erzeugt eine Cryptomatte mit einer einzigartigen Id für jedes Objekt. Material erzeugt eine Cryptomatte mit einzigartiger Id für die einzeln Materialien. Das ist nützlich, da es möglich ist auf einem 3D-Modell mehrere Materialien anzuwenden. In dieser Arbeit wird zum Beispiel ein Modell für Iris und Pupille verwendet. Damit erhalten beide die selbe Objekt-Id in einer Object-Cryptomatte. In der Material-Cryptomatte allerdings erhalten sie unterschiedliche Ids, da auf dem 3D-Modell für Iris und Pupille unterschiedliche Materialen verwendet werden. Die Dritte Art ist Asset. Hier erhalten alle Objekte, die in der Hierarchie das gleiche Elternobjekt haben die selbe Id.
Die Option Levels bestimmt, wie viele Objekte pro Pixel unterschieden werden können. Das ist wichtig, da pro Cryptomatte-Bild nur zwei Ids unterschieden werden können. Je nach konfiguriertem Wert werden halb so viele Cryptomatte-Bilder ausgegeben. Um zum Beispiel sechs Ids pro Pixel unterscheiden zu können sind drei Cryptomatte-Bilder nötig. Im ersten Bild werden die zwei Objekte Unterschieden, die am meisten zu einem Pixel beitragen, im zweiten die Objekte, die am dritt- und viertmeisten beitragen usw.
Diese einzelnen Cryptomatte-Bilder können dann ebenfalls über den Render Layers-Node verwendet werden. In Abbildung ABC sind beispielhaft die drei Cryptomatte-Bilder einer Szene gezeigt. Es ist ersichtlich, dass daraus nicht trivial Segmentierungsmasken erzeugt werden können.
Um dies zu vereinfachen gibt es in Blenders Compositing-Editor die sogenannte Cryptomatte-Node. Wie auch bei der Render Layers_-Node muss zunächst eine Szene und eine ViewLayer ausgewählt werden. Zusätzlich dazu kann die Object-, Material- oder Asset-Cryptomatte ausgewählt werden. Sie hat drei Ausgänge. Zum einen gibt es den Pick-Ausgang. Dieser gibt ein vereintes Bild zurück in dem alle Ids mit unterschiedlichen Farben dargestellt werden. Der zweite Ausgang heißt Matte. Im Feld Matte-Ids können eine oder mehrere Objekt- bzw. Material-Namen eingetragen werden. Die kombinierte Segmentierungsmaske wird dann im Matte-Ausgang bereitgestellt. Wenn ein Bild mit dem Image-Eingang verbunden ist, kommt aus dem Image- Ausgang ein Bild, in dem nur die Pixel der ausgewählten Ids verfügbar sind.
Abbildung BLA zeigt eine Beispielhafte Konfiguration der Cryptomatte-Node. Am Image-Eingang ist das gerenderte Bild verbunden.
---
compareMode: false
---
![[cryptomatte_full_image.png|Vollständiges Bild (Image-Eingabe)]]
![[cryptomatte_pick.png|Pick-Ausgabe]]
![[cryptomatte_matte.png|Matte-Ausgabe]]
![[cryptomatte_masked_image.png|Image-Ausgabe]]
Umbau der Szene:
Da innerhalb eines ViewLayers nur Kollektionen und keine einzelnen Objekte aktiviert und deaktiviert werden können, musste die Hierarchie der Szene angepasst werden. Die Abbildung zeigt die Hierarchie vor dem Umbau.
Die Kollektion Eye enthielt das eye-Objekt, welches die 3D-Geometrie für die Iris und Pupille hat (und daher auch zwei Materialien auf diesem Modell hat). Außerdem hat es ein Kind-Objekt Cornea, welches die Hornhaut und Sclera modelliert. Diese Eltern-Kind-Beziehung wurde hergestellt, damit das Verschieben und Rotieren des eye-Objekts den selben Effekt auf die Sclera hat. Alle Lichtquellen sind in der Kollektion "Lichter" gesammelt. Diese werden aber in der derzeitigen Implementierung nicht mehr verwendet. Camera ist die Kamera, aus deren Perspektive die Szene gerendert wird. Abschließend enthält das Skin-Objekt einerseits die Geometrie für die Haut, andererseits aber auch die Augenbrauen sowie Wimpern. Der Grund für diese Beziehung ist genau wie bei eye und Cornea, die Tatsache, dass Transformationen, die die Haut betreffen auch Augenbrauen und Wimpern betreffen müssen.
Da der Umbau von getrennten Szenen auf ViewLayer bereits einen signifikaten Einfluss auf die Python-Skripte haben würde, da insbesondere viele Objekte, die vorher doppelt oder dreifach existierten und dementsprechen getrennt bearbeitet werden mussten entfallen, war dieser Umbau auch eine gute Gelegenheit die Benennung der Objekte anzupassen, um deren Wiedererkennbarkeit in der Szene und auch im Quellcode zu vererinfachen.
Die Neue Hierarchie ist in Abbiludng ASDF gezeigt. Dies ist auch die Konfiguration des Main-ViewLayers
Die Kollektion "eye" wurde zu "Iris and Pupil" umbenannt. Das darun enthaltene "eye"-Objekt, welches die Iris und Pupille darstellt wurde dementsprechend zu "Iris_Pupil" umbenannt. Das Mesh, das vorher nur "Iris" hieß wurde zu "Iris_Pupil.Mesh" umbenannt. Diese Umbenennung wurde analog auch für die restlichen Meshes durchgeführt.
Neu ist die Kollektion "Cornea and Sclera". Hier ist jetzt das Objekt "Cornea_Sclera" enthalten, welches vorher nur "Cornea" hieß. Diese Trennung in separate Kollektionen erlaubt es, Iris und Pupille sowie Hornhaut und Sclera separat von einander anzuzeigen bzw. auszublenden. Hierbei ist anzumerken, dass die Eltern-Kind-Beziehung weiterhin besteht und dadurch Transformationen weiterhin vom Eltern-Objekt auf das Kind-Objekt übertrage werden. Da sie in unterschiedlichen Kollektionen liegen ist diese Beziehung jetzt nur noch Referenziell. Blender stellt solche Beziehungen ausgegraut in der Hierarchie dar. Außerdem wurde diese Beziehung umgekehrt, da es intuitiv sinnvoller ist, wenn der Augapfel explizit gedreht wird und die Iris implizit folgt.
Zusätzlich wurde das Skin-Objekt mitsamt der Augenbrauen und Wimpern in die Kollektion "Skin and Hair" verschoben, wodurch diese ebenfalls separat ausgeblendet werden können.
Nachdem die Hierarchie angepasst wurde, wurden die ViewLayer erstellt. Dabei wurde zunächst der standard-ViewLayer von "ViewLayer" zu "All" umbenannt. Dadurch wird ersichtlich, dass hier alle Objekte dargestellt werden. Es wurden zwei neue ViewLayer erstellt: "Iris and Pupil only" sowie "No Cornea and Sclera". In ersterem ist, wie der Name schon vermuten lässt, nur die Kollektion "Iris and Pupil" aktiv. Dieser ViewLayer erlaubt es Segmentierungsmasken zu rendern, in denen nur die Iris und die Pupille ohne Verdeckung durch Haut, Augenlider und Wimpern dargestellt sind.
Im "No Cornea and Sclera"-ViewLayer sind alle Kollektionen außer "Cornea and Sclera" aktiviert. Da zur Zeit die Glanzlichter nicht durch tatsächliche Lichtquellen dargestellt werden, sondern Teil der Textur der Hornhaut sind, können diese nicht ohne weiteres separiert werden.
Anschließend wurden die nicht mehr benötgiten Szenen (??? und ???) gelöscht und mit der Option "File -> Clean Up -> Purge unused data..." alle nun nicht mehr benutzten Objekte gelöscht.
Swappen der Hierarchie von Iris und Sclera: Probleme: Der Origin von Sclera und Iris war unterschiedlich, die Sclera war minimal rotiert im Verhältnis zur Iris. Da das Sclera objekt eine Komische Rotation (drei wilde Werte) hatte, konnte nicht einfach ausgerichtet werden. Lösung: Kleines Script, um die Sclera zu rotieren, sodass zwei ausgewählte Vertices parallel zu einer der Globalen Achsen liegen. Zweites Script, um Sclera Objekt so zu verschieben, dass die Spitze des Objekts mit dem Mittelpunkt der Iris eine Liniea parallel zur Globalen X-Achse bildet.
An dieser Stelle wurden bilder gerendert, um zu testen, ob die Ergebnisse sich ändern. Ja, aber nur, weil die Kamera-Position abhängig von der Spitze der Cornea ist, da die jetzt leicht anders Positioniert ist, ist auch der Kamera-Winkel anders.
Dann habe ich die Rotationen applied (Ctrl + A), sodass das gerade nach vorne schauende Auge eine Rotation von 0,0,0 hat, dadurch musste der Code geändert werden. Einerseits musste die "Default-Rotation" für das Auge angepasst werden, da vorher von 90° ausgegangen wurde, ist jetzt 0. Und im Code, der die Öffnung der Pupille macht mussten X,Y durch Y,Z ersetzt werden.
Dann wurden in der Hierarchie die Eltern-Kind-Beziehungen getauscht. Aus irgendwelchen Gründen musste die Rotation dann nochmal applied werden, dann waren die Ergebnisse Identisch.
Das Anwenden der Rotation, sodass diese nun 0,0,0 ist hat den Vorteil, dass dadruch das Auge nicht mehr in einem Gimbal-Lock ist (X und Z Rotation haben beide in die selbe Richtung gedreht).
Segmentierungsmaske für das Augenlid
TODO: gucken, was ich da schon so in der MA geschrieben habe.
In der vorherigen Implementierung mit separatem Augenlid-Modell war es dank Cryptomatte trivial eine separate Segmentierungsmaske für das Augenlid zu erzeugen. Da im hier präsentierten Ansatz allerdings das Augenlid und die restliche Haut das selbe Modell sind, erhalten sie auch die selbe Segmentierungsmaske. Eine einfache Lösung für dieses Problem wäre es, trotzdem für Augenlid und Haut verschiedene Materialien zu definieren, woraufhin in der Material-Cryptomatte dann separate Ids vergeben werden würden.
- Vertex-Group erstellen und alle Vertices des Augenlids auswählen:
- Mit Geometry-Nodes diese Vertex-Group als Attribut im Shader Verfügbar machen:
- Für das Skin-Objekt neuen Geometry-Nodes erstellen.
- Input- und Output-Attribute erstellen und verbinden:
- Unter "Group Sockets" einen neuen Input und Output hinzufügen. Typ: Float
- Einen sinnvollen Namen geben (eyelid_weight)
- Im GeometryNodes-Modifier die Vertex-Gruppe "Eyelid_Segmentation" als Wert für "eyelid_weight" festlegen (dafür auf den Knopf klicken um von Single Value auf Input Group umzustellen)
- Unter "Output Attributes" für eyelid_weight einen Namen eintragen. Das ist das Attribut, das später im Shader verwendet wird. Es wurde "eyelid_weight_attribute" gewählt
- In den ViewLayer-Einstellungen der Main-Viewl den AOV-Output hinzufügen. Es wurde der Name "Eyelid_Segmentation" gewählt. Der Typ muss "Value" sein.
- Shader anpassen:
- Attribute-Node hinzufügen. Attribut-Type: Geometry, Name: "eyelid_weight_attribute"
Dadurch gibt die Attribute-Node die Gewichte der "Eyelid Segmentation"-Vertex Gruppe aus. Es ist zu sehen, dass sie noch nicht ganz richtig ist. Das kann am Ende des Prozesses mit Weight-Painting angepasst werden.
- Da die Gewichte einen fließenden Übergang zwischen 0 und 1 haben, müssen wir die mit einer "Greater Than"-Node einen Schwellwert festlegen, der für eine harte Kante zwischen 0 und 1 (Haut und Augenlid) sorgt. Dafür wird der Factor-Output der Attribute-Node in den Value-Eingang gelinkt.
- Da wir diesen Wert aber nicht als Material ausgeben wollen, sondern neben dem normalen Haut-Shader als Wert im Compositing benötigen, müssen wir den Ausgang des Greater-Than nicht in den "Material Output" pipen, sondern in eine "AOV Output"-Node. Da es sich um eine Float-Eigenschaft handelt und nicht um eine Farbe, wird der Greate Than Node mit dem Value-Eingang der AOV-Node verbunden. Dem Attribut muss ein Name gegeben werden. Der kann wieder willkürlich gewählt werden. Es wurde "eyelid_value" gewählt.
Es wurde für die Übersichtlichkeit ein Rahmen um die Nodes gesetzt, der dient allein der Dokumentation
- Attribute-Node hinzufügen. Attribut-Type: Geometry, Name: "eyelid_weight_attribute"
- Im Compositing hat die Render Layers Node für den Main-ViewLayer jetzt einen neuen Ausgang "Eyelid Segmentation"
- .......
- Für das Skin-Objekt neuen Geometry-Nodes erstellen.
Besserer Ansatz
Nach dem Aufschreiben des zuvor beschriebenen Ansatzes ist aufgefallen, dass die meisten Schritte, abgesehen vom AOV-Output des Shaders, nämlich das Erstellen der Vertexgruppe bzw. das Weight-Painting, sowie der Geometry-Nodes-Aufbau für die Shader-Attribute alle durch gar nicht notwendig sind, um das gewünschte Ziel zu erreichen.
Einzig notwendig ist die Shader-AOV-Deklaration für den ViewLayer sowie die AOV-Output-Node im Haut-Shader. Die anderen Komponenten sind nach Belieben ersetztbar.
Weightpainting hat nämlich einen entscheidenden Nachteil: die Präzision ist auf die Vertices der Geometrie beschränkt. Auch bei Subdivision-Oberfächen kann man Gewichte nur für die original Vertices und nicht die durch die Unterteilung entstandenen Vertices festlegen.
Ein ähnliches Problem besteht auch, wenn man Oberflächen einfärben möchte (was strenggenommen auch das ist, was wir hier machen wollen, auch wenn das Ergebnis verhältnismäßig unüblich ist). Wenn man die Farben der Oberfläche pro Vertex festlegen würde, bräuchte man für mehr bzw. feinere Details mehr Vertices. Gelöst wurde dieses Problem in der Computergrafik, indem für jeden Vertex Textur-Koordinaten festgelegt werden. Auf der Oberfläche wird zwischen den Texturkoordinaten linear (genauer bilinear, also in zwei Richtungen) interpoliert um für jeden Punkt die Texturkoordinate zu berechnen. Diese Textur-Koordinaten sind 2D-Vektoren zwischen (0, 0) und (1, 1), anhand denen meist Farben aus einer Textur (einem Bild) ausgelesen werden können. Die Texturkoordinaten sind somit eine Abbildung von 3D Geometrie auf eine 2D Fläche. Als Namen für die Achsen in dieser 2D Projektion haben sich U und V statt X und Y durchgesetzt. Daher nennt man die Gesamtheit der Texturkoordinaten eines Modells auch das "UV-Mapping" dieses Modells. Dabei ist anzumerken, dass diese Festlegung der Texturkoordinaten einerseits durch mathematische Funktionen geschehen kann, aber oft auch manuell festgelegt wird. Wird eine Textur anhand von UV-Mapping auf die Geometrie projeziert spricht man von "Texture-Mapping".
Das wird in der hier präsentierten Implementierung bereits verwendet, um zusätzliches Detail auf der Hautoberfläche darzustellen. Abbildung ASDF zeigt das UV-Mapping der verwendeten Haut-Geometrie. Rechts sieht man die 3D-Geometrie mit Textur und Drahtgitter für die Haut-Geometrie. Dort ist in orange ein Polygon ausgewählt. Auf der linken Seite ist die verwendete Textur gezeigt und das projezierte Drahtgitter. Auf der linken Seite sind ebenfalls in orange die gleichen Vertices ausgewählt wie im rechten Bild um den Zusammenhang zwischen den Räumen zu verdeutlichen. Es ist auch ersichtlich, dass bei Texture-Mapping die menge an Detail auf der Oberfläche nun nicht mehr (nur) abhängig von der Auflösung des Modells ist, sondern vor allem abhängig von der Auflösung der verwendeten Textur ist, da zwischen zwei Textur-Koordinaten und somit zwei Vertices theoretisch beliebig viele Pixel liegen können.
Blender liefert im Shader-Graph die "Image Texture"-Node um einfach anhand der Textur-Koordinaten eine Textur auszulesen. Um also einfach eine Segmentierungsmaske für das Augenlid zu erstellen und als Shader AOV zu rendern, kann einfach eine Augenlid-Textur gemalt werden, die dann im Shader ausgelesen wird und als input für eine AOV-Output-Node verwendet wird.
Schritte für das Implementieren
- In den ViewLayer-Einstellungen der Main-View den AOV-Output hinzufügen. Es wurde der Name "Eyelid Mask" gewählt. Der Typ muss "Value" sein, das heißt, es wird ein einzelner Gleitkomma-Wert ausgegeben, anstatt einer Farbe.
- Als nächstes muss die Maske gezeichnet werden. Das kann direkt in Blender getan werden. Dazu bietet sich die Standardmäßig verfügbare "Texture Paint"-Ansicht an.
In dieser Ansicht ist auf der linken Seite eine Ansicht der 2D-Textur und auf der rechten Seite eine Ansicht der 3D-Geometrie. Auf beiden Ansichten kann mit der Maus (oder einem Grafiktablet) gemalt werden.
- Zunächst muss eine neue Textur erzeugt werden
1. Diese Textur kann dann auf beiden oberflächen angezeigt werden (dazu kann man entweder den Shader verändern, oder die 3D-Ansicht auf Viewport stellen und den Anzeige-Modus von Material auf Single-Image ändern)
2. Dann kann mit dem Pinsel-Werkzeug gemalt werden. Da eine Segmentierungsmaske erzeugt werden soll, bleibt das gesamte Bild schwarz, außer auf den Vertices des Augenlids. Es kann Hilfreich sein, das Drahtgitter in der 3D-Ansicht zu aktivieren. Da es sich um eine Binärmaske handeln soll, ist es sinnvoll den weichen Übergang zwischen existierender Farbe und zu malender Farbe zu deaktiveren. Dazu kann in den Pinsel-Einstellungen der Falloff auf "Constant" gestellt werden.
Abbildung FASD demonstriert, dass bei UV-Mapping grundsätzlich berücksichtigt werden muss, dass die Kanten von Polygonen nicht unbedingt mit dem Pixelraster übereinstimmen. Das kann dazu führen, dass ein Pixel auf mehreren Polygonen liegen kann. Das kann in bestimmten Fällen zu unerwünschtem Verhalten führen. (Fußnote: Dem aufmerksamen Leser fällt auf, dass in der gezeigten Abbildung die Poygon-Grenzen nicht gerade sind, was bei einer bilinearen Interpolation nicht möglich ist. Das liegt daran, dass die Kanten der original Polygone unter Berücksichtigung der Subdivision angezeigt werden, bei der die original Kanten gebogen werden können. Warum Blender anfing mir das Drahtgitter auf diese Weise anzuzeigen entzieht sich meinem Verständnis.)
Abbiludng Bla zeigt die fertig gemalte Segmentierungsmaske. Da grundsätzlich die Texturkoordinaten eines Vertices fest sind, werden sie durch die Shape-Keys nicht verändert und die Segmentierungmaske sieht in allen Öffnungsweiten des Auges plausibel aus. Nachdem das Bild fertig bearbeitet wurde, muss es noch als Datei abgespeichert werden.
- Zunächst muss eine neue Textur erzeugt werden
1. Diese Textur kann dann auf beiden oberflächen angezeigt werden (dazu kann man entweder den Shader verändern, oder die 3D-Ansicht auf Viewport stellen und den Anzeige-Modus von Material auf Single-Image ändern)
In der textures.py-Datei wurde der Code für die generierung des Skin-Materials so angepasst, dass wie in Abbilunsdfa zu sehen ist erstmal eine "Texture Image"-Node erzeugt wird, welche die Augenlid-Maske als Bild verwendet. Die Interpolation wird auf Kubisch (Cubic) gesetzt, für beste Interpolationsergebnisse zwischen den Pixeln. Damit die Segmentierungsmaske im Kompositing nur 0 und 1 enthält muss später noch eine Step-Operation durchgeführt werden. Außerdem wurde der Color Space auf "Non-Color" gesetzt. Das hat in diesem Fall zwar keine Auswirkungen, ist aber best-practice, wenn die Pixel des Bildes keine Farben repräsentieren. (TODO: Wenn Zeit ist: Exkurs sRGB, als Anhang)
Außerdem wird eine AOV Output-Node erzeugt. Der Name muss der selbe sein, wie er in den Shader AOVs des ViewLayers festgelegt wurde. Hier "Eyelid Mask". Der Color-Ausgang der Texture Image Node wird mit dem Value EIngang des AOV Outputs verbunden.
Die Abbildung zeigt den finalen Node-Graphen für das Haut-Material. Es gibt unverändert den Teil, der die Haut-Textur einliest, die Grauwerte bearbeitet und als Emission-Shader ausgibt. Neu ist der Teil "Eyelid Segmentation Mask", welche die eben beschriebene "eyelid-mask"-Textur einliest und als AOV rausschreibt. Für bessere Übersichtlichkeit und Dokumentation wurde im blender_helper.py-Script die Funktion frame_nodes implementiert. Dieser übergibt man einen Node-Tree, in dem der neue Rahmen erzeugt werden soll, den Titel, der über dem Rahmen stehen soll, sowie alle Nodes, die in diesem zusammengefasst werden sollen.
Auch neu ist die Funktion create_dont_change_note, welche eine Notiz an einer angegebenen Stelle erzeugt, die darauf hinweist, dass der Node-Graph automatisch generiert wird und in den Scripts angepasst werden muss.
def frame_nodes(node_tree: bpy.types.NodeTree, frame_label: str, framed_nodes: list[bpy.types.Node]) -> bpy.types.NodeFrame:
...
def create_dont_change_note(node_tree: bpy.types.NodeTree, location: tuple[float, float] = (-420.000, 40.000)) -> bpy.types.Node:
...
Im Compositing Node-Tree erhält die "Render Layers"-Node jetzt einen zusätzlichen Ausgang "Eyelid Mask", wenn der Main-ViewLayer ausgewählt wird. Dieser stellt ein Bild bereit in dem die an die AOV Output-Nodes übergebenen Ergebnisse enthalten sind, also die Augenlid-Maske. Diese ist allerdings noch nicht direkt als Segmentierungsmaske verwendbar, da sie als Werte nicht nur 0 und 1 enthält, sondern auch interpolierte Werte dazwischen. Für diese Interpolation gibt es zwei Gründe: Zum einen liegt es daran, dass die einzelnen Texel der Augenlid-Textur, die im Shader eingelesen wird, ggf. mehrere Pixel der finalen Segmentierungsmaske abdecken. In diesem Fall wird zwischen den Texels interpoliert. Das ist mit normalen Texturen oft erwünscht, damit keine harten Texel-Kanten sichtbar sind, führt in diesem Fall aber dazu, dass andere Werte als 0 und 1 vorhanden gerendert werden. Denn die kubische Interpolation erzeugt an Pixel-Übergängen zwischen einem schwarzen und einem weißen Pixel einen weichen Übergang aus Graustufen. Auf der anderen Seite liegt das aber daran, dass Blender mit dem Eevee-Renderer Temporal-Anti-Aliasing (TAA) verwendet um harte Pixelkanten zu glätten, indem mehrere Samples des gleichen Pixels zusammengeführt werden. Diese Kantenglättung kann deaktiviert werden, ist aber für normale Bilder grundsätzlich ein erwünschter Effekt um unnatürliche Kanten zu glätten und Flimmern von Details, die kleiner als einzelne Pixel sind, zu reduzieren. Dieser Effekt wird auch auf AOVs angewandt, was für unsere Segmentierungsmaske unerwünscht ist. Um trotz Kantenglättung und Texel-Interpolation scharfe Kanten in der Segmentierungsmaske zu erreichen wird eine "Greate Than"-Node (egl. Größer als) verwendet, die als Eingangswert ("Value") den "Eyelid Mask"-Output der "Render Layer"-Node erhält. In der Node kann ein Schwellwert festgelegt werden. Für alle Pixel, deren Wert über diesen Schwellwert liegen wird 1 ausgegeben. Für alle, die drunter liegen 0. Dadurch wird trotz Kantenglättung im Renderprozess in der finalen Maske nur 0 und 1 ausgegeben. Abbildung CYC demonstriert die Effekte von Kantenglättung und verschiedenen Textur-Interpolationsmethoden auf die finale Segmentierungsmaske.
(TODO: Wenn Zeit ist, kann das ja ein Exkurs werden. Es reicht eigentlich zu zeigen: (Kein) AA Kubisch vs Closest)
| Closest | Linear | Cubic | |
|---|---|---|---|
| No Antialiasing | |||
| Antialiasing, no Greater Than | |||
| Antialiasing, Greater Than |
Der Code für das Generieren des Compositing Graphen (nodetree.py) wurde so angepasst, dass diese Schwellwert-Berechnung angewandt wird. Außerdem wurden alle Verbindungen mit der alten Cryptomatte-Maske des Augenlid-Objekts durch die Schwellwert-Node binärisierte "Eyelid Mask" ersetzt. (TODO: vielleicht ei

