Skip to content

Commit

Permalink
add support for object motion blur (issue #16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Theverat committed Jan 10, 2018
1 parent eb55aa5 commit 4b5adb8
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 5 deletions.
4 changes: 3 additions & 1 deletion engine/__init__.py
Expand Up @@ -41,10 +41,12 @@ def render(self, scene):
try:
if self.error:
# We have to re-raise the error from update() here because
# we can only use self.error_set() in this function
# this function (render()) is the only one that can use self.error_set()
# to show a warning to the user after the render finished.
raise self.error

if self._session is None:
# session is None, but self.error is not set -> User cancelled.
print("Export cancelled by user.")
return

Expand Down
49 changes: 47 additions & 2 deletions export/__init__.py
Expand Up @@ -3,7 +3,7 @@
from ..bin import pyluxcore
from .. import utils
from ..utils import node as utils_node
from . import blender_object, camera, config, light, material
from . import blender_object, camera, config, light, material, motion_blur
from .light import WORLD_BACKGROUND_LIGHT_NAME
from ..nodes.output import get_active_output

Expand Down Expand Up @@ -194,6 +194,15 @@ def create_session(self, engine, scene, context=None):
if engine.test_break():
return None

# Motion blur (TODO: camera blur)
if scene.camera:
blur_settings = scene.camera.data.luxcore.motion_blur

if blur_settings.enable and blur_settings.shutter > 0 and blur_settings.object_blur:
# Object motion blur
motion_blur_props = motion_blur.convert(scene, objs, self.exported_objects)
scene_props.Set(motion_blur_props)

# World
if scene.world and scene.world.luxcore.light != "none":
engine.update_stats("Export", "World")
Expand Down Expand Up @@ -362,7 +371,7 @@ def _scene_edit(self, context, changes, luxcore_scene):
continue

# exported_objects contains instances of ExportedObject and ExportedLight
if isinstance(exported_thing, blender_object.ExportedObject):
if isinstance(exported_thing, utils.ExportedObject):
remove_func = luxcore_scene.DeleteObject
else:
remove_func = luxcore_scene.DeleteLight
Expand All @@ -385,3 +394,39 @@ def _scene_edit(self, context, changes, luxcore_scene):
props.Set(world_props)

return props

# def _motion_blur(self, scene, scene_props, objs, step, time):
# """
# Create motion blur properties for all objs.
# You have to call this function at least two times to get valid motion blur properties.
# """
# for obj in objs:
# # TODO: check if object is even moved (compare matrices?)
#
# if obj.type == "LAMP" and obj.data.type == "AREA":
# # TODO: Area lights need special matrix calculation
# continue
#
# key = utils.make_key(obj)
#
# try:
# exported_thing = self.exported_objects[key]
# except KeyError:
# # This is not a problem, objects are skipped during epxort for various reasons
# continue
#
# for luxcore_name in exported_thing.luxcore_names:
# # exported_objects contains instances of ExportedObject and ExportedLight
# if isinstance(exported_thing, utils.ExportedObject):
# prefix = "scene.objects." + luxcore_name + "."
# else:
# prefix = "scene.lights." + luxcore_name + "."
#
# matrix_raw = obj.matrix_world.copy()
# matrix = utils.matrix_to_list(matrix_raw, scene, apply_worldscale=True, invert=True)
# definitions = {
# "motion.%d.time" % step: time,
# "motion.%d.transformation" % step: matrix,
# }
# print("definitions:", definitions)
# scene_props.Set(utils.create_props(prefix, definitions))
91 changes: 91 additions & 0 deletions export/motion_blur.py
@@ -0,0 +1,91 @@
import math
from ..bin import pyluxcore
from .. import utils


def convert(scene, objects, exported_objects):
assert scene.camera
motion_blur = scene.camera.data.luxcore.motion_blur

steps = motion_blur.steps
assert steps > 1

step_interval = motion_blur.shutter / (steps - 1)
times = [step_interval * step - motion_blur.shutter * 0.5 for step in range(steps)]
print("times:", times)

matrices = _get_matrices(scene, objects, exported_objects, steps, times)

# Find and delete entries of non-moving objects (where all matrices are equal)
for prefix, matrix_steps in list(matrices.items()):
# https://stackoverflow.com/a/10285205
first = matrix_steps[0]
matrices_equal = all(matrix == first for matrix in matrix_steps)

if matrices_equal:
# This object does not need motion blur because it does not move
del matrices[prefix]

# Export the properties for moving objects
props = pyluxcore.Properties()

for prefix, matrix_steps in matrices.items():
for step in range(steps):
time = times[step]
matrix = matrix_steps[step]
transformation = utils.matrix_to_list(matrix, scene, apply_worldscale=True, invert=True)
definitions = {
"motion.%d.time" % step: time,
"motion.%d.transformation" % step: transformation,
}
print("time:", time)
props.Set(utils.create_props(prefix, definitions))

return props


def _get_matrices(scene, objects, exported_objects, steps, times):
matrices = {} # {prefix: [matrix1, matrix2, ...]}

frame_center = scene.frame_current
subframe_center = scene.frame_subframe
print("orig frame, subframe:", frame_center, subframe_center)

for step in range(steps):
offset = times[step]
time = frame_center + subframe_center + offset
frame = math.floor(time)
subframe = time - frame
print("frame, subframe:", frame, subframe)
scene.frame_set(frame, subframe)

for obj in objects:
if obj.type == "LAMP" and obj.data.type == "AREA":
# TODO: Area lights need special matrix calculation
continue

key = utils.make_key(obj)

try:
exported_thing = exported_objects[key]
except KeyError:
# This is not a problem, objects are skipped during epxort for various reasons
continue

matrix = obj.matrix_world.copy()

for luxcore_name in exported_thing.luxcore_names:
# exported_objects contains instances of ExportedObject and ExportedLight
if isinstance(exported_thing, utils.ExportedObject):
prefix = "scene.objects." + luxcore_name + "."
else:
prefix = "scene.lights." + luxcore_name + "."

if step == 0:
matrices[prefix] = [matrix]
else:
matrices[prefix].append(matrix)

# Restore original frame
scene.frame_set(frame_center, subframe_center)
return matrices
22 changes: 20 additions & 2 deletions properties/camera.py
@@ -1,5 +1,6 @@
import bpy
from bpy.props import PointerProperty, BoolProperty, FloatProperty
from bpy.props import PointerProperty, BoolProperty, FloatProperty, IntProperty
from bpy.types import PropertyGroup

FSTOP_DESC = (
"Aperture, lower values result in stronger depth of field effect and "
Expand All @@ -12,12 +13,27 @@
"The clipping plane object will not be exported"
)

SHUTTER_TIME_DESC = "Time in seconds between shutter open and shutter close, higher values lead to more blur"


def init():
bpy.types.Camera.luxcore = PointerProperty(type=LuxCoreCameraProps)


class LuxCoreCameraProps(bpy.types.PropertyGroup):
class LuxCoreMotionBlur(PropertyGroup):
"""
motion_blur.*
"""
enable = BoolProperty(name="Enable Motion Blur", default=False)
object_blur = BoolProperty(name="Object", default=True, description="Blur moving objects")
camera_blur = BoolProperty(name="Camera", default=True, description="Blur if camera moves")
shutter = FloatProperty(name="Shutter (s)", default=0.1, min=0, soft_max=2, description=SHUTTER_TIME_DESC)
# TODO: add description about accelerators (Embree only supports 2 steps?) and what happens
# if more than 2 steps are used
steps = IntProperty(name="Steps", default=2, min=2, description="Number of substeps")


class LuxCoreCameraProps(PropertyGroup):
# TODO descriptions
use_clipping = BoolProperty(name="Clipping:", default=False)
use_dof = BoolProperty(name="Use Depth of Field", default=False)
Expand All @@ -26,3 +42,5 @@ class LuxCoreCameraProps(bpy.types.PropertyGroup):
fstop = FloatProperty(name="F-stop", default=2.8, min=0.01, description=FSTOP_DESC)
use_clipping_plane = BoolProperty(name="Use Clipping Plane:", default=False, description=CLIPPING_PLANE_DESC)
clipping_plane = PointerProperty(name="Clipping Plane", type=bpy.types.Object, description=CLIPPING_PLANE_DESC)

motion_blur = PointerProperty(type=LuxCoreMotionBlur)
Binary file not shown.
26 changes: 26 additions & 0 deletions ui/camera.py
Expand Up @@ -114,3 +114,29 @@ def draw(self, context):
col.prop(dof_options, "fstop")
if dof_options.use_high_quality and hq_support:
col.prop(dof_options, "blades")


class LuxCoreMotionBlur(CameraButtonsPanel, Panel):
bl_label = "Motion Blur"
COMPAT_ENGINES = {"LUXCORE"}

def draw_header(self, context):
self.layout.prop(context.camera.luxcore.motion_blur, "enable", text="")

def draw(self, context):
layout = self.layout
cam = context.camera
motion_blur = cam.luxcore.motion_blur
layout.active = motion_blur.enable

split = layout.split()

col = split.column(align=True)
col.prop(motion_blur, "object_blur")
col.prop(motion_blur, "camera_blur")

col = split.column(align=True)
col.prop(motion_blur, "shutter")
col.prop(motion_blur, "steps")

layout.label("Note: camera blur not implemented yet", icon="INFO")

0 comments on commit 4b5adb8

Please sign in to comment.