Cycles: volume sampling method can now be set per material/world.
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Sat, 7 Jun 2014 16:47:14 +0000 (18:47 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Sat, 14 Jun 2014 11:49:56 +0000 (13:49 +0200)
This gives you "Multiple Importance", "Distance" and "Equiangular" choices.

What multiple importance sampling does is make things more robust to certain
types of noise at the cost of a bit more noise in cases where the individual
strategies are always better.

So if you've got a pretty dense volume that's lit from far away then distance
sampling is usually more efficient. If you've got a light inside or near the
volume then equiangular sampling is better. If you have a combination of both,
then the multiple importance sampling will be better.

intern/cycles/blender/addon/properties.py
intern/cycles/blender/addon/ui.py
intern/cycles/blender/blender_shader.cpp
intern/cycles/blender/blender_sync.cpp
intern/cycles/kernel/kernel_path.h
intern/cycles/kernel/kernel_types.h
intern/cycles/kernel/kernel_volume.h
intern/cycles/render/integrator.cpp
intern/cycles/render/shader.cpp
intern/cycles/render/shader.h

index 12babd95ed847c96b28971c07df214be1bbbdcc8..1a2d264e12dc04aee365fda3497b4db382a0a7f6 100644 (file)
@@ -108,10 +108,10 @@ enum_integrator = (
     ('PATH', "Path Tracing", "Pure path tracing integrator"),
     )
 
-enum_volume_homogeneous_sampling = (
-    ('DISTANCE', "Distance", "Use Distance Sampling"),
-    ('EQUI_ANGULAR', "Equi-angular", "Use Equi-angular Sampling"),
-    ('MULTIPLE_IMPORTANCE', "Multiple Importance", "Combine distance and equi-angular sampling"),
+enum_volume_sampling = (
+    ('DISTANCE', "Distance", "Use distance sampling, best for dense volumes with lights far away"),
+    ('EQUIANGULAR', "Equiangular", "Use equiangular sampling, best for volumes with low density with light inside or near the volume"),
+    ('MULTIPLE_IMPORTANCE', "Multiple Importance", "Combine distance and equi-angular sampling for volumes where neither method is ideal"),
     )
 
 
@@ -147,13 +147,6 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
                 default='PATH',
                 )
 
-        cls.volume_homogeneous_sampling = EnumProperty(
-                name="Homogeneous Sampling",
-                description="Sampling method to use for homogeneous volumes",
-                items=enum_volume_homogeneous_sampling,
-                default='DISTANCE',
-                )
-
         cls.use_square_samples = BoolProperty(
                 name="Square Samples",
                 description="Square sampling values for easier artist control",
@@ -237,7 +230,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
                 name="Volume Samples",
                 description="Number of volume scattering samples to render for each AA sample",
                 min=1, max=10000,
-                default=1,
+                default=0,
                 )
 
         cls.sampling_pattern = EnumProperty(
@@ -603,6 +596,12 @@ class CyclesMaterialSettings(bpy.types.PropertyGroup):
                             "(not using any textures), for faster rendering",
                 default=False,
                 )
+        cls.volume_sampling = EnumProperty(
+                name="Volume Sampling",
+                description="Sampling method to use for volumes",
+                items=enum_volume_sampling,
+                default='DISTANCE',
+                )
 
     @classmethod
     def unregister(cls):
@@ -673,6 +672,12 @@ class CyclesWorldSettings(bpy.types.PropertyGroup):
                             "(not using any textures), for faster rendering",
                 default=False,
                 )
+        cls.volume_sampling = EnumProperty(
+                name="Volume Sampling",
+                description="Sampling method to use for volumes",
+                items=enum_volume_sampling,
+                default='EQUIANGULAR',
+                )
 
     @classmethod
     def unregister(cls):
index a6a3501b912c36e13117b241897542e5961549f5..9dd5a4e81cec749ea646ce00227aa8a2092e8fd4 100644 (file)
@@ -176,16 +176,11 @@ class CyclesRender_PT_volume_sampling(CyclesButtonsPanel, Panel):
         scene = context.scene
         cscene = scene.cycles
 
-        split = layout.split(align=True)
-
-        sub = split.column(align=True)
-        sub.label("Heterogeneous:")
-        sub.prop(cscene, "volume_step_size")
-        sub.prop(cscene, "volume_max_steps")
-
-        sub = split.column(align=True)
-        sub.label("Homogeneous:")
-        sub.prop(cscene, "volume_homogeneous_sampling", text="")
+        row = layout.row()
+        row.label("Heterogeneous:")
+        row = layout.row()
+        row.prop(cscene, "volume_step_size")
+        row.prop(cscene, "volume_max_steps")
 
 
 class CyclesRender_PT_light_paths(CyclesButtonsPanel, Panel):
@@ -829,8 +824,6 @@ class CyclesWorld_PT_volume(CyclesButtonsPanel, Panel):
         world = context.world
         panel_node_draw(layout, world, 'OUTPUT_WORLD', 'Volume')
 
-        layout.prop(world.cycles, "homogeneous_volume")
-
 
 class CyclesWorld_PT_ambient_occlusion(CyclesButtonsPanel, Panel):
     bl_label = "Ambient Occlusion"
@@ -922,15 +915,24 @@ class CyclesWorld_PT_settings(CyclesButtonsPanel, Panel):
         cworld = world.cycles
         cscene = context.scene.cycles
 
-        col = layout.column()
+        split = layout.split()
+
+        col = split.column()
+
+        col.label(text="Surface:")
+        col.prop(cworld, "sample_as_light", text="Multiple Importance")
 
-        col.prop(cworld, "sample_as_light")
-        sub = col.row(align=True)
+        sub = col.column(align=True)
         sub.active = cworld.sample_as_light
         sub.prop(cworld, "sample_map_resolution")
         if cscene.progressive == 'BRANCHED_PATH':
             sub.prop(cworld, "samples")
 
+        col = split.column()
+        col.label(text="Volume:")
+        col.prop(cworld, "volume_sampling", text="")
+        col.prop(cworld, "homogeneous_volume", text="Homogeneous")
+
 
 class CyclesMaterial_PT_preview(CyclesButtonsPanel, Panel):
     bl_label = "Preview"
@@ -979,8 +981,6 @@ class CyclesMaterial_PT_volume(CyclesButtonsPanel, Panel):
 
         panel_node_draw(layout, mat, 'OUTPUT_MATERIAL', 'Volume')
 
-        layout.prop(cmat, "homogeneous_volume")
-
 
 class CyclesMaterial_PT_displacement(CyclesButtonsPanel, Panel):
     bl_label = "Displacement"
@@ -1023,10 +1023,18 @@ class CyclesMaterial_PT_settings(CyclesButtonsPanel, Panel):
         col.label()
         col.prop(mat, "pass_index")
 
-        col = layout.column()
-        col.prop(cmat, "sample_as_light")
+        split = layout.split()
+
+        col = split.column()
+        col.label(text="Surface:")
+        col.prop(cmat, "sample_as_light", text="Multiple Importance")
         col.prop(cmat, "use_transparent_shadow")
 
+        col = split.column()
+        col.label(text="Volume:")
+        col.prop(cmat, "volume_sampling", text="")
+        col.prop(cmat, "homogeneous_volume", text="Homogeneous")
+
 
 class CyclesTexture_PT_context(CyclesButtonsPanel, Panel):
     bl_label = ""
index bce236530b93dcc21c2bea6f690bda9f28e8ac6e..3b783d36b1eb7d1874ba4fbf5cc74f639daa5d55 100644 (file)
@@ -977,6 +977,7 @@ void BlenderSync::sync_materials(bool update_all)
                        shader->use_mis = get_boolean(cmat, "sample_as_light");
                        shader->use_transparent_shadow = get_boolean(cmat, "use_transparent_shadow");
                        shader->heterogeneous_volume = !get_boolean(cmat, "homogeneous_volume");
+                       shader->volume_sampling_method = RNA_enum_get(&cmat, "volume_sampling");
 
                        shader->set_graph(graph);
                        shader->tag_update(scene);
@@ -1006,6 +1007,7 @@ void BlenderSync::sync_world(bool update_all)
                        /* volume */
                        PointerRNA cworld = RNA_pointer_get(&b_world.ptr, "cycles");
                        shader->heterogeneous_volume = !get_boolean(cworld, "homogeneous_volume");
+                       shader->volume_sampling_method = RNA_enum_get(&cworld, "volume_sampling");
                }
                else if(b_world) {
                        ShaderNode *closure, *out;
index bdb3e20632f946796edc9494ec4403bc193cd8a5..042bbca94979b95e6dcd0fe7bd4b91c345dc6976 100644 (file)
@@ -177,7 +177,6 @@ void BlenderSync::sync_integrator()
        integrator->transparent_min_bounce = get_int(cscene, "transparent_min_bounces");
        integrator->transparent_shadows = get_boolean(cscene, "use_transparent_shadows");
 
-       integrator->volume_homogeneous_sampling = RNA_enum_get(&cscene, "volume_homogeneous_sampling");
        integrator->volume_max_steps = get_int(cscene, "volume_max_steps");
        integrator->volume_step_size = get_float(cscene, "volume_step_size");
 
index 22024483bfc8c1d57998f93987eb26ea78d30acf..d9ad348a9523d86f39b4e84df5c2be19858a06ee 100644 (file)
@@ -89,7 +89,8 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, Ray ray,
                        volume_ray.t = (hit)? isect.t: FLT_MAX;
 
                        bool heterogeneous = volume_stack_is_heterogeneous(kg, state.volume_stack);
-                       bool decoupled = kernel_volume_use_decoupled(kg, heterogeneous, false);
+                       int sampling_method = volume_stack_sampling_method(kg, state.volume_stack);
+                       bool decoupled = kernel_volume_use_decoupled(kg, heterogeneous, false, sampling_method);
 
                        if(decoupled) {
                                /* cache steps along volume for repeated sampling */
@@ -99,6 +100,8 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, Ray ray,
                                shader_setup_from_volume(kg, &volume_sd, &volume_ray, state.bounce, state.transparent_bounce);
                                kernel_volume_decoupled_record(kg, &state,
                                        &volume_ray, &volume_sd, &volume_segment, heterogeneous);
+                               
+                               volume_segment.sampling_method = sampling_method;
 
                                /* emission */
                                if(volume_segment.closure_flag & SD_EMISSION)
@@ -468,7 +471,8 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
                        volume_ray.t = (hit)? isect.t: FLT_MAX;
 
                        bool heterogeneous = volume_stack_is_heterogeneous(kg, state.volume_stack);
-                       bool decoupled = kernel_volume_use_decoupled(kg, heterogeneous, true);
+                       int sampling_method = volume_stack_sampling_method(kg, state.volume_stack);
+                       bool decoupled = kernel_volume_use_decoupled(kg, heterogeneous, true, sampling_method);
 
                        if(decoupled) {
                                /* cache steps along volume for repeated sampling */
@@ -479,6 +483,8 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
                                kernel_volume_decoupled_record(kg, &state,
                                        &volume_ray, &volume_sd, &volume_segment, heterogeneous);
 
+                               volume_segment.sampling_method = sampling_method;
+
                                /* emission */
                                if(volume_segment.closure_flag & SD_EMISSION)
                                        path_radiance_accum_emission(&L, throughput, volume_segment.accum_emission, state.bounce);
@@ -814,7 +820,10 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
 
                        /* direct light sampling */
                        if(volume_segment.closure_flag & SD_SCATTER) {
+                               volume_segment.sampling_method = volume_stack_sampling_method(kg, state.volume_stack);
+
                                bool all = kernel_data.integrator.sample_all_lights_direct;
+
                                kernel_branched_path_volume_connect_light(kg, rng, &volume_sd,
                                        throughput, &state, &L, 1.0f, all, &volume_ray, &volume_segment);
 
index f5c73b72c49dbdb99adc4439b26519ec3110faad..3ea3988254088599c64baba90c3b22ab6a921f37 100644 (file)
@@ -577,7 +577,8 @@ enum ShaderDataFlag {
        SD_AO = 512,                    /* have ao closure? */
        SD_TRANSPARENT = 1024,  /* have transparent closure? */
 
-       SD_CLOSURE_FLAGS = (SD_EMISSION|SD_BSDF|SD_BSDF_HAS_EVAL|SD_BSDF_GLOSSY|SD_BSSRDF|SD_HOLDOUT|SD_ABSORPTION|SD_SCATTER|SD_AO),
+       SD_CLOSURE_FLAGS = (SD_EMISSION|SD_BSDF|SD_BSDF_HAS_EVAL|SD_BSDF_GLOSSY|
+                           SD_BSSRDF|SD_HOLDOUT|SD_ABSORPTION|SD_SCATTER|SD_AO),
 
        /* shader flags */
        SD_USE_MIS = 2048,                                      /* direct light sample */
@@ -586,13 +587,17 @@ enum ShaderDataFlag {
        SD_HAS_ONLY_VOLUME = 16384,                     /* has only volume shader, no surface */
        SD_HETEROGENEOUS_VOLUME = 32768,        /* has heterogeneous volume */
        SD_HAS_BSSRDF_BUMP = 65536,                     /* bssrdf normal uses bump */
+       SD_VOLUME_EQUIANGULAR = 131072,         /* use equiangular sampling */
+       SD_VOLUME_MIS = 262144,                         /* use multiple importance sampling */
 
-       SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME|SD_HAS_BSSRDF_BUMP),
+       SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|
+                          SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME|
+                                          SD_HAS_BSSRDF_BUMP|SD_VOLUME_EQUIANGULAR|SD_VOLUME_MIS),
 
        /* object flags */
-       SD_HOLDOUT_MASK = 131072,                       /* holdout for camera rays */
-       SD_OBJECT_MOTION = 262144,                      /* has object motion blur */
-       SD_TRANSFORM_APPLIED = 524288,          /* vertices have transform applied */
+       SD_HOLDOUT_MASK = 524288,                       /* holdout for camera rays */
+       SD_OBJECT_MOTION = 1048576,                     /* has object motion blur */
+       SD_TRANSFORM_APPLIED = 2097152,         /* vertices have transform applied */
 
        SD_OBJECT_FLAGS = (SD_HOLDOUT_MASK|SD_OBJECT_MOTION|SD_TRANSFORM_APPLIED)
 };
@@ -894,7 +899,6 @@ typedef struct KernelIntegrator {
        int aa_samples;
 
        /* volume render */
-       int volume_homogeneous_sampling;
        int use_volumes;
        int volume_max_steps;
        float volume_step_size;
index 61e4989147b7836240adb5dee5adf1ff5aa90b8d..e3de0ff90c88900eef1a6b79b5625e52f0c54309 100644 (file)
@@ -116,6 +116,36 @@ ccl_device bool volume_stack_is_heterogeneous(KernelGlobals *kg, VolumeStack *st
        return false;
 }
 
+ccl_device int volume_stack_sampling_method(KernelGlobals *kg, VolumeStack *stack)
+{
+       if(kernel_data.integrator.num_all_lights == 0)
+               return 0;
+
+       int method = -1;
+
+       for(int i = 0; stack[i].shader != SHADER_NONE; i++) {
+               int shader_flag = kernel_tex_fetch(__shader_flag, (stack[i].shader & SHADER_MASK)*2);
+
+               if(shader_flag & SD_VOLUME_MIS) {
+                       return SD_VOLUME_MIS;
+               }
+               else if(shader_flag & SD_VOLUME_EQUIANGULAR) {
+                       if(method == 0)
+                               return SD_VOLUME_MIS;
+
+                       method = SD_VOLUME_EQUIANGULAR;
+               }
+               else {
+                       if(method == SD_VOLUME_EQUIANGULAR)
+                               return SD_VOLUME_MIS;
+
+                       method = 0;
+               }
+       }
+
+       return method;
+}
+
 /* Volume Shadows
  *
  * These functions are used to attenuate shadow rays to lights. Both absorption
@@ -562,6 +592,8 @@ typedef struct VolumeSegment {
 
        float3 accum_emission;          /* accumulated emission at end of segment */
        float3 accum_transmittance;     /* accumulated transmittance at end of segment */
+
+       int sampling_method;            /* volume sampling method */
 } VolumeSegment;
 
 /* record volume steps to the end of the volume.
@@ -709,6 +741,8 @@ ccl_device VolumeIntegrateResult kernel_volume_decoupled_scatter(
 {
        int closure_flag = segment->closure_flag;
 
+       /* XXX add probalistic scattering! */
+
        if(!(closure_flag & SD_SCATTER))
                return VOLUME_PATH_MISSED;
 
@@ -737,8 +771,8 @@ ccl_device VolumeIntegrateResult kernel_volume_decoupled_scatter(
        bool distance_sample = true;
        bool use_mis = false;
 
-       if(kernel_data.integrator.volume_homogeneous_sampling && light_P) {
-               if(kernel_data.integrator.volume_homogeneous_sampling == 2) {
+       if(segment->sampling_method && light_P) {
+               if(segment->sampling_method == SD_VOLUME_MIS) {
                        /* multiple importance sample: randomly pick between
                         * equiangular and distance sampling strategy */
                        if(xi < 0.5f) {
@@ -872,7 +906,7 @@ ccl_device VolumeIntegrateResult kernel_volume_decoupled_scatter(
 }
 
 /* decide if we need to use decoupled or not */
-ccl_device bool kernel_volume_use_decoupled(KernelGlobals *kg, bool heterogeneous, bool direct)
+ccl_device bool kernel_volume_use_decoupled(KernelGlobals *kg, bool heterogeneous, bool direct, int sampling_method)
 {
        /* decoupled ray marching for heterogenous volumes not supported on the GPU,
         * which also means equiangular and multiple importance sampling is not
@@ -882,9 +916,8 @@ ccl_device bool kernel_volume_use_decoupled(KernelGlobals *kg, bool heterogeneou
                return false;
 #endif
 
-       /* equiangular sampling only implemented for decoupled */
-       bool equiangular = kernel_data.integrator.volume_homogeneous_sampling != 0;
-       if(equiangular)
+       /* equiangular and multiple importance sampling only implemented for decoupled */
+       if(sampling_method != 0)
                return true;
 
        /* for all light sampling use decoupled, reusing shader evaluations is
index ee3419b055c39a7cf46d79940b71bad07997de1a..4a8b490b1ad90d3f9e9245c689268b922106743c 100644 (file)
@@ -101,11 +101,6 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
        if(!transparent_shadows)
                kintegrator->transparent_shadows = false;
 
-       if(kintegrator->num_all_lights > 0)
-               kintegrator->volume_homogeneous_sampling = volume_homogeneous_sampling;
-       else
-               kintegrator->volume_homogeneous_sampling = 0;
-
        kintegrator->volume_max_steps = volume_max_steps;
        kintegrator->volume_step_size = volume_step_size;
 
index 0f9a5a5d39d571b5ffd3c38abbc7e51826cc8b1c..662caed72f142abd599ad5f8e5e7f3205804e508 100644 (file)
@@ -44,6 +44,7 @@ Shader::Shader()
        use_mis = true;
        use_transparent_shadow = true;
        heterogeneous_volume = true;
+       volume_sampling_method = 0;
 
        has_surface = false;
        has_surface_transparent = false;
@@ -257,6 +258,10 @@ void ShaderManager::device_update_common(Device *device, DeviceScene *dscene, Sc
                        flag |= SD_HAS_BSSRDF_BUMP;
                if(shader->has_converter_blackbody)
                        has_converter_blackbody = true;
+               if(shader->volume_sampling_method == 1)
+                       flag |= SD_VOLUME_EQUIANGULAR;
+               if(shader->volume_sampling_method == 2)
+                       flag |= SD_VOLUME_MIS;
 
                /* regular shader */
                shader_flag[i++] = flag;
index dc18b385cc345363cc64d6206afc4d461c531fc0..84be4b469d85f24b066198110331074d7f8a30b8 100644 (file)
@@ -68,6 +68,7 @@ public:
        bool use_mis;
        bool use_transparent_shadow;
        bool heterogeneous_volume;
+       int volume_sampling_method;
 
        /* synchronization */
        bool need_update;