Tipp: Noise Reduction Nodes mit Python verknüpfen

Tipp: Noise Reduction Nodes mit Python verknüpfen

b°wide hat der Community einen Node-Pack zur Verfügung gestellt, der neben vielen anderen Verwendungszwecken zur Reduktion von Cycles-Rauschen im Compositor benutzt werden kann. Herunterladen könnt ihr ihn hier. Es bedarf vieler Klicks diese Node Group zu benutzen, zunächst muss man alle Channels aktivieren und dann alle In- und Outputs verbinden, hier gibt es eine one-click-solution.

In diesem Artikel möchte ich mich auf die höchsteffektiven Node Groups konzentrieren, die mit Hilfe des Bilateral Blurs Rauschen aus Renderings mit wenig Qualitätsverlust stark reduzieren. Ich gehe allerdings davon aus, dass ein paar grundlegende Vorgehensweisen in Python schon bekannt sind, falls das nicht der Fall sein sollte, schaut euch bitte ein paar der ARewO Entwicklungstutorials an, bei denen ich auf Level 0 ansetze.
Hier ist das komplette Script, danach folgt die Erklärung:

[code:python]import bpy

bpy.context.scene.render.layers["RenderLayer"].use_pass_combined = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_z = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_ambient_occlusion = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_diffuse_direct = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_diffuse_indirect = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_diffuse_color = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_glossy_direct = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_glossy_indirect = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_glossy_color = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_transmission_direct = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_transmission_indirect = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_transmission_color = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_subsurface_direct = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_subsurface_indirect = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_subsurface_color = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_emit = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_environment = True
bpy.context.scene.render.layers["RenderLayer"].use_pass_normal = True

# get the components
nt = bpy.context.scene.node_tree
render = nt.nodes['Render Layers']
reduce_noise = nt.nodes['Group']

# store all relevant (not hidden) sockets
list_out = [n.name for n in render.outputs]
list_in = [n.name for n in reduce_noise.inputs if not n.hide]

for socket in list_in: #list of names -> strings
    if socket.startswith('SSS'): # He used SSS instead of Subsurface, so we have to fix that.
        reduce_noise.inputs[socket].name = socket.replace('SSS', 'Subsurface')
        connection = socket.replace('SSS', 'Subsurface')
    else: # for all others we just leave it
        connection = socket
    
    if connection in list_out:
        #now that all out and inputs have the same name, we can link them by name
        nt.links.new(render.outputs[connection], reduce_noise.inputs[connection])[/code]

Setup:
Zunächst braucht ihr die Blenddatei, in der diese Node Groups gespeichert sind. Ladet sie mit dem Link oben herunter und speichert sie auf der Festplatte. Dann öffnet ihr die Datei in der Rauschen reduziert werden soll und geht auf File -> Append (SHIFT + F1). Navigiert im Menü zur b°wide Datei, klickt sie an und geht in das Verzeichnis NodeTree. Dort sind alle Node Groups gespeichert und ihr könnt die PassCombineDenoiser  mit einem Doppelklick auswählen. Geht danach in das Compositor Layout oder öffnet den Node Editor in einem Fenster. In den Compositing Nodes stellt ihr Use Nodes an (1). Dann SHIFT + A -> Group -> PassCombineDenoiser.

Das erstellt eine Node Group mit einer Menge Inputs. Wir haben aber nur 3 Outputs in der Render Layer  Node, also müssen wir das ändern. Im Render Layer Tab des Properties Window geht zu Passes und erweitert diese Option (2).
Bevor wir jetzt alle nötigen Passes aktivieren, schauen wir uns an, was genau passiert, wenn wir einen dieser Passes anklicken. Dazu müssen wir mit der Maus über den Rand direkt unter der Info Leiste gehen und diese herunterziehen (3). Neben der Python Console ist diese Vorgehensweise der beste Freund des Blender Python-Anfängers. Wenn wir jetzt also beispielsweise den Haken beim AO Pass setzen, kann man dort lesen:

[code:python]bpy.context.scene.render.layers["RenderLayer"].use_pass_ambient_occlusion = True[/code]

Jetzt müssen wir noch die restlichen notwendigen Passes anwählen. Keine Sorge, das müssen wir nur einmal machen, bei jedem weiteren Importieren wird das Skript das für uns übernehmen. In dem Python Information-Fenster könnt ihr mit B alles auswählen, was wir brauchen (alles was use_pass_ enthält). Mit der Maus immer noch über dem blau markierten Text STRG + C drücken und einen Text Editor öffnen oder zum Window Preset Scripting wechseln. Dort auf New (4) klicken, damit wir ein Skript beginnen können. Als Erstes schreiben wir: 

[code:python]import bpy[/code]

Danach fügen wir den Code ein, den wir eben kopiert haben (STRG + V). Jetzt macht unser Skript dasselbe wie das, was wir eben per Hand getan haben, schon eine große Hilfe, aber da geht noch mehr. Zuerst erstellen wir ein paar Variablen, die es anderen oder uns später erleichtern nachzuvollziehen, was wir getan haben, also die Lesbarkeit verbessern:

[code:python]nt = bpy.context.scene.node_tree
render = nt.nodes['Render Layers']
reduce_noise = nt.nodes['Group'][/code]

Man kann sich den node_tree als Verzeichnis vorstellen, die Dateien, die wir daraus verwenden, sind die Variablen: render, welche die Node im Nodetree enthält, die  'Render Layers' heißt und reduce_noise. Die nächsten Zeilen sind etwas komplizierter. Man könnte auch eine For-Schleife verwenden, um durch die Attribute der In- und Outputs zu gehen, aber diese Vorgehensweise ist die bei Python übliche.

[code:python]list_out = [n.name for n in render.outputs]
list_in = [n.name for n in reduce_noise.inputs if not n.hide][/code]

Erklärung: Wenn wir alle Namen der Outputs in einer Liste speichern möchten, geht das nicht mit render.outputs.names weil outputs eine Liste ist und nur die einzelnen Objekte der Liste einen Namen haben. Wir benutzen [] um zu zeigen, dass das Ergebnis eine Liste sein wird. Das n ist eine temporäre Variable, die alle Outputs einen nach dem anderen annimmt, denn jetzt können wir mit n.name sagen, dass wir den Namen des Outputs wollen. For also für alle render.outputs. Das gibt uns ein Array von allen Namen, das Outputs der oben deklarierten Variable render enthält. Die nächste Zeile macht dasselbe, mit der kleinen Einschränkung: wir wollen nur die Inputs in der Liste, die nicht versteckt sind, also deren Eigenschaft n.hide nicht wahr ist. Das ist nur eine Vorsichtsmaßnahme, falls es automatisch generierte oder eben versteckte und damit nicht benutzte Inputs geben sollte.

Jetzt möchten wir mit den Elementen in diesen Listen auch etwas anstellen. Um Python zu sagen, dass wir eine Liste abarbeiten wollen, und zwar jedes einzeln, schreibt man:

[code:python]for socket in list_in:[/code]

Die Variable socket wird in jedem Zyklus einen anderen Wert annehmen, nämlich den Namen, der gerade betrachtet wird. Wir müssen alle Zeilen einrücken, die zur Schleife gehören, so zeigt man Python, wann die Schleife vorbei ist. Wahrscheinlich habt ihr schon gesehen, dass die meisten Inputs den gleichen Namen wie die Outputs haben, mit Außnahme der letzten 3, dort steht statt 'Subsuface' 'SSS'. Das verhindert zwar, dass wir die Sockets einfach mit F verbinden können und verlangt ein paar Zeilen mehr in unserem Skript, aber es hat mich trotzdem gefreut, weil es dadurch noch ein wenig mehr Sinn macht, diese Schritte tatsächlich zu skripten und nicht jedesmal per Hand macht. Außerdem können wir noch ein wenig mehr Code betrachten.

[code:python]    if socket.startswith('SSS'): # Er hat SSS statt Subsurface benutzt also: reparieren.
        reduce_noise.inputs[socket].name = socket.replace('SSS', 'Subsurface')
        connection = socket.replace('SSS', 'Subsurface')[/code]

Eines der großartigen Dinge an Python ist: Selbst wenn man keine Ahnung von Code hat, kann man doch einigermaßen erraten, was gerade passiert. Wenn also der Socket (wir erinnern uns, das ist ein Name, also String) mit 'SSS' anfängt, soll das 'SSS' durch 'Subsurface' ersetzt werden. Dann erstellen wir eine weitere Variable, die ebenfalls den ersetzten String enthält. Die Variable socket selbst zu ersetzen kann problematisch sein, da sie von der Schleife zugewiesen wird, also lieber auf Nummer sicher gehen. 

[code:python]    else: # für alle anderen lassen wir alles, wie's ist.
        connection = socket
    if connection in list_out:
        nt.links.new(render.outputs[connection], reduce_noise.inputs[connection])[/code]

Jetzt haben also alle zusammengehörigen In- und Outputs denselben Namen und wir können sie nach Namen verbinden. Es ist wichtig zu beachten, dass die Verbindung (link) nicht Teil der In- und Outputs ist, was auf den ersten Blick vielleicht logisch erscheinen mag, sondern Teil des Nodetrees selbst. Deshalb nt.links.new. nt war ja der gesamte Nodetree der Szene s.o.