701 lines
23 KiB
C++
701 lines
23 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "blender/light_linking.h"
|
|
#include "blender/object_cull.h"
|
|
#include "blender/sync.h"
|
|
#include "blender/util.h"
|
|
|
|
#include "scene/camera.h"
|
|
#include "scene/integrator.h"
|
|
#include "scene/light.h"
|
|
#include "scene/mesh.h"
|
|
#include "scene/object.h"
|
|
#include "scene/particles.h"
|
|
#include "scene/scene.h"
|
|
#include "scene/shader.h"
|
|
#include "scene/shader_graph.h"
|
|
#include "scene/shader_nodes.h"
|
|
|
|
#include "util/hash.h"
|
|
#include "util/log.h"
|
|
#include "util/task.h"
|
|
|
|
#include "BKE_duplilist.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_material.hh"
|
|
#include "BKE_object.hh"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "RE_engine.h"
|
|
|
|
using blender::Object;
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Utilities */
|
|
|
|
bool BlenderSync::BKE_object_is_modified(blender::Object &b_ob)
|
|
{
|
|
/* test if we can instance or if the object is modified */
|
|
if (b_ob.type == blender::OB_MBALL) {
|
|
/* Multi-user and dupli meta-balls are fused, can't instance. */
|
|
return true;
|
|
}
|
|
const int settings = preview ? blender::eModifierMode_Realtime : blender::eModifierMode_Render;
|
|
if ((blender::BKE_object_is_modified(b_scene, &b_ob) & settings) != 0) {
|
|
/* modifiers */
|
|
return true;
|
|
}
|
|
|
|
/* Object level material links. Note the geometry material slot array may not match
|
|
* the object matbits array, so we need to guard against out of bounds. */
|
|
for (const int i : blender::IndexRange(BKE_object_material_count_eval(&b_ob))) {
|
|
if (i < b_ob.totcol && b_ob.matbits && b_ob.matbits[i] != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BlenderSync::object_is_geometry(BObjectInfo &b_ob_info)
|
|
{
|
|
blender::ID *b_ob_data = b_ob_info.object_data;
|
|
|
|
if (!b_ob_data) {
|
|
return false;
|
|
}
|
|
|
|
const blender::ObjectType type = b_ob_info.iter_object->type;
|
|
|
|
if (type == blender::OB_VOLUME || type == blender::OB_CURVES || type == blender::OB_POINTCLOUD ||
|
|
type == blender::OB_LAMP)
|
|
{
|
|
/* Will be exported as geometry. */
|
|
return true;
|
|
}
|
|
|
|
return GS(b_ob_data->name) == blender::ID_ME;
|
|
}
|
|
|
|
bool BlenderSync::object_can_have_geometry(blender::Object &b_ob)
|
|
{
|
|
const blender::ObjectType type = b_ob.type;
|
|
switch (type) {
|
|
case blender::OB_MESH:
|
|
case blender::OB_CURVES_LEGACY:
|
|
case blender::OB_SURF:
|
|
case blender::OB_MBALL:
|
|
case blender::OB_FONT:
|
|
case blender::OB_CURVES:
|
|
case blender::OB_POINTCLOUD:
|
|
case blender::OB_VOLUME:
|
|
/* TODO(weizhen): OB_LAMP */
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool BlenderSync::object_is_light(blender::Object &b_ob)
|
|
{
|
|
blender::ID *b_ob_data = object_get_data(b_ob, true);
|
|
|
|
return (b_ob_data && GS(b_ob_data->name) == blender::ID_LA);
|
|
}
|
|
|
|
bool BlenderSync::object_is_camera(blender::Object &b_ob)
|
|
{
|
|
blender::ID *b_ob_data = object_get_data(b_ob, true);
|
|
|
|
return (b_ob_data && GS(b_ob_data->name) == blender::ID_CA);
|
|
}
|
|
|
|
void BlenderSync::sync_object_motion_init(blender::Object &b_parent,
|
|
blender::Object &b_ob,
|
|
Object *object)
|
|
{
|
|
/* Initialize motion blur for object, detecting if it's enabled and creating motion
|
|
* steps array if so. */
|
|
array<Transform> motion = object->get_motion();
|
|
|
|
Geometry *geom = object->get_geometry();
|
|
if (!geom) {
|
|
return;
|
|
}
|
|
|
|
int motion_steps = 0;
|
|
bool use_motion_blur = false;
|
|
|
|
const Scene::MotionType need_motion = scene->need_motion();
|
|
if (need_motion == Scene::MOTION_BLUR) {
|
|
motion_steps = object_motion_steps(b_parent, b_ob, Object::MAX_MOTION_STEPS);
|
|
if (motion_steps && object_use_deform_motion(b_parent, b_ob)) {
|
|
use_motion_blur = true;
|
|
}
|
|
}
|
|
else if (need_motion != Scene::MOTION_NONE) {
|
|
motion_steps = 3;
|
|
}
|
|
|
|
geom->set_use_motion_blur(use_motion_blur);
|
|
|
|
motion.resize(motion_steps, transform_empty());
|
|
|
|
if (motion_steps) {
|
|
motion[motion_steps / 2] = object->get_tfm();
|
|
|
|
/* update motion socket before trying to access object->motion_time */
|
|
object->set_motion(motion);
|
|
|
|
for (size_t step = 0; step < motion_steps; step++) {
|
|
motion_times.insert(object->motion_time(step));
|
|
}
|
|
}
|
|
else {
|
|
object->set_motion(motion);
|
|
}
|
|
}
|
|
|
|
Object *BlenderSync::sync_object(blender::ViewLayer &b_view_layer,
|
|
blender::Object &b_ob,
|
|
blender::DEGObjectIterData &b_deg_iter_data,
|
|
const float motion_time,
|
|
bool use_particle_hair,
|
|
bool show_lights,
|
|
BlenderObjectCulling &culling,
|
|
TaskPool *geom_task_pool)
|
|
{
|
|
const bool is_instance = b_deg_iter_data.dupli_object_current;
|
|
blender::Object *b_parent = is_instance ? b_deg_iter_data.dupli_parent : &b_ob;
|
|
blender::Object *b_real_object = is_instance ? b_deg_iter_data.dupli_object_current->ob : &b_ob;
|
|
const bool use_adaptive_subdiv = object_subdivision_type(
|
|
*b_real_object, preview, use_adaptive_subdivision) !=
|
|
Mesh::SUBDIVISION_NONE;
|
|
BObjectInfo b_ob_info{
|
|
&b_ob, b_real_object, object_get_data(b_ob, use_adaptive_subdiv), use_adaptive_subdiv};
|
|
const bool motion = motion_time != 0.0f;
|
|
const Transform tfm = get_transform(b_ob.object_to_world());
|
|
const int *persistent_id = nullptr;
|
|
if (is_instance) {
|
|
persistent_id = b_deg_iter_data.dupli_object_current->persistent_id;
|
|
if (!motion && !b_ob_info.is_real_object_data()) {
|
|
/* Remember which object data the geometry is coming from, so that we can sync it when the
|
|
* object has changed. */
|
|
instance_geometries_by_object[b_ob_info.real_object].insert(b_ob_info.object_data);
|
|
}
|
|
}
|
|
|
|
/* only interested in object that we can create geometry from */
|
|
if (!object_is_geometry(b_ob_info)) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Perform object culling. */
|
|
if (object_is_light(b_ob)) {
|
|
if (!show_lights) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if (culling.test(scene, b_ob, tfm)) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Visibility flags for both parent and child. */
|
|
blender::PointerRNA b_ob_rna_ptr = RNA_id_pointer_create(&b_ob.id);
|
|
blender::PointerRNA cobject = RNA_pointer_get(&b_ob_rna_ptr, "cycles");
|
|
/* Note base_parent is null for objects from the background scene. */
|
|
const blender::Base *base_parent = BKE_view_layer_base_find(&b_view_layer, b_parent);
|
|
const bool use_holdout = (base_parent && (base_parent->flag & blender::BASE_HOLDOUT) != 0) ||
|
|
((b_parent->visibility_flag & blender::OB_HOLDOUT) != 0);
|
|
PathRayVisibility visibility = object_ray_visibility(b_ob);
|
|
|
|
if (b_parent != &b_ob) {
|
|
visibility &= object_ray_visibility(*b_parent);
|
|
}
|
|
|
|
/* TODO: make holdout objects on excluded layer invisible for non-camera rays. */
|
|
#if 0
|
|
if (use_holdout && (layer_flag & view_layer.exclude_layer)) {
|
|
visibility &= ~(PATH_RAY_VISIBILITY_ALL & ~PATH_RAY_VISIBILITY_CAMERA);
|
|
}
|
|
#endif
|
|
|
|
/* Clear camera visibility for indirect only objects. */
|
|
const bool use_indirect_only = !use_holdout && base_parent &&
|
|
((base_parent->flag & blender::BASE_INDIRECT_ONLY) != 0);
|
|
if (use_indirect_only) {
|
|
visibility &= ~PATH_RAY_VISIBILITY_CAMERA;
|
|
}
|
|
|
|
/* Don't export completely invisible objects. */
|
|
if (visibility == PATH_RAY_VISIBILITY_NONE) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Use task pool only for non-instances, since sync_dupli_particle accesses
|
|
* geometry. This restriction should be removed for better performance. */
|
|
TaskPool *object_geom_task_pool = (is_instance) ? nullptr : geom_task_pool;
|
|
|
|
/* key to lookup object */
|
|
const ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, use_particle_hair);
|
|
Object *object;
|
|
|
|
/* motion vector case */
|
|
if (motion) {
|
|
object = object_map.find(key);
|
|
|
|
if (object && object->use_motion()) {
|
|
/* Set transform at matching motion time step. */
|
|
const int time_index = object->motion_step(motion_time);
|
|
if (time_index >= 0) {
|
|
object->set_motion_tfm(tfm, time_index);
|
|
}
|
|
|
|
/* mesh deformation */
|
|
if (object->get_geometry()) {
|
|
sync_geometry_motion(
|
|
b_ob_info, object, motion_time, use_particle_hair, object_geom_task_pool);
|
|
}
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
/* test if we need to sync */
|
|
bool object_updated = object_map.add_or_update(&object, &b_ob.id, &b_parent->id, key) ||
|
|
!object->tfm_equals(tfm);
|
|
|
|
/* mesh sync */
|
|
Geometry *geometry = sync_geometry(
|
|
b_ob_info, object_updated, use_particle_hair, object_geom_task_pool);
|
|
object->set_geometry(geometry);
|
|
|
|
/* special case not tracked by object update flags */
|
|
|
|
if (sync_object_attributes(b_ob, b_deg_iter_data, object)) {
|
|
object_updated = true;
|
|
}
|
|
|
|
/* holdout */
|
|
object->set_use_holdout(use_holdout);
|
|
|
|
object->set_visibility(visibility);
|
|
|
|
object->set_is_shadow_catcher((b_ob.visibility_flag & blender::OB_SHADOW_CATCHER) != 0 ||
|
|
(b_parent->visibility_flag & blender::OB_SHADOW_CATCHER) != 0);
|
|
|
|
object->set_shadow_terminator_shading_offset(b_ob.shadow_terminator_shading_offset);
|
|
|
|
object->set_shadow_terminator_geometry_offset(b_ob.shadow_terminator_geometry_offset);
|
|
|
|
float ao_distance = get_float(cobject, "ao_distance");
|
|
if (ao_distance == 0.0f && b_parent != &b_ob) {
|
|
blender::PointerRNA b_parent_rna_ptr = RNA_id_pointer_create(&b_parent->id);
|
|
blender::PointerRNA cparent = RNA_pointer_get(&b_parent_rna_ptr, "cycles");
|
|
ao_distance = get_float(cparent, "ao_distance");
|
|
}
|
|
object->set_ao_distance(ao_distance);
|
|
|
|
const bool is_caustics_caster = get_boolean(cobject, "is_caustics_caster");
|
|
object->set_is_caustics_caster(is_caustics_caster);
|
|
|
|
const bool is_caustics_receiver = get_boolean(cobject, "is_caustics_receiver");
|
|
object->set_is_caustics_receiver(is_caustics_receiver);
|
|
|
|
object->set_is_bake_target(b_ob_info.real_object == b_bake_target);
|
|
|
|
/* sync the asset name for Cryptomatte */
|
|
blender::Object *parent = b_ob.parent;
|
|
ustring parent_name;
|
|
if (parent) {
|
|
while (parent->parent) {
|
|
parent = parent->parent;
|
|
}
|
|
parent_name = BKE_id_name(parent->id);
|
|
}
|
|
else {
|
|
parent_name = BKE_id_name(b_ob.id);
|
|
}
|
|
object->set_asset_name(parent_name);
|
|
|
|
/* object sync
|
|
* transform comparison should not be needed, but duplis don't work perfect
|
|
* in the depsgraph and may not signal changes, so this is a workaround */
|
|
const bool do_sync = object->is_modified() || object_updated ||
|
|
(object->get_geometry() && object->get_geometry()->is_modified());
|
|
if (do_sync) {
|
|
object->name = BKE_id_name(b_ob.id);
|
|
object->set_pass_id(b_ob.index);
|
|
const float *object_color = b_ob.color;
|
|
object->set_color(make_float3(object_color[0], object_color[1], object_color[2]));
|
|
object->set_alpha(object_color[3]);
|
|
object->set_tfm(tfm);
|
|
|
|
/* dupli texture coordinates and random_id */
|
|
if (is_instance) {
|
|
const float *orco = b_deg_iter_data.dupli_object_current->orco;
|
|
object->set_dupli_generated(0.5f * make_float3(orco[0], orco[1], orco[2]) -
|
|
make_float3(0.5f, 0.5f, 0.5f));
|
|
const float *uv = b_deg_iter_data.dupli_object_current->uv;
|
|
object->set_dupli_uv(make_float2(uv[0], uv[1]));
|
|
object->set_random_id(b_deg_iter_data.dupli_object_current->random_id);
|
|
}
|
|
else {
|
|
object->set_dupli_generated(zero_float3());
|
|
object->set_dupli_uv(zero_float2());
|
|
object->set_random_id(hash_uint2(hash_string(object->name.c_str()), 0));
|
|
}
|
|
|
|
/* Light group and linking. */
|
|
string lightgroup = b_ob.lightgroup ? b_ob.lightgroup->name : "";
|
|
if (lightgroup.empty()) {
|
|
lightgroup = b_parent->lightgroup ? b_parent->lightgroup->name : "";
|
|
}
|
|
object->set_lightgroup(ustring(lightgroup));
|
|
|
|
object->set_light_set_membership(BlenderLightLink::get_light_set_membership(b_parent, b_ob));
|
|
object->set_receiver_light_set(BlenderLightLink::get_receiver_light_set(b_parent, b_ob));
|
|
object->set_shadow_set_membership(BlenderLightLink::get_shadow_set_membership(b_parent, b_ob));
|
|
object->set_blocker_shadow_set(BlenderLightLink::get_blocker_shadow_set(b_parent, b_ob));
|
|
}
|
|
|
|
sync_object_motion_init(*b_parent, b_ob, object);
|
|
|
|
if (do_sync || object->motion_is_modified()) {
|
|
object->tag_update(scene);
|
|
}
|
|
|
|
if (is_instance) {
|
|
/* Sync possible particle data. */
|
|
sync_dupli_particle(*b_parent, b_deg_iter_data, b_ob, object);
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
static float4 lookup_instance_property(blender::Object &ob,
|
|
blender::DEGObjectIterData &b_deg_iter_data,
|
|
const string &name,
|
|
bool use_instancer)
|
|
{
|
|
blender::DupliObject *dupli = nullptr;
|
|
blender::Object *dupli_parent = nullptr;
|
|
|
|
/* If requesting instance data, check the parent particle system and object. */
|
|
if (use_instancer && b_deg_iter_data.dupli_object_current) {
|
|
dupli = b_deg_iter_data.dupli_object_current;
|
|
dupli_parent = b_deg_iter_data.dupli_parent;
|
|
}
|
|
|
|
float4 value;
|
|
BKE_object_dupli_find_rgba_attribute(&ob, dupli, dupli_parent, name.c_str(), &value.x);
|
|
|
|
return value;
|
|
}
|
|
|
|
bool BlenderSync::sync_object_attributes(blender::Object &b_ob,
|
|
blender::DEGObjectIterData &b_deg_iter_data,
|
|
Object *object)
|
|
{
|
|
/* Find which attributes are needed. */
|
|
AttributeRequestSet requests = object->get_geometry()->needed_attributes();
|
|
|
|
/* Delete attributes that became unnecessary. */
|
|
vector<ParamValue> &attributes = object->attributes;
|
|
bool changed = false;
|
|
|
|
for (int i = attributes.size() - 1; i >= 0; i--) {
|
|
if (!requests.find(attributes[i].name())) {
|
|
attributes.erase(attributes.begin() + i);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
/* Update attribute values. */
|
|
for (const AttributeRequest &req : requests.requests) {
|
|
const ustring name = req.name;
|
|
|
|
std::string real_name;
|
|
const int type = blender_attribute_name_split_type(name, &real_name);
|
|
|
|
if (type == blender::SHD_ATTRIBUTE_OBJECT || type == blender::SHD_ATTRIBUTE_INSTANCER) {
|
|
const bool use_instancer = (type == blender::SHD_ATTRIBUTE_INSTANCER);
|
|
float4 value = lookup_instance_property(b_ob, b_deg_iter_data, real_name, use_instancer);
|
|
|
|
/* Try finding the existing attribute value. */
|
|
ParamValue *param = nullptr;
|
|
|
|
for (size_t i = 0; i < attributes.size(); i++) {
|
|
if (attributes[i].name() == name) {
|
|
param = &attributes[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Replace or add the value. */
|
|
const ParamValue new_param(name, TypeFloat4, 1, &value);
|
|
assert(new_param.datasize() == sizeof(value));
|
|
|
|
if (!param) {
|
|
changed = true;
|
|
attributes.push_back(new_param);
|
|
}
|
|
else {
|
|
/* Cannot use param->get<float4>, ParamValue storage is not guaranteed to be aligned. */
|
|
const float *param_data = static_cast<const float *>(param->data());
|
|
if (make_float4(param_data[0], param_data[1], param_data[2], param_data[3]) != value) {
|
|
changed = true;
|
|
*param = new_param;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Object Loop */
|
|
|
|
void BlenderSync::sync_objects(blender::Depsgraph &b_depsgraph,
|
|
blender::bScreen *b_screen,
|
|
blender::View3D *b_v3d,
|
|
const float motion_time)
|
|
{
|
|
/* Task pool for multithreaded geometry sync. */
|
|
TaskPool geom_task_pool;
|
|
|
|
/* layer data */
|
|
const bool motion = motion_time != 0.0f;
|
|
|
|
if (!motion) {
|
|
/* prepare for sync */
|
|
geometry_map.pre_sync();
|
|
object_map.pre_sync();
|
|
procedural_map.pre_sync();
|
|
particle_system_map.pre_sync();
|
|
motion_times.clear();
|
|
}
|
|
else {
|
|
geometry_motion_synced.clear();
|
|
}
|
|
|
|
if (!motion) {
|
|
/* Object to geometry instance mapping is built for the reference time, as other
|
|
* times just look up the corresponding geometry. */
|
|
instance_geometries_by_object.clear();
|
|
}
|
|
|
|
/* initialize culling */
|
|
BlenderObjectCulling culling(scene, *b_scene);
|
|
|
|
/* object loop */
|
|
bool cancel = false;
|
|
const bool show_lights =
|
|
BlenderViewportParameters(b_screen, b_v3d, use_developer_ui).use_scene_lights;
|
|
|
|
blender::ViewLayer &b_view_layer = *DEG_get_evaluated_view_layer(&b_depsgraph);
|
|
|
|
BKE_view_layer_synced_ensure(*b_data, b_scene, &b_view_layer);
|
|
|
|
blender::DEGObjectIterSettings deg_iter_settings{};
|
|
deg_iter_settings.depsgraph = &b_depsgraph;
|
|
deg_iter_settings.flags = DEG_OBJECT_ITER_FOR_RENDER_ENGINE_FLAGS;
|
|
blender::DEGObjectIterData deg_iter_data{};
|
|
deg_iter_data.settings = °_iter_settings;
|
|
deg_iter_data.graph = deg_iter_settings.depsgraph;
|
|
deg_iter_data.flag = deg_iter_settings.flags;
|
|
|
|
ITER_BEGIN (blender::DEG_iterator_objects_begin,
|
|
blender::DEG_iterator_objects_next,
|
|
blender::DEG_iterator_objects_end,
|
|
°_iter_data,
|
|
blender::Object *,
|
|
b_ob)
|
|
{
|
|
/* Viewport visibility. */
|
|
const bool show_in_viewport = !b_v3d || BKE_object_is_visible_in_viewport(b_v3d, b_ob);
|
|
if (show_in_viewport == false) {
|
|
continue;
|
|
}
|
|
|
|
/* Load per-object culling data. */
|
|
culling.init_object(scene, *b_ob);
|
|
|
|
const int ob_visibility = BKE_object_visibility(b_ob, deg_iter_data.eval_mode);
|
|
|
|
/* Ensure the object geom supporting the hair is processed before adding
|
|
* the hair processing task to the task pool, calling .to_mesh() on the
|
|
* same object in parallel does not work. */
|
|
const bool sync_hair = (ob_visibility & blender::OB_VISIBLE_PARTICLES) != 0 &&
|
|
object_has_particle_hair(b_ob);
|
|
|
|
/* Object itself. */
|
|
if ((ob_visibility & blender::OB_VISIBLE_SELF) != 0) {
|
|
sync_object(b_view_layer,
|
|
*b_ob,
|
|
deg_iter_data,
|
|
motion_time,
|
|
false,
|
|
show_lights,
|
|
culling,
|
|
sync_hair ? nullptr : &geom_task_pool);
|
|
}
|
|
|
|
/* Particle hair as separate object. */
|
|
if (sync_hair) {
|
|
sync_object(b_view_layer,
|
|
*b_ob,
|
|
deg_iter_data,
|
|
motion_time,
|
|
true,
|
|
show_lights,
|
|
culling,
|
|
&geom_task_pool);
|
|
}
|
|
|
|
cancel = progress.get_cancel();
|
|
if (cancel) {
|
|
break;
|
|
}
|
|
}
|
|
ITER_END;
|
|
|
|
geom_task_pool.wait_work();
|
|
|
|
progress.set_sync_status("");
|
|
|
|
if (!cancel && !motion) {
|
|
/* After object for world_use_portal. */
|
|
sync_background_light(b_screen, b_v3d);
|
|
|
|
/* Handle removed data and modified pointers, as this may free memory, delete Nodes in the
|
|
* right order to ensure that dependent data is freed after their users. Objects should be
|
|
* freed before particle systems and geometries. */
|
|
object_map.post_sync();
|
|
geometry_map.post_sync();
|
|
particle_system_map.post_sync();
|
|
procedural_map.post_sync();
|
|
}
|
|
|
|
if (motion) {
|
|
geometry_motion_synced.clear();
|
|
}
|
|
}
|
|
|
|
void BlenderSync::sync_objects_and_motion(blender::RenderData &b_render,
|
|
blender::Depsgraph &b_depsgraph,
|
|
blender::bScreen *b_screen,
|
|
blender::View3D *b_v3d,
|
|
blender::RegionView3D *b_rv3d,
|
|
const int width,
|
|
const int height,
|
|
void **python_thread_state)
|
|
{
|
|
/* get camera object here to deal with camera switch */
|
|
blender::Object *b_cam = get_camera_object(b_v3d, b_rv3d);
|
|
|
|
const int frame_center = b_scene->r.cfra;
|
|
const float subframe_center = b_scene->r.subframe;
|
|
float frame_center_delta = 0.0f;
|
|
|
|
if (scene->need_motion() == Scene::MOTION_BLUR &&
|
|
scene->camera->get_motion_position() != MOTION_POSITION_CENTER)
|
|
{
|
|
const float shuttertime = scene->camera->get_shuttertime();
|
|
if (scene->camera->get_motion_position() == MOTION_POSITION_END) {
|
|
frame_center_delta = -shuttertime * 0.5f;
|
|
}
|
|
else {
|
|
assert(scene->camera->get_motion_position() == MOTION_POSITION_START);
|
|
frame_center_delta = shuttertime * 0.5f;
|
|
}
|
|
|
|
const float time = frame_center + subframe_center + frame_center_delta;
|
|
const int frame = (int)floorf(time);
|
|
const float subframe = time - frame;
|
|
python_thread_state_restore(python_thread_state);
|
|
RE_engine_frame_set(b_engine, frame, subframe);
|
|
python_thread_state_save(python_thread_state);
|
|
if (b_cam) {
|
|
sync_camera_motion(b_render, b_cam, width, height, 0.0f);
|
|
}
|
|
}
|
|
|
|
sync_objects(b_depsgraph, b_screen, b_v3d);
|
|
|
|
/* In the viewport, only motion between previous frame and current frame is of interest, which is
|
|
* kept updated separately. */
|
|
if (b_v3d) {
|
|
assert(scene->need_motion() == Scene::MOTION_NONE ||
|
|
scene->need_motion() == Scene::MOTION_PASS_INTERACTIVE);
|
|
return;
|
|
}
|
|
|
|
if (scene->need_motion() == Scene::MOTION_NONE) {
|
|
return;
|
|
}
|
|
|
|
/* Insert motion times from camera. Motion times from other objects
|
|
* have already been added in a sync_objects call. */
|
|
if (b_cam) {
|
|
const uint camera_motion_steps = object_motion_steps(*b_cam, *b_cam);
|
|
for (size_t step = 0; step < camera_motion_steps; step++) {
|
|
motion_times.insert(scene->camera->motion_time(step));
|
|
}
|
|
}
|
|
|
|
/* Check which geometry already has motion blur so it can be skipped. */
|
|
geometry_motion_attribute_synced.clear();
|
|
for (Geometry *geom : scene->geometry) {
|
|
const Attribute *attr_P = geom->attributes.find(ATTR_STD_POSITION);
|
|
if (attr_P && attr_P->has_motion()) {
|
|
geometry_motion_attribute_synced.insert(geom);
|
|
}
|
|
}
|
|
|
|
/* note iteration over motion_times set happens in sorted order */
|
|
for (const float relative_time : motion_times) {
|
|
/* center time is already handled. */
|
|
if (relative_time == 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
LOG_DEBUG << "Synchronizing motion for the relative time " << relative_time << ".";
|
|
|
|
/* fixed shutter time to get previous and next frame for motion pass */
|
|
const float shuttertime = scene->motion_shutter_time();
|
|
|
|
/* compute frame and subframe time */
|
|
const float time = frame_center + subframe_center + frame_center_delta +
|
|
relative_time * shuttertime * 0.5f;
|
|
const int frame = (int)floorf(time);
|
|
const float subframe = time - frame;
|
|
|
|
/* change frame */
|
|
python_thread_state_restore(python_thread_state);
|
|
RE_engine_frame_set(b_engine, frame, subframe);
|
|
python_thread_state_save(python_thread_state);
|
|
|
|
/* Syncs camera motion if relative_time is one of the camera's motion times. */
|
|
sync_camera_motion(b_render, b_cam, width, height, relative_time);
|
|
|
|
/* sync object */
|
|
sync_objects(b_depsgraph, b_screen, b_v3d, relative_time);
|
|
}
|
|
|
|
geometry_motion_attribute_synced.clear();
|
|
|
|
/* we need to set the python thread state again because this
|
|
* function assumes it is being executed from python and will
|
|
* try to save the thread state */
|
|
python_thread_state_restore(python_thread_state);
|
|
RE_engine_frame_set(b_engine, frame_center, subframe_center);
|
|
python_thread_state_save(python_thread_state);
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|