Cycles: equi-angular sampling for homogeneous volumes
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 5 Feb 2014 15:33:51 +0000 (16:33 +0100)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Fri, 14 Feb 2014 16:37:34 +0000 (17:37 +0100)
This adds an option in the Volume Sampling panel, which helps rendering lamps
inside or near volumes with less noise. It can also increase noise though and
needs improvements to support MIS and heterogeneous volumes, but since it's
useful in some cases already (especially world volumes) it's there now.

Based on the code in the old branch by Stuart, with modifications by Thomas
and Brecht.

Differential Revision: https://developer.blender.org/D291

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

index c80e8a3250ca9a4365504130666f7b9f7bf6806b..3920510e38fcb97ef3d4b9c38353c7ac757cd55f 100644 (file)
@@ -107,6 +107,11 @@ enum_integrator = (
     ('BRANCHED_PATH', "Branched Path Tracing", "Path tracing integrator that branches on the first bounce, giving more control over the number of light and material samples"),
     ('PATH', "Path Tracing", "Pure path tracing integrator"),
     )
+    
+enum_volume_homogeneous_sampling = (
+    ('DISTANCE', "Distance", "Use Distance Sampling"),
+    ('EQUI_ANGULAR', "Equi-angular", "Use Equi-angular Sampling"),
+    )
 
 
 class CyclesRenderSettings(bpy.types.PropertyGroup):
@@ -140,6 +145,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
                 items=enum_integrator,
                 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",
index c0ce80426c0a099317795cf91436e2eaedf42724..cb50db23767eaf4a9a6021a781aff8bcecbe71c9 100644 (file)
@@ -167,6 +167,10 @@ class CyclesRender_PT_volume_sampling(CyclesButtonsPanel, Panel):
         scene = context.scene
         cscene = scene.cycles
 
+        layout.prop(cscene, "volume_homogeneous_sampling", text="Homogeneous")
+
+        layout.label("Heterogeneous:")
+
         split = layout.split()
         split.prop(cscene, "volume_step_size")
         split.prop(cscene, "volume_max_steps")
index 8e2197a2aa6a04aadaa8e9569b998e6bd842aa3e..1d507ed9b1dbbeff5d68a80ebe2ddbce6534f5b0 100644 (file)
@@ -172,6 +172,7 @@ 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 5ee25a6cb9835b818e057df24ed56af9792d4954..8d7adb2b6d7d5cbeda9aeda4f38a0938291291f6 100644 (file)
@@ -824,7 +824,6 @@ typedef struct KernelIntegrator {
        /* clamp */
        float sample_clamp_direct;
        float sample_clamp_indirect;
-       float pad1, pad2, pad3;
 
        /* branched path */
        int branched;
@@ -843,10 +842,12 @@ typedef struct KernelIntegrator {
        int sampling_pattern;
 
        /* volume render */
+       int volume_homogeneous_sampling;
        int use_volumes;
        int volume_max_steps;
        float volume_step_size;
        int volume_samples;
+       int pad1, pad2;
 } KernelIntegrator;
 
 typedef struct KernelBVH {
index dc2ddf1098e6141e0d1837ef0aca3dcd040419e7..4d058763f227f918c0041067f5ccd78462b25266 100644 (file)
@@ -228,21 +228,72 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(KernelGloba
                else
                        sample_sigma_t = sigma_t.z;
 
-               /* xi is [0, 1[ so log(0) should never happen, division by zero is
-                * avoided because sample_sigma_t > 0 when SD_SCATTER is set */
-               float xi = path_state_rng_1D(kg, rng, state, PRNG_SCATTER_DISTANCE);
-               float sample_t = min(t, -logf(1.0f - xi)/sample_sigma_t);
-
-               transmittance = volume_color_attenuation(sigma_t, sample_t);
-
-               if(sample_t < t) {
-                       float pdf = dot(sigma_t, transmittance);
-                       new_tp = *throughput * coeff.sigma_s * transmittance * (3.0f / pdf);
-                       t = sample_t;
+               /* distance sampling */
+               if(kernel_data.integrator.volume_homogeneous_sampling == 0 || !kernel_data.integrator.num_all_lights) { 
+                       /* xi is [0, 1[ so log(0) should never happen, division by zero is
+                        * avoided because sample_sigma_t > 0 when SD_SCATTER is set */
+                       float xi = path_state_rng_1D(kg, rng, state, PRNG_SCATTER_DISTANCE);
+                       float sample_t = min(t, -logf(1.0f - xi)/sample_sigma_t);
+
+                       transmittance = volume_color_attenuation(sigma_t, sample_t);
+
+                       if(sample_t < t) {
+                               float pdf = dot(sigma_t, transmittance);
+                               new_tp = *throughput * coeff.sigma_s * transmittance * (3.0f / pdf);
+                               t = sample_t;
+                       }
+                       else {
+                               float pdf = (transmittance.x + transmittance.y + transmittance.z);
+                               new_tp = *throughput * transmittance * (3.0f / pdf);
+                       }
                }
+               /* equi-angular sampling */
                else {
-                       float pdf = (transmittance.x + transmittance.y + transmittance.z);
-                       new_tp = *throughput * transmittance * (3.0f / pdf);
+                       /* decide if we are going to scatter or not, based on sigma_t. this
+                        * is not ideal, instead we should perhaps split the path here and
+                        * do both, and at least add multiple importance sampling */
+                       float xi = path_state_rng_1D(kg, rng, state, PRNG_SCATTER_DISTANCE);
+                       float sample_transmittance = expf(-sample_sigma_t * t);
+
+                       if(xi < sample_transmittance) {
+                               /* no scattering */
+                               float3 transmittance = volume_color_attenuation(sigma_t, t);
+                               float pdf = (transmittance.x + transmittance.y + transmittance.z);
+                               new_tp = *throughput * transmittance * (3.0f / pdf);
+                       }
+                       else {
+                               /* rescale random number so we can reuse it */
+                               xi = (xi - sample_transmittance)/(1.0f - sample_transmittance);
+
+                               /* equi-angular scattering somewhere on segment 0..t */
+                               /* see "Importance Sampling Techniques for Path Tracing in Participating Media" */
+
+                               /* light RNGs */
+                               float light_t = path_state_rng_1D(kg, rng, state, PRNG_LIGHT);
+                               float light_u, light_v;
+                               path_state_rng_2D(kg, rng, state, PRNG_LIGHT_U, &light_u, &light_v);
+
+                               /* light sample */
+                               LightSample ls;
+                               light_sample(kg, light_t, light_u, light_v, ray->time, ray->P, &ls);
+                               if(ls.pdf == 0.0f)
+                                       return VOLUME_PATH_MISSED;
+
+                               /* sampling */
+                               float delta = dot((ls.P - ray->P) , ray->D);
+                               float D = sqrtf(len_squared(ls.P - ray->P) - delta * delta);
+                               float theta_a = -atan2f(delta, D);
+                               float theta_b = atan2f(t - delta, D);
+                               float t_ = D * tan((xi * theta_b) + (1 - xi) * theta_a);
+
+                               float pdf = D / ((theta_b - theta_a) * (D * D + t_ * t_));
+                               float sample_t = min(t, delta + t_);
+
+                               transmittance = volume_color_attenuation(sigma_t, sample_t);
+
+                               new_tp = *throughput * coeff.sigma_s * transmittance / ((1.0f - sample_transmittance) * pdf);
+                               t = sample_t;
+                       }
                }
        }
        else if(closure_flag & SD_ABSORPTION) {
index f48e04f31e1fffb273b0804d687d26cb915f4aeb..5b26440594a6120e8521ac436c9bb28249df4ba5 100644 (file)
@@ -41,6 +41,7 @@ Integrator::Integrator()
        transparent_probalistic = true;
        transparent_shadows = false;
 
+       volume_homogeneous_sampling = 0;
        volume_max_steps = 1024;
        volume_step_size = 0.1;
 
@@ -104,6 +105,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
 
        kintegrator->transparent_shadows = transparent_shadows;
 
+       kintegrator->volume_homogeneous_sampling = volume_homogeneous_sampling;
        kintegrator->volume_max_steps = volume_max_steps;
        kintegrator->volume_step_size = volume_step_size;
 
@@ -176,6 +178,7 @@ bool Integrator::modified(const Integrator& integrator)
                transparent_max_bounce == integrator.transparent_max_bounce &&
                transparent_probalistic == integrator.transparent_probalistic &&
                transparent_shadows == integrator.transparent_shadows &&
+               volume_homogeneous_sampling == integrator.volume_homogeneous_sampling &&
                volume_max_steps == integrator.volume_max_steps &&
                volume_step_size == integrator.volume_step_size &&
                no_caustics == integrator.no_caustics &&
index 573b258af60a1ba11de67e9c651fbc2487e55e5e..4a8240c3941014ad5c9ec9b9d281e60f893fb2b7 100644 (file)
@@ -41,6 +41,7 @@ public:
        bool transparent_probalistic;
        bool transparent_shadows;
 
+       int volume_homogeneous_sampling;
        int volume_max_steps;
        float volume_step_size;