Merge pull request #506 from pulquero/video

Blender 360 and VR video scripts

Former-commit-id: a236d85f2e
pull/1161/head
Piero Toffanin 2017-11-18 12:37:30 -05:00 zatwierdzone przez GitHub
commit 7511fc1e98
5 zmienionych plików z 180 dodań i 42 usunięć

Wyświetl plik

@ -2,6 +2,7 @@
# odm_photo # odm_photo
Renders photos from ODM generated texture models. Renders photos from ODM generated texture models.
Currently can produce 360 panoramic photos and 360 3D panoramic (VR) photos. Currently can produce 360 panoramic photos and 360 3D panoramic (VR) photos.
NB: the default resolution for 360 photos is 6000x3000 (maximum supported by Facebook).
## Requirements ## Requirements
* Blender * Blender
@ -21,3 +22,20 @@ To generate a 360 3D panoramic photo:
Output is `<project-path>/odm_photo/odm_photo_vr_L.jpg` and `<project-path>/odm_photo/odm_photo_vr_R.jpg`. Output is `<project-path>/odm_photo/odm_photo_vr_L.jpg` and `<project-path>/odm_photo/odm_photo_vr_R.jpg`.
**NB: argument order matters!** **NB: argument order matters!**
# odm_video
Renders videos from ODM generated texture models.
Currently can produce 360 panoramic videos.
NB: the default resolution is 4096x2048 (maximum supported by Facebook).
## Requirements
* Blender
* Python 2.7 (must be on your PATH)
* Spatial Media Metadata Injector (https://github.com/google/spatial-media/tree/master/spatialmedia) (place in `spatialmedia` subdirectory)
## Usage
To generate a 360 panoramic photo:
blender -b photo_360.blend --python odm_video.py -- <project-path> <camera-waypoints.xyz> <number-of-frames>
Output is `<project-path>/odm_video/odm_video_360.mp4`.

Wyświetl plik

@ -0,0 +1,45 @@
import bpy
import materials_utils
def loadMesh(file):
bpy.utils.register_module('materials_utils')
bpy.ops.import_scene.obj(filepath=file,
axis_forward='Y',
axis_up='Z')
bpy.ops.xps_tools.convert_to_cycles_all()
model = bpy.data.objects[-1]
minX = float('inf')
maxX = float('-inf')
minY = float('inf')
maxY = float('-inf')
minZ = float('inf')
maxZ = float('-inf')
for coord in model.bound_box:
x = coord[0]
y = coord[1]
z = coord[2]
minX = min(x, minX)
maxX = max(x, maxX)
minY = min(y, minY)
maxY = max(y, maxY)
minZ = min(z, minZ)
maxZ = max(z, maxZ)
model.location[2] += (maxZ - minZ)/2
surfaceShaderType = 'ShaderNodeEmission'
surfaceShaderName = 'Emission'
for m in bpy.data.materials:
nt = m.node_tree
nt.nodes.remove(nt.nodes['Color Mult'])
nt.nodes.remove(nt.nodes['Diffuse BSDF'])
nt.nodes.new(surfaceShaderType)
nt.links.new(nt.nodes['Material Output'].inputs[0],
nt.nodes[surfaceShaderName].outputs[0])
nt.links.new(nt.nodes[surfaceShaderName].inputs[0],
nt.nodes['Diffuse Texture'].outputs[0])

Wyświetl plik

@ -10,11 +10,8 @@
import sys import sys
import bpy import bpy
import materials_utils
import subprocess import subprocess
from common import loadMesh
surfaceShaderType = 'ShaderNodeEmission'
surfaceShaderName = 'Emission'
def main(): def main():
@ -24,47 +21,12 @@ def main():
projectHome = sys.argv[-1] projectHome = sys.argv[-1]
bpy.utils.register_module('materials_utils') loadMesh(projectHome +
'/odm_texturing/odm_textured_model_geo.obj')
bpy.ops.import_scene.obj(filepath=projectHome +
'/odm_texturing/odm_textured_model_geo.obj',
axis_forward='Y', axis_up='Z')
bpy.ops.xps_tools.convert_to_cycles_all()
model = bpy.data.objects[-1]
minX = float('inf')
maxX = float('-inf')
minY = float('inf')
maxY = float('-inf')
minZ = float('inf')
maxZ = float('-inf')
for coord in model.bound_box:
x = coord[0]
y = coord[1]
z = coord[2]
minX = min(x, minX)
maxX = max(x, maxX)
minY = min(y, minY)
maxY = max(y, maxY)
minZ = min(z, minZ)
maxZ = max(z, maxZ)
model.location[2] += (maxZ - minZ)/2
for m in bpy.data.materials:
nt = m.node_tree
nt.nodes.remove(nt.nodes['Color Mult'])
nt.nodes.remove(nt.nodes['Diffuse BSDF'])
nt.nodes.new(surfaceShaderType)
nt.links.new(nt.nodes['Material Output'].inputs[0],
nt.nodes[surfaceShaderName].outputs[0])
nt.links.new(nt.nodes[surfaceShaderName].inputs[0],
nt.nodes['Diffuse Texture'].outputs[0])
blendName = bpy.path.display_name_from_filepath(bpy.data.filepath) blendName = bpy.path.display_name_from_filepath(bpy.data.filepath)
fileName = projectHome + '/odm_photo/odm_' + blendName fileName = projectHome + '/odm_photo/odm_' + blendName
render = bpy.data.scenes[0].render render = bpy.data.scenes['Scene'].render
render.filepath = fileName render.filepath = fileName
bpy.ops.render.render(write_still=True) bpy.ops.render.render(write_still=True)

Wyświetl plik

@ -0,0 +1,113 @@
#!/usr/bin/env python
# Renders a video.
# To generate a 360 panoramic video:
# blender -b photo_360.blend --python odm_video.py -- <project-path> <camera-waypoints.xyz> <number-of-frames>
import sys
import subprocess
import os
import bpy
from common import loadMesh
def main():
if len(sys.argv) < 7 or sys.argv[-4] != '--':
sys.exit('Please provide the ODM project path, camera waypoints (xyz format), and number of frames.')
projectHome = sys.argv[-3]
waypointFile = sys.argv[-2]
numFrames = int(sys.argv[-1])
loadMesh(projectHome +
'/odm_texturing/odm_textured_model_geo.obj')
waypoints = loadWaypoints(waypointFile)
numWaypoints = len(waypoints)
scene = bpy.data.scenes['Scene']
# create path thru waypoints
curve = bpy.data.curves.new(name='CameraPath', type='CURVE')
curve.dimensions = '3D'
curve.twist_mode = 'Z_UP'
nurbs = curve.splines.new('NURBS')
nurbs.points.add(numWaypoints-1)
weight = 1
for i in range(numWaypoints):
nurbs.points[i].co[0] = waypoints[i][0]
nurbs.points[i].co[1] = waypoints[i][1]
nurbs.points[i].co[2] = waypoints[i][2]
nurbs.points[i].co[3] = weight
nurbs.use_endpoint_u = True
path = bpy.data.objects.new(name='CameraPath', object_data=curve)
scene.objects.link(path)
camera = bpy.data.objects['Camera']
camera.location[0] = 0
camera.location[1] = 0
camera.location[2] = 0
followPath = camera.constraints.new(type='FOLLOW_PATH')
followPath.name = 'CameraFollowPath'
followPath.target = path
followPath.use_curve_follow = True
animateContext = bpy.context.copy()
animateContext['constraint'] = followPath
bpy.ops.constraint.followpath_path_animate(animateContext,
constraint='CameraFollowPath',
frame_start=0,
length=numFrames)
blendName = bpy.path.display_name_from_filepath(bpy.data.filepath)
fileName = projectHome + '/odm_video/odm_' + blendName.replace('photo', 'video')
scene.frame_start = 0
scene.frame_end = numFrames
render = scene.render
render.filepath = fileName + '.mp4'
render.image_settings.file_format = 'FFMPEG'
if(render.use_multiview):
render.image_settings.stereo_3d_format.display_mode = 'TOPBOTTOM'
render.image_settings.views_format = 'STEREO_3D'
render.views[0].file_suffix = ''
format3d = 'top-bottom'
else:
width = render.resolution_x
height = render.resolution_y
format3d = 'none'
render.resolution_x = 4096
render.resolution_y = 2048
render.ffmpeg.audio_codec = 'AAC'
render.ffmpeg.codec = 'H264'
render.ffmpeg.format = 'MPEG4'
render.ffmpeg.video_bitrate = 45000
bpy.ops.render.render(animation=True)
writeMetadata(fileName+'.mp4', format3d)
def loadWaypoints(filename):
waypoints = []
with open(filename) as f:
for line in f:
xyz = line.split()
waypoints.append((float(xyz[0]), float(xyz[1]), float(xyz[2])))
return waypoints
def writeMetadata(filename, format3d):
subprocess.run(['python',
'spatialmedia',
'-i',
'--stereo='+format3d,
filename,
filename+'.injected'])
# check metadata injector was succesful
if os.path.exists(filename+'.injected'):
os.remove(filename)
os.rename(filename+'.injected', filename)
if __name__ == '__main__':
main()

Plik binarny nie jest wyświetlany.