Cycles: Camera frustum space object culling scene simplification
authorSergey Sharybin <sergey.vfx@gmail.com>
Fri, 10 Apr 2015 13:09:58 +0000 (18:09 +0500)
committerSergey Sharybin <sergey.vfx@gmail.com>
Sat, 18 Jul 2015 16:17:22 +0000 (18:17 +0200)
The idea is to give artists a simplier way to control memory usage in such
scenes as grass fields by doing automatic object culling based on whether
object is visible in the frame or not.

This is controlled on per-object level. In order to use this option few steps
are required:

- Enable Simplify in scene settings

- Enable Camera Cull option in the Simplify panel

- Set camera cull margin (measured in relative value to the render resolution)
  This setting is used to avoid possible flickering caused by changes in shadow
  which are cast by objects outside of the frame.

- Enable Camera Cull for objects which are desired to be culled
  (object culling option could be found in Option panel in object buttons).

There is still room for improvements, but this worked quite well during
Gooseberry open movie project, so think it's useful feature even in it's current
non-ideal state.

intern/cycles/blender/addon/properties.py
intern/cycles/blender/addon/ui.py
intern/cycles/blender/blender_object.cpp
intern/cycles/blender/blender_sync.h

index 87c0f7c154ab7d3732b2a763d790338555854640..4ba335f3b4b236c6f8d442a5a7f9465d5b6176a0 100644 (file)
@@ -511,6 +511,19 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
                 ),
             )
 
+        cls.use_camera_cull = BoolProperty(
+                name="Use Camera Cull",
+                description="Allow objects to be culled based on the camera frustum",
+                default=False,
+                )
+
+        cls.camera_cull_margin = FloatProperty(
+                name="Camera Cull Margin",
+                description="Margin for the camera space culling",
+                default=0.1,
+                min=0.0, max=5.0
+                )
+
     @classmethod
     def unregister(cls):
         del bpy.types.Scene.cycles
@@ -896,6 +909,12 @@ class CyclesObjectBlurSettings(bpy.types.PropertyGroup):
                 default=1,
                 )
 
+        cls.use_camera_cull = BoolProperty(
+                name="Use Camera Cull",
+                description="Allow this object and it's duplicators to be culled by camera space culling",
+                default=False,
+                )
+
     @classmethod
     def unregister(cls):
         del bpy.types.Object.cycles
index f7179e2472b53dbc2ec163dde99728666c6c6b47..8bb086df06a25a1828f407ebe8003f05cf28042e 100644 (file)
@@ -687,8 +687,8 @@ class CyclesObject_PT_motion_blur(CyclesButtonsPanel, Panel):
         sub.prop(cob, "motion_steps", text="Steps")
 
 
-class CyclesObject_PT_ray_visibility(CyclesButtonsPanel, Panel):
-    bl_label = "Ray Visibility"
+class CyclesObject_PT_cycles_settings(CyclesButtonsPanel, Panel):
+    bl_label = "Cycles Settings"
     bl_context = "object"
     bl_options = {'DEFAULT_CLOSED'}
 
@@ -702,9 +702,13 @@ class CyclesObject_PT_ray_visibility(CyclesButtonsPanel, Panel):
     def draw(self, context):
         layout = self.layout
 
+        scene = context.scene
+        cscene = scene.cycles
         ob = context.object
+        cob = ob.cycles
         visibility = ob.cycles_visibility
 
+        layout.label(text="Ray Visibility:")
         flow = layout.column_flow()
 
         flow.prop(visibility, "camera")
@@ -716,6 +720,12 @@ class CyclesObject_PT_ray_visibility(CyclesButtonsPanel, Panel):
         if ob.type != 'LAMP':
             flow.prop(visibility, "shadow")
 
+        col = layout.column()
+        col.label(text="Performance:")
+        row = col.row()
+        row.active = scene.render.use_simplify and cscene.use_camera_cull
+        row.prop(cob, "use_camera_cull")
+
 
 class CYCLES_OT_use_shading_nodes(Operator):
     """Enable nodes on a material, world or lamp"""
@@ -1450,7 +1460,9 @@ class CyclesScene_PT_simplify(CyclesButtonsPanel, Panel):
     def draw(self, context):
         layout = self.layout
 
-        rd = context.scene.render
+        scene = context.scene
+        rd = scene.render
+        cscene = scene.cycles
 
         layout.active = rd.use_simplify
         split = layout.split()
@@ -1465,6 +1477,12 @@ class CyclesScene_PT_simplify(CyclesButtonsPanel, Panel):
         col.prop(rd, "simplify_subdivision_render", text="Subdivision")
         col.prop(rd, "simplify_child_particles_render", text="Child Particles")
 
+        col = layout.column()
+        col.prop(cscene, "use_camera_cull")
+        subsub = col.column()
+        subsub.active = cscene.use_camera_cull
+        subsub.prop(cscene, "camera_cull_margin")
+
 
 def draw_device(self, context):
     scene = context.scene
index 9fa1b35d2ad698c464ac3085dba8ec1f0fc230da..35ba450be41b8475cc33448e1d443277e8dca22f 100644 (file)
@@ -235,8 +235,56 @@ void BlenderSync::sync_background_light(bool use_portal)
 
 /* Object */
 
-Object *BlenderSync::sync_object(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::DupliObject b_dupli_ob,
-                                 Transform& tfm, uint layer_flag, float motion_time, bool hide_tris, bool *use_portal)
+/* TODO(sergey): Not really optimal, consider approaches based on k-DOP in order
+ * to reduce number of objects which are wrongly considered visible.
+ */
+static bool object_boundbox_clip(Scene *scene,
+                                 BL::Object b_ob,
+                                 Transform& tfm,
+                                 float margin)
+{
+       Camera *cam = scene->camera;
+       Transform& worldtondc = cam->worldtondc;
+       BL::Array<float, 24> boundbox = b_ob.bound_box();
+       float3 bb_min = make_float3(FLT_MAX, FLT_MAX, FLT_MAX),
+              bb_max = make_float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+       bool all_behind = true;
+       for(int i = 0; i < 8; ++i) {
+               float3 p = make_float3(boundbox[3 * i + 0],
+                                      boundbox[3 * i + 1],
+                                      boundbox[3 * i + 2]);
+               p = transform_point(&tfm, p);
+               p = transform_point(&worldtondc, p);
+               if(p.z >= -margin) {
+                       all_behind = false;
+               }
+               p /= p.z;
+               bb_min = min(bb_min, p);
+               bb_max = max(bb_max, p);
+       }
+       if(!all_behind) {
+               if(bb_min.x >= 1.0f + margin ||
+                  bb_min.y >= 1.0f + margin ||
+                  bb_max.x <= -margin ||
+                  bb_max.y <= -margin)
+               {
+                       return true;
+               }
+               return false;
+       }
+       return true;
+}
+
+Object *BlenderSync::sync_object(BL::Object b_parent,
+                                 int persistent_id[OBJECT_PERSISTENT_ID_SIZE],
+                                 BL::DupliObject b_dupli_ob,
+                                 Transform& tfm,
+                                 uint layer_flag,
+                                 float motion_time,
+                                 bool hide_tris,
+                                 bool use_camera_cull,
+                                 float camera_cull_margin,
+                                 bool *use_portal)
 {
        BL::Object b_ob = (b_dupli_ob ? b_dupli_ob.object() : b_parent);
        bool motion = motion_time != 0.0f;
@@ -254,6 +302,11 @@ Object *BlenderSync::sync_object(BL::Object b_parent, int persistent_id[OBJECT_P
        if(!object_is_mesh(b_ob))
                return NULL;
 
+       /* Perform camera space culling. */
+       if(use_camera_cull && object_boundbox_clip(scene, b_ob, tfm, camera_cull_margin)) {
+               return NULL;
+       }
+
        /* key to lookup object */
        ObjectKey key(b_parent, persistent_id, b_ob);
        Object *object;
@@ -481,6 +534,18 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
                mesh_motion_synced.clear();
        }
 
+       bool allow_camera_cull = false;
+       float camera_cull_margin = 0.0f;
+       if(b_scene.render().use_simplify()) {
+               PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
+               allow_camera_cull = scene->camera->type != CAMERA_PANORAMA &&
+                                   !b_scene.render().use_multiview() &&
+                                   get_boolean(cscene, "use_camera_cull");
+               if(allow_camera_cull) {
+                       camera_cull_margin = get_float(cscene, "camera_cull_margin");
+               }
+       }
+
        /* object loop */
        BL::Scene::object_bases_iterator b_base;
        BL::Scene b_sce = b_scene;
@@ -503,6 +568,12 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
                        if(!hide) {
                                progress.set_sync_status("Synchronizing object", b_ob.name());
 
+                               PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
+                               bool use_camera_cull = allow_camera_cull && get_boolean(cobject, "use_camera_cull");
+                               if(use_camera_cull) {
+                                       /* Need to have proper projection matrix. */
+                                       scene->camera->update();
+                               }
                                if(b_ob.is_duplicator() && !object_render_hide_duplis(b_ob)) {
                                        /* dupli objects */
                                        b_ob.dupli_list_create(b_scene, dupli_settings);
@@ -522,7 +593,16 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
                                                        BL::Array<int, OBJECT_PERSISTENT_ID_SIZE> persistent_id = b_dup->persistent_id();
 
                                                        /* sync object and mesh or light data */
-                                                       Object *object = sync_object(b_ob, persistent_id.data, *b_dup, tfm, ob_layer, motion_time, hide_tris, &use_portal);
+                                                       Object *object = sync_object(b_ob,
+                                                                                    persistent_id.data,
+                                                                                    *b_dup,
+                                                                                    tfm,
+                                                                                    ob_layer,
+                                                                                    motion_time,
+                                                                                    hide_tris,
+                                                                                    use_camera_cull,
+                                                                                    camera_cull_margin,
+                                                                                    &use_portal);
 
                                                        /* sync possible particle data, note particle_id
                                                         * starts counting at 1, first is dummy particle */
@@ -542,7 +622,16 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
                                if(!object_render_hide(b_ob, true, true, hide_tris)) {
                                        /* object itself */
                                        Transform tfm = get_transform(b_ob.matrix_world());
-                                       sync_object(b_ob, NULL, PointerRNA_NULL, tfm, ob_layer, motion_time, hide_tris, &use_portal);
+                                       sync_object(b_ob,
+                                                   NULL,
+                                                   PointerRNA_NULL,
+                                                   tfm,
+                                                   ob_layer,
+                                                   motion_time,
+                                                   hide_tris,
+                                                   use_camera_cull,
+                                                   camera_cull_margin,
+                                                   &use_portal);
                                }
                        }
 
index 89d93e19e9f08ae98dbe0bc8978dd722b2f5c6e8..b8bd14c0f710eead4142d487818e5183a9817601 100644 (file)
@@ -85,8 +85,16 @@ private:
        void sync_nodes(Shader *shader, BL::ShaderNodeTree b_ntree);
        Mesh *sync_mesh(BL::Object b_ob, bool object_updated, bool hide_tris);
        void sync_curves(Mesh *mesh, BL::Mesh b_mesh, BL::Object b_ob, bool motion, int time_index = 0);
-       Object *sync_object(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::DupliObject b_dupli_ob,
-                                        Transform& tfm, uint layer_flag, float motion_time, bool hide_tris, bool *use_portal);
+       Object *sync_object(BL::Object b_parent,
+                           int persistent_id[OBJECT_PERSISTENT_ID_SIZE],
+                           BL::DupliObject b_dupli_ob,
+                           Transform& tfm,
+                           uint layer_flag,
+                           float motion_time,
+                           bool hide_tris,
+                           bool use_camera_cull,
+                           float camera_cull_margin,
+                           bool *use_portal);
        void sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::Object b_ob, Transform& tfm, bool *use_portal);
        void sync_background_light(bool use_portal);
        void sync_mesh_motion(BL::Object b_ob, Object *object, float motion_time);