Beim rumexperimentieren mit Blender habe ich nach einer Möglichkeit gesucht, um den Rendervorgang ein wenig auszulagern. Ich wurde auch schnell fündig, leider ist das gefundene Script von ideasman nur für einzelne Frames zu gebrauchen. Also habe ich ein wenig Hand angelegt und die fehlende Funktionalität, ganze Animationen zu berechnen, mit ein paar Zeilen eingebaut.
Beim Testen sind mir keine Fehler aufgefallen. Falls doch jemand etwas hat: Ab in die Kommentare!
Genug Vorgeschichte. Wie bringt man Blender zum verteilten Rendern?
Benötigte Programme
1. Blender mit einem Pythonscript füttern
Das Script unterteilt jedes Bild in 256×256 Pixel große Quadrate, die einzeln berechnet und gespeichert werden. Bereits begonnene oder fertiggestellte Teile werden von den anderen Clients ignoriert.
Die Datei render.py im Projektverzeichnis ablegen:
import Blender from Blender import sys, Scene, Noise from Blender.Scene import Render import string NUM = '0123456789' DEFAULT_TILE_SIZE=256 # px # Clamp def max(number): if number > 1.0: number = 1.0 return number def isfile(file): try: open(file) return 1 except: return 0 def randList(list): randList = [] lenList = len(list) while lenList != len(randList): randIndex = int( Noise.random() * len(list) ) randList.append( list[randIndex] ) list.remove( list[randIndex] ) return randList # Strip all chars after a '.' def stripExt(text): print 'filename ', text return text[:text.index('.')] def getParts(): name = Blender.Get('filename') name = stripExt(name) tileSize = DEFAULT_TILE_SIZE # OK either way we have a tile size. # Get the data scn = Scene.GetCurrent() context = scn.getRenderingContext() xPix = context.imageSizeX() yPix = context.imageSizeY() xParts = int(xPix / tileSize) yParts = int(yPix / tileSize) # Incase the tile size is larger then the render size. if xParts < 2: xParts = 2 if yParts < 2: yParts = 2 return xParts, yParts # Makes a list of rects that setBorder will pass. def makeBorderList(xparts, yparts): borderRectList=[] #We store all the rects here and then return them. xlen = 1.0 / xparts ylen = 1.0 / yparts xPoint = 0.0 # This stores the current X value, and incriments xlen each iteration until its equel to 1.0 yPoint = 0.0 counter = 1 # Inde each border while xPoint < 0.999: while yPoint < 0.999: # Write the rect to the list borderRectList.append( (counter, max(xPoint), max(yPoint), max(xPoint+xlen), max(yPoint+ylen)) ) counter += 1 # Keep a tag of which index this one is. yPoint += ylen # Reset yPoint for the next colum. yPoint = 0.0 xPoint += xlen return borderRectList # SETS UP DEFAULTS NEEDED FOR OUTPUTTING AN IMAGE THAT CAN BE COMPOSITED. scn = Scene.GetCurrent() context = scn.getRenderingContext() context.enableBorderRender(1) context.enableRGBAColor() # Save RGBA context.setImageType(Render.PNG) # Save RGBA context.enableExtensions(1) # Make image name imageName = Blender.Get('filename') # Remove .blend imageName = stripExt(imageName) renderName = imageName + '_' # frameNum.png will be added. # Set the start and end frame to the current frame. staFrame = Blender.Get( 'staframe') endFrame = Blender.Get( 'endframe') # Keep track of frames rendered, only for a report. Blender.Set( 'curframe', 1) xParts, yParts = getParts() randBorderList = randList( makeBorderList(xParts,yParts) ) print '= staframe: ', staFrame print '= endframe: ', endFrame print '=== frames: ', endFrame-staFrame+1 for curFrame in range(staFrame,endFrame): context.startFrame(curFrame) context.endFrame(curFrame) for border in randBorderList: # Set the new file name WITH X/Y parts # blendfilename_partnum_framenum.ext # eg. render_01_0001.png partNum = str(border[0]) while len(partNum) < 4: partNum = '0' + partNum # CREATE THE REAL NAME OF THE OUTPUT FILE frameNum = str(curFrame) while len(frameNum) < 4: frameNum = '0' + frameNum uniqueRenderName = renderName + partNum + '_' fileToRender = uniqueRenderName + frameNum + '.png' print '= frame: ', curFrame print '== part: ', partNum # Check that the file isnt alredy there if isfile(fileToRender) == 0: # TOUCH FILE SO NOBODY OVERWRITES IT. # Create a dummy file so no other nodes try to render the image. file = open(fileToRender,"w") file.close() # SET RENDER NAME AND PATH. context.setRenderPath('//' + uniqueRenderName) # // is the currentdir # Set border context.setBorder(border[1], border[2], border[3], border[4] ) # RENDER THE IMAGE context.renderAnim() # This saves the pics. else: print '= was already rendered' # Quit Blender.Quit()
2. Projektordner freigeben
Man muss dafür sorgen, dass auf allen Rechnern die mithelfen sollen das Projektverzeichnis eingebunden ist. Hierzu bieten sich Samba und nfs an. Das folgende Beispiel nutzt nfs.
Auf dem Hauptsystem kann man die nfs-Freigabe in der Datei /etc/exports einstellen:
/home/eisfrei/blender/projekt 192.168.0.1/24(rw,anonuid=1000,anongid=100,all_squash,sync)
Sämtliche Renderknechte binden nun die Freigabe ein:
mount.nfs 192.168.0.23:/home/eisfrei/blender/projekt /mnt/
3. Rendern
Auf allen Systemen die mitwerkeln sollen wechselt man in das Projektverzeichnis und startet das Rendern mit:
blender -b projekt.blend -P render.py -a
Jetzt erst mal eine Runde hinlegen und abwarten, während eine Unmenge von Pngs erzeugt wird.
4. Einzelteile zusammenkitten
Zum verkleben der Bildbestandteile benutze ich eine Kombination aus Perl und ImageMagick. Das Perlscript sucht die Bestandteile zusammen und übergibt diese an ImageMagick, welches diese dann alle schön zusammenleimt.
merge.pl
#!/usr/bin/perl use strict; my $fps=25; my $project=shift; my @files; print $project,"\n"; opendir(DIR, "."); @files = grep(/$project[_][0-9]+_[0-9]+\.png$/,readdir(DIR)); closedir(DIR); my %frames; my %parts; foreach my $file (@files) { print "pruefe ",$file,"\n"; if ($file=~/$project[_]([0-9]+)_([0-9]+)\.png$/) { $frames{$2}=1; $parts{$1}=1; } } foreach my $fr (sort(keys(%frames))) { my $cmd='convert '; foreach my $pa (keys(%parts)) { $cmd.=$project.'_'.$pa.'_'.$fr.'.png '; } $cmd.=' -flatten '.$project.'_fr_'.$fr.'.jpg'; print $cmd,"\n"; `$cmd`; }
Das Perlscript ruft man mit dem angehängten Projektnamen auf:
./merge.pl projekt
5. Aus den Frames ein Video erstellen
Um den einzelnen Frames zu Bewegung zu verhelfen benutze ich mencoder. Es gibt aber hunderte von Möglichkeiten, dies zu realisieren.
mencoder "mf://projekt_fr_*.jpg" -mf fps=25 -o output.avi -ovc lavc -lavcopts vcodec=mpeg4
Für die Ungeduldigen …
habe ich alles in einer Datei zusammengefasst und auch noch eine Szene zum ausprobieren beigelegt: renderfarm.tgz
Die Datei muss nur noch entpackt werden und dann dies ausgeführt werden:
blender -b projekt.blend -P render.py -a ./merge.pl projekt mencoder "mf://projekt_fr_*.jpg" -mf fps=25 -o output.avi -ovc lavc -lavcopts vcodec=mpeg4
Einschränkungen
Max. 9999 Frames – Diese Einschränkung lässt sich aber relativ einfach beseitigen, indem man
while len(frameNum) < 4:
auf einen höheren Wert setzt. Dies führt allerdings zu leeren Lockfiles.
Coole Sache. Blender kann alles.
Ja, wirklich alles!
Das funktioniert sogar echt super!!!!
Und eine sehr gute Anleitung!
Pingback: Blender Cluster « bytesnake