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