sudo apt-get install libpython3-dev libpython3.5-dev libopenimageio-dev libopenimageio1.6 openimageio-tools libopenimageio-doc python-openimageio cmake libx11-dev libjpeg-dev libpng12-dev libz3-dev libfreetype6-dev libboost-all-dev libglew-dev libopenexr-dev libopenjpeg-dev libopenal-dev python3-numpy libjemalloc-dev wget http://download.blender.org/source/blender-2.77a.tar.gz tar xvf ../blender-2.77a.tar.gz mkdir cmake-make cd cmake-make cmake ../blender-2.77a/ make -j4 sudo make install
Nous avons eu des problèmes de compilation, dus à des problèmes de version de librairies, il est nécessaire d'avoir: cmake 3.2 et python 3.5
Après essai infructueux de:MAJ CMake 3.2 , nous sommes passé en 16.04
modification des sources blender pour gérer le modèle caméra rolling: https://developer.blender.org/D1624
fichier: intern/cycles/kernel/kernel_camera.h
ray->time += (time - 0.5f) * (1.0f - duration) + 0.5f; } else { ray->time = time; } //BVDP FILE *f; f=fopen("test.bv","a"); fprintf(f,"%d , %d , %lf\n", x,y,(double)ray->time); fclose(f); //!BVDP } } #endif /* sample */ if(kernel_data.cam.type == CAMERA_PERSPECTIVE) camera_sample_perspective(kg, raster_x, raster_y, lens_u, lens_v, ray); else if(kernel_data.cam.type == CAMERA_ORTHOGRAPHIC)
Le code pour rolling shutter est à la ligne (l322) dans intern/cycles/kernel/kernel_camera.h Le calcul du temps correspondant à chaque partie du rendu est fait par:
ray->time = (ray->time - 0.5f) * duration;
la demande de rendu à cet instant par:
if(kernel_data.cam.type == CAMERA_PERSPECTIVE) camera_sample_perspective(kg, raster_x, raster_y, lens_u, lens_v, ray);
En (l42) on trouve la fonction de calcul du rayon primaire:
ccl_device void camera_sample_perspective(KernelGlobals *kg, float raster_x, float raster_y, float lens_u, float lens_v, ccl_addr_space Ray *ray)
Pour l'interpolation des matrices caméras (en cas de changement des paramètres intrinsèques):
if(kernel_data.cam.have_perspective_motion) {
Pour l'interpolation des matrices de pose:
if(kernel_data.cam.have_motion) { #ifdef __CAMERA_MOTION__ if(kernel_data.cam.have_motion) { #ifdef __KERNEL_OPENCL__ const MotionTransform tfm = kernel_data.cam.motion; transform_motion_interpolate(&cameratoworld,((const DecompMotionTransform*)&tfm), ray->time); #else transform_motion_interpolate(&cameratoworld,((const DecompMotionTransform*)&kernel_data.cam.motion), ray->time); #endif
L'interpolation de pose est réalisée par la fonction transform_motion_interpolate dans intern/cycles/util/util_transform.h (l410)
Pour l'interpolation des quaternions, elle utilise:
ccl_device_inline float4 quat_interpolate(float4 q1, float4 q2, float t) (l317)
Pour les réglages de caméra sous blender, voir: https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro/Understanding_the_Camera
Matrice caméra:
Sous blender les caméras sont gérées avec des pixels carrés () et définies soit par une focale en mm soit par un champ de vision en degrés.
La position du point principal au centre de l'image, pour un indicage des images type C (pixel coin haut gauche en 0.0): et
Le calcul des focales pixelliques se fait ainsi:
Attention, les caméras blender pointent vers les Z négatifs, il faut donc ajouter une rotation de 180° à la matrice rotation. Todo jessica: compléter.
TODO pour jessica: expliquer le processus de création de la chaine de rendu pour générer les zmap au format openExr.
http://www.cs.tut.fi/kurssit/SGN-5406/vrlab2012/blender-manual.pdf
http://blender.stackexchange.com/questions/33293/z-buffer-output-from-blender-as-file
https://www.blender.org/manual/render/blender_render/layers.html#
Pour l'exploitation des images au format openExp, voir la page openexr.
Les commandes pythons correspondantes aux actions en cours dans la GUI sont affichées dans la console python intégrée: https://www.youtube.com/watch?v=K0yb4sZ7B4g
https://wiki.blender.org/index.php/Doc:FR/2.4/Manual/Extensions/Python/Example
Exemple de rendu avec caméra fisheye: https://blender.stackexchange.com/questions/32848/script-for-rendering-a-simple-scene
Utiliser l’interpréteur python intégré à blender depuis la ligne de commande
blender --python test.py
import bpy import numpy as np import sys # switch on nodes bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree links = tree.links # clear default nodes for n in tree.nodes: tree.nodes.remove(n) # create input render layer node rl = tree.nodes.new('CompositorNodeRLayers') rl.location = 185,285 # create output node v = tree.nodes.new('CompositorNodeViewer') v.location = 750,210 v.use_alpha = False # create output node of_c_node = tree.nodes.new('CompositorNodeOutputFile') of_c_node.location = 600, 200 #of_node.base_path = of_c_node.format.file_format = 'PNG' # Links links.new(rl.outputs[0], v.inputs[0]) # link Image output to Viewer input links.new(rl.outputs[0], of_c_node.inputs[0]) # Define path where to save image of_c_node.base_path = "./images_test_png" # render bpy.ops.render.render() # get viewer pixels directly pixels = bpy.data.images['Viewer Node'].pixels print(len(pixels)) # size is always width * height * 4 (rgba) # copy buffer to numpy array for faster manipulation arr = np.array(pixels[:]) print('one pixel \n',arr[100:104]) #exit blender sys.exit(0)
ssh bvandepo@cuda.laas.fr wget https://bvdp.inetdoc.net/files/blender/blender-2.74-linux-glibc211-x86_64.tar.bz2 tar -jxvf blender-2.74-linux-glibc211-x86_64.tar.bz2 rm blender-2.74-linux-glibc211-x86_64.tar.bz2 cd blender-2.74-linux-glibc211-x86_64 ./blender
Utiliser l'option -b pour désactiver le rendu openGL. Par exemple pour exécuter un script python:
./blender --python test.py -b
Pour lancer le rendu à distance:
ssh -t bvandepo@cuda.laas.fr "cd blender-2.74-linux-glibc211-x86_64 && ./blender --python test.py -b"
Copier la clef publique rsa pour ne pas avoir à saisir le mot de passe à chaque connexion
Blender a deux modes de rendu possible : le mode par défaut “blender render” et le mode ray-tracing nommé “cycles”. Les documentations associées sont :
Lien fournissant une comparaison entre les deux moteurs de rendu :
https://blender.stackexchange.com/questions/5820/how-is-cycles-different-from-blender-internal
Notions à connaître : ray-tracing et anti-aliasing (cours) :
https://www.cs.cmu.edu/afs/cs/academic/class/15462-s09/www/lec/13/lec13.pdf
Ray-tracing (et anti-aliasing) du moteur de rendu Cycles :
https://docs.blender.org/manual/en/dev/render/cycles/settings/scene/render/integrator.html https://docs.blender.org/manual/en/dev/render/cycles/settings/scene/render/light_paths.html
Anti-aliasing du moteur de rendu Blender (par défaut) :
https://docs.blender.org/manual/en/dev/render/blender_render/settings/antialiasing.html
Le script suit les étapes suivantes :
import bpy import numpy as np import os import sys #****** CREATE DIRECTORY WHERE TO SAVE IMAGES *********** img_dir = '/tmp/images/' if not os.path.exists(img_dir): os.makedirs(img_dir) #****** SET GPU AS DEVICE *********** # Before check your gpu compute device type ver = bpy.app.version[0]*1000 + bpy.app.version[1]*10 + bpy.app.version[2] if ver < 2782 : # Before blender 2.78b bpy.context.scene.cycles.device = 'GPU' bpy.context.user_preferences.system.compute_device_type = 'CUDA' else : # Since blender 2.78b sysp = bpy.context.user_preferences.addons['cycles'].preferences sysp.compute_device_type = 'CUDA' bpy.context.scene.cycles.device = 'GPU' #****** INITIALIZE THE BLENDER 3D WORLD ******** # gather list of items of interest. candidate_list = [item.name for item in bpy.data.objects if item.type == "MESH" or item.type == "LAMP" or item.type == "CAMERA"] # select them only. for object_name in candidate_list: bpy.data.objects[object_name].select = True # remove all selected. bpy.ops.object.delete() # remove the meshes, they have no users anymore. for item in bpy.data.meshes: bpy.data.meshes.remove(item) # delete all materials for i in bpy.data.materials.values(): bpy.data.materials.remove(i) # delete all textures for i in bpy.data.textures.values(): bpy.data.textures.remove(i) # delete all images for i in bpy.data.images.values(): # delete image path, this is only possible without a user i.user_clear() # delete all, except »Render Result« if i.name != "Render Result": bpy.data.images.remove(i) #****** ADD OBJECTS ******** # Add a mesh bpy.ops.mesh.primitive_plane_add(location=(0,0,0), radius=1.75) plane = bpy.data.objects['Plane'] # Add a camera bpy.ops.object.camera_add( location = ( 1.5, 0, 5), rotation = ( 0,0.3,0 ) ) # Add a lamp bpy.ops.object.lamp_add( location = ( 1.0, -1.0, 6.0 ), type = 'POINT' ); #****** ADD MATERIALS ********* # create material plane_material = bpy.data.materials.new("plane_material") # new material plane_material_texture = plane_material.texture_slots.add() # add texture slot to material plane_material.specular_intensity = 0.0 # edit texture plane_image_path = os.path.expanduser('ton_image.jpg') # get image (needs the lib os) plane_image = bpy.data.images.load(plane_image_path) # load image # create texture plane_texture = bpy.data.textures.new("plane_texture", type = 'IMAGE') # create texture type "image" plane_texture.image = plane_image # link picture to image # link texture and material plane_material_texture.texture = plane_texture plane_material_texture.texture_coords = 'UV' plane_material_texture.mapping = 'FLAT' # prepare object bpy.context.scene.objects.active = plane # select pyramid object bpy.ops.object.mode_set(mode='EDIT') # switch to edit mode bpy.ops.uv.smart_project() # automatically unwrap object bpy.ops.object.mode_set(mode='OBJECT') # switch back to object mode plane.data.materials.append(plane_material) # link material to object #************* INIT NODE COMPOSITOR FOR DEPTH RENDERING *********** # switch on nodes bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree links = tree.links # clear default nodes for n in tree.nodes: tree.nodes.remove(n) # create input render layer node rl = tree.nodes.new('CompositorNodeRLayers') rl.location = 185,285 # create output node for color image out_c_node = tree.nodes.new('CompositorNodeOutputFile') out_c_node.location = 600, 200 out_c_node.format.file_format = 'PNG' # create output node for z map out_z_node = tree.nodes.new('CompositorNodeOutputFile') out_z_node.location = 600, 400 out_z_node.format.file_format = 'OPEN_EXR' #links links.new(rl.outputs[2], out_z_node.inputs[0]) # render node to output file for Z image links.new(rl.outputs[0], out_c_node.inputs[0]) # render node to output file for color image #************* RENDER AND SAVE IMAGES *********** # Define image resolution for rendering bpy.data.scenes["Scene"].render.resolution_x = 250 bpy.data.scenes["Scene"].render.resolution_y = 250 # resolution percentage : have to be 100% to have the whole image resolution defined earlier bpy.context.scene.render.resolution_percentage = 100 # Activate which camera will be used for rendering # (if more than one camera are defined, the rendering have to be repeated for each camera) bpy.context.scene.camera = bpy.data.objects["Camera"] # Define path where to save images str4 = img_dir + "image" out_c_node.base_path = str4 + "000" out_z_node.base_path = str4 +"_Z_"+ "000" # Render bpy.ops.render.render()#write_still=True) #************* EXIT BLENDER *********** sys.exit(0)
Le moteur de rendu doit être changé avec la variable “bpy.context.scene.render.engine”. Avec le rendu Cycles certaines choses changent :
import bpy import numpy as np import os import sys #****** CREATE DIRECTORY WHERE TO SAVE IMAGES *********** img_dir = '/tmp/images/' if not os.path.exists(img_dir): os.makedirs(img_dir) #****** SELECT RENDERING MODE (by default blender render) *********** bpy.context.scene.render.engine = 'CYCLES' print(bpy.context.scene.render.engine) # Other parameters #bpy.context.scene.cycles.progressive = 'PATH' #bpy.context.scene.cycles.samples = 50 #bpy.context.scene.cycles.max_bounces = 1 #bpy.context.scene.cycles.min_bounces = 1 #bpy.context.scene.cycles.glossy_bounces = 1 #bpy.context.scene.cycles.transmission_bounces = 1 #bpy.context.scene.cycles.volume_bounces = 1 #bpy.context.scene.cycles.transparent_max_bounces = 1 #bpy.context.scene.cycles.transparent_min_bounces = 1 #bpy.context.scene.cycles.use_progressive_refine = True #bpy.context.scene.render.tile_x = 64 #bpy.context.scene.render.tile_y = 64 #****** SET GPU AS DEVICE *********** # Before check your gpu compute device type ver = bpy.app.version[0]*1000 + bpy.app.version[1]*10 + bpy.app.version[2] if ver < 2782 : # Before blender 2.78b bpy.context.scene.cycles.device = 'GPU' bpy.context.user_preferences.system.compute_device_type = 'CUDA' else : # Since blender 2.78b sysp = bpy.context.user_preferences.addons['cycles'].preferences sysp.compute_device_type = 'CUDA' bpy.context.scene.cycles.device = 'GPU' #****** INITIALIZE THE BLENDER 3D WORLD ******** # gather list of items of interest. candidate_list = [item.name for item in bpy.data.objects if item.type == "MESH" or item.type == "LAMP" or item.type == "CAMERA"] # select them only. for object_name in candidate_list: bpy.data.objects[object_name].select = True # remove all selected. bpy.ops.object.delete() # remove the meshes, they have no users anymore. for item in bpy.data.meshes: bpy.data.meshes.remove(item) # delete all materials for i in bpy.data.materials.values(): bpy.data.materials.remove(i) # delete all textures for i in bpy.data.textures.values(): bpy.data.textures.remove(i) # delete all images for i in bpy.data.images.values(): # delete image path, this is only possible without a user i.user_clear() # delete all, except »Render Result« if i.name != "Render Result": bpy.data.images.remove(i) #************* INIT NODE COMPOSITOR FOR DEPTH RENDERING *********** # switch on nodes bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree links = tree.links # clear default nodes for n in tree.nodes: tree.nodes.remove(n) # create input render layer node rl = tree.nodes.new('CompositorNodeRLayers') rl.location = 185,285 # create output node for color image out_c_node = tree.nodes.new('CompositorNodeOutputFile') out_c_node.location = 600, 200 out_c_node.format.file_format = 'PNG' # create output node for z map out_z_node = tree.nodes.new('CompositorNodeOutputFile') out_z_node.location = 600, 400 out_z_node.format.file_format = 'OPEN_EXR' #links links.new(rl.outputs[2], out_z_node.inputs[0]) # render node to output file for Z image links.new(rl.outputs[0], out_c_node.inputs[0]) # render node to output file for color image #****** ADD OBJECTS ******** # Add a mesh bpy.ops.mesh.primitive_plane_add(location=(0,0,0), radius=1.75) plane = bpy.data.objects['Plane'] # Add a camera bpy.ops.object.camera_add( location = ( 1.5, 0, 5), rotation = ( 0,0.3,0 ) ) # Add a lamp bpy.ops.object.lamp_add( location = ( 1.0, -1.0, 6.0 ), type = 'SUN' ); #****** ADD MATERIALS ********* # Create material plane_material = bpy.data.materials.new("plane_material") # new material # Activate nodes for materials plane_material.use_nodes = True plane_nt = plane_material.node_tree plane_nodes = plane_nt.nodes plane_links = plane_nt.links # clear previous materials nodes while(plane_nodes): plane_nodes.remove(plane_nodes[0]) # Add nodes for materials plane_output = plane_nodes.new("ShaderNodeOutputMaterial") plane_diffuse = plane_nodes.new("ShaderNodeBsdfDiffuse") plane_texture = plane_nodes.new("ShaderNodeTexImage") plane_texcoord = plane_nodes.new("ShaderNodeTexCoord") # edit texture and texture coordinates plane_image_path = os.path.expanduser('ton_image.jpg') # get image (needs the lib os) plane_texture.image = bpy.data.images.load(plane_image_path) # load image # Links materials nodes plane_links.new( plane_output.inputs['Surface'], plane_diffuse.outputs['BSDF']) plane_links.new(plane_diffuse.inputs['Color'], plane_texture.outputs['Color']) plane_links.new(plane_texture.inputs['Vector'], plane_texcoord.outputs['Generated']) # Distribute nodes along the x axis for index, node in enumerate((plane_texcoord, plane_texture, plane_diffuse, plane_output)): node.location.x = 200.0 * index # Link material to object plane.data.materials.append(plane_material) #************* RENDER AND SAVE IMAGES *********** # Define image resolution for rendering bpy.data.scenes["Scene"].render.resolution_x = 250 bpy.data.scenes["Scene"].render.resolution_y = 250 # resolution percentage : have to be 100% to have the whole image resolution defined earlier bpy.context.scene.render.resolution_percentage = 100 # Activate which camera will be used for rendering # (if more than one camera are defined, the rendering have to be repeated for each camera) bpy.context.scene.camera = bpy.data.objects["Camera"] # Define path where to save images str4 = img_dir + "image" out_c_node.base_path = str4 + "000" out_z_node.base_path = str4 +"_Z_"+ "000" # Render bpy.ops.render.render()#write_still=True) #************* EXIT BLENDER *********** sys.exit(0)
Blender est livré avec son propre python. On le trouve à l'emplacement suivant : “chemin_vers_le_dossier_blender/blender-2.79-linux-glibc219-x86_64/2.79/python/lib/python3.5/”. Votre version de blender (ici 2.79) et de python (ici 3.5) sera à adapter à votre configuration. On retrouve à l'emplacement “python3.5/site-packages” les modules “numpy” et “requests” déjà présent dans blender.
Pour importer de nouveaux modules dans mes scripts, j'ai appliqué la solution suivante :
Si la version de numpy de blender ne coïncide pas avec celle utilisée par les modules externes, renommez le numpy de blender (par exemple en 'numpy_1.10.1'). Le script utilisera alors le numpy installé dans /usr/local/.
Pour voir la version de numpy, entrer dans la console python :
>>> import numpy >>> numpy.version.version
charger la scène Swirski-EyeModel.blend
Mode d'affichage default
Développer l'arbre Armature Head→the rig→Pose_head.002→eyetargetparent
à droite, choisir mode Bone (OS)
en bas, chosir Pose Mode
à droite dans transform→Location, changer x,y,z pour spécifier la cible visée par l'oeil
passer en mode scripting
recliquer sur Bone et changer transform→Location, la commande python correspondante apparait dans la console:
bpy.context.object.pose.bones["eyetargetparent"].location[0] = -0.113456
bpy.context dépend de l'objet actuellement selectionné dans la GUI, pour modifier dans le script, il faut retrouver dans l'arborescence de la scene (s'aider de l'auto complétion avec CTRL+SPACE)
scene = bpy.context.scene armature = bpy.data.objects['Armature Head'] armature.pose.bones['eyetargetparent'].location[0] = -0.113456
./blender-2.74-linux-glibc211-x86_64/blender --python ./Swirski-EyeModel.py -b
import bpy import math import mathutils import os import sys import numpy as np #lancer avec ./blender-2.74-linux-glibc211-x86_64/blender --python ./Swirski-EyeModel.py -b #****** CREATE DIRECTORY WHERE TO SAVE IMAGES *********** img_dir = '/tmp/images/' if not os.path.exists(img_dir): os.makedirs(img_dir) bpy.ops.wm.open_mainfile(filepath="/media/HD500GO/blender/Swirski-EyeModel.blend") scene = bpy.context.scene armature = bpy.data.objects['Armature Head'] camera_obj = bpy.data.objects['Camera'] camera = bpy.data.cameras['Camera'] eyeL = bpy.data.objects['eye.L'] pupilGroup = eyeL.vertex_groups['eyepulpex.L'] pupilVertices = [v for v in eyeL.data.vertices if pupilGroup.index in [g.group for g in v.groups]] pupil_base_radius = max((v1.co - v2.co).length for v1 in pupilVertices for v2 in pupilVertices) * eyeL.scale[1] eyeLbone = armature.pose.bones['def_eye.L'] pupilLbone = armature.pose.bones['eyepulpex.L'] def strVec(vec): return "({},{},{})".format(vec[0], vec[1], vec[2]) datafilepath = os.path.join(os.path.dirname(bpy.data.filepath), "render_eye_data.txt") # switch on nodes scene.use_nodes = True tree = scene.node_tree links = tree.links # clear default nodes for n in tree.nodes: tree.nodes.remove(n) # create input render layer node rl = tree.nodes.new('CompositorNodeRLayers') rl.location = 185,285 # create output node v = tree.nodes.new('CompositorNodeViewer') v.location = 750,210 v.use_alpha = False # create output node of_c_node = tree.nodes.new('CompositorNodeOutputFile') of_c_node.location = 600, 200 #of_node.base_path = of_c_node.format.file_format = 'PNG' # Links links.new(rl.outputs[0], v.inputs[0]) # link Image output to Viewer input links.new(rl.outputs[0], of_c_node.inputs[0]) # Define path where to save image base_path = "/tmp/images" #"./images_test_png" with open(datafilepath, "w") as datafile: #for frame in range(scene.frame_end+1): for frame in range(2): scene.frame_set(frame) #modification position de visée de l'oeil print('Jessica et Bertrand sont passés par la'); armature.pose.bones['eyetargetparent'].location[0] = -0.113456 camera_mat = camera_obj.matrix_world * mathutils.Matrix([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]]) camera_invmat = camera_mat.inverted() armature_mat = armature.matrix_world head_world = armature_mat*eyeLbone.head tail_world = armature_mat*eyeLbone.tail head_cam = camera_invmat*head_world tail_cam = camera_invmat*tail_world eye_centre = head_cam eye_radius = eyeLbone.bone.length pupil_gaze = (tail_cam - head_cam).normalized() pupil_radius = pupilLbone.scale[0] * pupil_base_radius pupil_centre = eye_centre + eye_radius*pupil_gaze #framestr = "{} | head_world: {} | tail_world: {} | vec_world: {} | head_cam: {} | tail_cam: {} | vec_cam {}".format( # frame, *[strVec(x) for x in [head_world,tail_world,vec_world,head_cam,tail_cam,vec_cam]]) framestr = "{} {} {} {} {} {}".format( frame, strVec(eye_centre), eye_radius, strVec(pupil_centre), strVec(pupil_gaze), pupil_radius ) print(framestr) print(framestr, file=datafile) #************* RENDER AND SAVE IMAGES *********** # Define image resolution for rendering scene.render.resolution_x = 250 scene.render.resolution_y = 250 # resolution percentage : have to be 100% to have the whole image resolution defined earlier scene.render.resolution_percentage = 100 # Activate which camera will be used for rendering # (if more than one camera are defined, the rendering have to be repeated for each camera) scene.camera = bpy.data.objects["Camera"] # get viewer pixels directly #pixels = bpy.data.images['Viewer Node'].pixels #print(len(pixels)) # size is always width * height * 4 (rgba) # copy buffer to numpy array for faster manipulation # arr = np.array(pixels[:]) # print('one pixel \n',arr[100:104]) # Define path where to save images str4 = base_path + "/image" of_c_node.base_path = str4 + "000" #out_z_node.base_path = str4 +"_Z_"+ "000" # Render bpy.ops.render.render()#write_still=True) #************* EXIT BLENDER *********** sys.exit(0)
Plusieurs astuces existent pour accelérer le rendu. Ces astuces diffèrent suivant si on est en “Blender render” ou en “Cycle render”.
Lien pour le “Cycle render”:
Lien pour “Blender render”:
Pour le calcul de Blender occupe 100% du CPU il faut indiquer le bon nombre de threads et la bonne taille de tuile (“Tile size”) : https://blenderartists.org/forum/showthread.php?410469-Why-Blender-Cycles-Rendering-Not-Using-100-CPU