Cycles: Added support for light portals
authorLukas Stockner <lukas.stockner@freenet.de>
Mon, 27 Apr 2015 19:51:55 +0000 (00:51 +0500)
committerSergey Sharybin <sergey.vfx@gmail.com>
Mon, 27 Apr 2015 20:30:16 +0000 (01:30 +0500)
This patch adds support for light portals: objects that help sampling the
environment light, therefore improving convergence. Using them tor other
lights in a unidirectional pathtracer is virtually useless.

The sampling is done with the area-preserving code already used for area lamps.
MIS is used both for combination of different portals and for combining portal-
and envmap-sampling.

The direction of portals is considered, they aren't used if the sampling point
is behind them.

Reviewers: sergey, dingto, #cycles

Reviewed By: dingto, #cycles

Subscribers: Lapineige, nutel, jtheninja, dsisco11, januz, vitorbalbio, candreacchio, TARDISMaker, lichtwerk, ace_dragon, marcog, mib2berlin, Tunge, lopataasdf, lordodin, sergey, dingto

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

intern/cycles/blender/addon/properties.py
intern/cycles/blender/addon/ui.py
intern/cycles/blender/blender_object.cpp
intern/cycles/blender/blender_sync.h
intern/cycles/kernel/kernel_emission.h
intern/cycles/kernel/kernel_light.h
intern/cycles/kernel/kernel_types.h
intern/cycles/render/light.cpp
intern/cycles/render/light.h

index 875b5d384e3d8c567eb0e5064f5d0fe4327cb574..538876a71e2d450657e9d665a3b5ae71c1365ee4 100644 (file)
@@ -694,6 +694,12 @@ class CyclesLampSettings(bpy.types.PropertyGroup):
                             "reduces noise for area lamps and sharp glossy materials",
                 default=False,
                 )
+        cls.is_portal = BoolProperty(
+                name="Is Portal",
+                description="Use this area lamp to guide sampling of the background, "
+                            "note that this will make the lamp invisible",
+                default=False,
+                )
 
     @classmethod
     def unregister(cls):
index 74de31850ee16facdca19e1a31414cb5eaa3d658..cdacad752218487eed966847357de80e7585809f 100644 (file)
@@ -743,7 +743,10 @@ class CyclesLamp_PT_preview(CyclesButtonsPanel, Panel):
 
     @classmethod
     def poll(cls, context):
-        return context.lamp and CyclesButtonsPanel.poll(context)
+        return context.lamp and \
+               not (context.lamp.type == 'AREA' and
+                    context.lamp.cycles.is_portal) \
+               and CyclesButtonsPanel.poll(context)
 
     def draw(self, context):
         self.layout.template_preview(context.lamp)
@@ -781,13 +784,21 @@ class CyclesLamp_PT_lamp(CyclesButtonsPanel, Panel):
                 sub.prop(lamp, "size", text="Size X")
                 sub.prop(lamp, "size_y", text="Size Y")
 
-        if cscene.progressive == 'BRANCHED_PATH':
-            col.prop(clamp, "samples")
-        col.prop(clamp, "max_bounces")
+        if not (lamp.type == 'AREA' and clamp.is_portal):
+            sub = col.column(align=True)
+            if cscene.progressive == 'BRANCHED_PATH':
+                sub.prop(clamp, "samples")
+            sub.prop(clamp, "max_bounces")
 
         col = split.column()
-        col.prop(clamp, "cast_shadow")
-        col.prop(clamp, "use_multiple_importance_sampling", text="Multiple Importance")
+
+        sub = col.column(align=True)
+        sub.active = not (lamp.type == 'AREA' and clamp.is_portal)
+        sub.prop(clamp, "cast_shadow")
+        sub.prop(clamp, "use_multiple_importance_sampling", text="Multiple Importance")
+
+        if lamp.type == 'AREA':
+            col.prop(clamp, "is_portal", text="Portal")
 
         if lamp.type == 'HEMI':
             layout.label(text="Not supported, interpreted as sun lamp")
@@ -799,7 +810,9 @@ class CyclesLamp_PT_nodes(CyclesButtonsPanel, Panel):
 
     @classmethod
     def poll(cls, context):
-        return context.lamp and CyclesButtonsPanel.poll(context)
+        return context.lamp and not (context.lamp.type == 'AREA' and
+                                     context.lamp.cycles.is_portal) and \
+               CyclesButtonsPanel.poll(context)
 
     def draw(self, context):
         layout = self.layout
index 7364a8b170a65c77b461f878d10506d207543f8f..bcc366732df85a683fff786fd92d6cccba125bff 100644 (file)
@@ -90,14 +90,17 @@ static uint object_ray_visibility(BL::Object b_ob)
 
 /* Light */
 
-void BlenderSync::sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::Object b_ob, Transform& tfm)
+void BlenderSync::sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::Object b_ob, Transform& tfm, bool *use_portal)
 {
        /* test if we need to sync */
        Light *light;
        ObjectKey key(b_parent, persistent_id, b_ob);
 
-       if(!light_map.sync(&light, b_ob, b_parent, key))
+       if(!light_map.sync(&light, b_ob, b_parent, key)) {
+               if(light->is_portal)
+                       *use_portal = true;
                return;
+       }
        
        BL::Lamp b_lamp(b_ob.data());
 
@@ -171,6 +174,14 @@ void BlenderSync::sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSI
 
        light->max_bounces = get_int(clamp, "max_bounces");
 
+       if(light->type == LIGHT_AREA)
+               light->is_portal = get_boolean(clamp, "is_portal");
+       else
+               light->is_portal = false;
+
+       if(light->is_portal)
+               *use_portal = true;
+
        /* visibility */
        uint visibility = object_ray_visibility(b_ob);
        light->use_diffuse = (visibility & PATH_RAY_DIFFUSE) != 0;
@@ -182,7 +193,7 @@ void BlenderSync::sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSI
        light->tag_update(scene);
 }
 
-void BlenderSync::sync_background_light()
+void BlenderSync::sync_background_light(bool use_portal)
 {
        BL::World b_world = b_scene.world();
 
@@ -191,19 +202,20 @@ void BlenderSync::sync_background_light()
                PointerRNA cworld = RNA_pointer_get(&b_world.ptr, "cycles");
                bool sample_as_light = get_boolean(cworld, "sample_as_light");
 
-               if(sample_as_light) {
+               if(sample_as_light || use_portal) {
                        /* test if we need to sync */
                        Light *light;
                        ObjectKey key(b_world, 0, b_world);
 
                        if(light_map.sync(&light, b_world, b_world, key) ||
-                          world_recalc ||
-                          b_world.ptr.data != world_map)
+                               world_recalc ||
+                               b_world.ptr.data != world_map)
                        {
                                light->type = LIGHT_BACKGROUND;
                                light->map_resolution  = get_int(cworld, "sample_map_resolution");
                                light->shader = scene->default_background;
-                               
+                               light->use_mis = sample_as_light;
+
                                int samples = get_int(cworld, "samples");
                                if(get_boolean(cscene, "use_square_samples"))
                                        light->samples = samples * samples;
@@ -223,7 +235,7 @@ void BlenderSync::sync_background_light()
 /* 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)
+                                 Transform& tfm, uint layer_flag, float motion_time, bool hide_tris, bool *use_portal)
 {
        BL::Object b_ob = (b_dupli_ob ? b_dupli_ob.object() : b_parent);
        bool motion = motion_time != 0.0f;
@@ -232,7 +244,7 @@ Object *BlenderSync::sync_object(BL::Object b_parent, int persistent_id[OBJECT_P
        if(object_is_light(b_ob)) {
                /* don't use lamps for excluded layers used as mask layer */
                if(!motion && !((layer_flag & render_layer.holdout_layer) && (layer_flag & render_layer.exclude_layer)))
-                       sync_light(b_parent, persistent_id, b_ob, tfm);
+                       sync_light(b_parent, persistent_id, b_ob, tfm, use_portal);
 
                return NULL;
        }
@@ -476,6 +488,7 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
        int dupli_settings = preview ? 1 : 2;
 
        bool cancel = false;
+       bool use_portal = false;
 
        for(; b_sce && !cancel; b_sce = b_sce.background_set()) {
                for(b_sce.object_bases.begin(b_base); b_base != b_sce.object_bases.end() && !cancel; ++b_base) {
@@ -506,7 +519,7 @@ 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);
+                                                       Object *object = sync_object(b_ob, persistent_id.data, *b_dup, tfm, ob_layer, motion_time, hide_tris, &use_portal);
 
                                                        /* sync possible particle data, note particle_id
                                                         * starts counting at 1, first is dummy particle */
@@ -526,7 +539,7 @@ 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);
+                                       sync_object(b_ob, NULL, PointerRNA_NULL, tfm, ob_layer, motion_time, hide_tris, &use_portal);
                                }
                        }
 
@@ -537,7 +550,7 @@ void BlenderSync::sync_objects(BL::SpaceView3D b_v3d, float motion_time)
        progress.set_sync_status("");
 
        if(!cancel && !motion) {
-               sync_background_light();
+               sync_background_light(use_portal);
 
                /* handle removed data and modified pointers */
                if(light_map.post_sync())
index 5fbf2c3011fb50f914065f8c2bba0c8832b797db..89d93e19e9f08ae98dbe0bc8978dd722b2f5c6e8 100644 (file)
@@ -86,9 +86,9 @@ private:
        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);
-       void sync_light(BL::Object b_parent, int persistent_id[OBJECT_PERSISTENT_ID_SIZE], BL::Object b_ob, Transform& tfm);
-       void sync_background_light();
+                                        Transform& tfm, uint layer_flag, float motion_time, bool hide_tris, 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);
        void sync_camera_motion(BL::Object b_ob, float motion_time);
 
index d3cbc5cff06e60665f405b64b2722385f02fa3ff..6c5a5fac8c547060f1b750a60e1d93df5415f20c 100644 (file)
@@ -254,7 +254,7 @@ ccl_device_noinline float3 indirect_background(KernelGlobals *kg, PathState *sta
        if(!(state->flag & PATH_RAY_MIS_SKIP) && res) {
                /* multiple importance sampling, get background light pdf for ray
                 * direction, and compute weight with respect to BSDF pdf */
-               float pdf = background_light_pdf(kg, ray->D);
+               float pdf = background_light_pdf(kg, ray->P, ray->D);
                float mis_weight = power_heuristic(state->ray_pdf, pdf);
 
                return L*mis_weight;
index 76fa754b5faa7b8777a8873b35466934dd43357c..d1b8db2c018f3f25122b946046c021d61579a6ac 100644 (file)
@@ -33,6 +33,98 @@ typedef struct LightSample {
        LightType type;         /* type of light */
 } LightSample;
 
+/* Area light sampling */
+
+/* Uses the following paper:
+ *
+ * Carlos Urena et al.
+ * An Area-Preserving Parametrization for Spherical Rectangles.
+ *
+ * https://www.solidangle.com/research/egsr2013_spherical_rectangle.pdf
+ *
+ * Note: light_p is modified when sample_coord is true.
+ */
+ccl_device float area_light_sample(float3 P,
+                                   float3 *light_p,
+                                   float3 axisu, float3 axisv,
+                                   float randu, float randv,
+                                   bool sample_coord)
+{
+       /* In our name system we're using P for the center,
+       * which is o in the paper.
+       */
+
+       float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f;
+       float axisu_len, axisv_len;
+       /* Compute local reference system R. */
+       float3 x = normalize_len(axisu, &axisu_len);
+       float3 y = normalize_len(axisv, &axisv_len);
+       float3 z = cross(x, y);
+       /* Compute rectangle coords in local reference system. */
+       float3 dir = corner - P;
+       float z0 = dot(dir, z);
+       /* Flip 'z' to make it point against Q. */
+       if(z0 > 0.0f) {
+               z *= -1.0f;
+               z0 *= -1.0f;
+       }
+       float x0 = dot(dir, x);
+       float y0 = dot(dir, y);
+       float x1 = x0 + axisu_len;
+       float y1 = y0 + axisv_len;
+       /* Create vectors to four vertices. */
+       float3 v00 = make_float3(x0, y0, z0);
+       float3 v01 = make_float3(x0, y1, z0);
+       float3 v10 = make_float3(x1, y0, z0);
+       float3 v11 = make_float3(x1, y1, z0);
+       /* Compute normals to edges. */
+       float3 n0 = normalize(cross(v00, v10));
+       float3 n1 = normalize(cross(v10, v11));
+       float3 n2 = normalize(cross(v11, v01));
+       float3 n3 = normalize(cross(v01, v00));
+       /* Compute internal angles (gamma_i). */
+       float g0 = safe_acosf(-dot(n0, n1));
+       float g1 = safe_acosf(-dot(n1, n2));
+       float g2 = safe_acosf(-dot(n2, n3));
+       float g3 = safe_acosf(-dot(n3, n0));
+       /* Compute predefined constants. */
+       float b0 = n0.z;
+       float b1 = n2.z;
+       float b0sq = b0 * b0;
+       float k = M_2PI_F - g2 - g3;
+       /* Compute solid angle from internal angles. */
+       float S = g0 + g1 - k;
+
+       if(sample_coord) {
+               /* Compute cu. */
+               float au = randu * S + k;
+               float fu = (cosf(au) * b0 - b1) / sinf(au);
+               float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
+               cu = clamp(cu, -1.0f, 1.0f);
+               /* Compute xu. */
+               float xu = -(cu * z0) / sqrtf(1.0f - cu * cu);
+               xu = clamp(xu, x0, x1);
+               /* Compute yv. */
+               float z0sq = z0 * z0;
+               float y0sq = y0 * y0;
+               float y1sq = y1 * y1;
+               float d = sqrtf(xu * xu + z0sq);
+               float h0 = y0 / sqrtf(d * d + y0sq);
+               float h1 = y1 / sqrtf(d * d + y1sq);
+               float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
+               float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
+
+               /* Transform (xu, yv, z0) to world coords. */
+               *light_p = P + xu * x + yv * y + z0 * z;
+       }
+
+       /* return pdf */
+       if(S != 0.0f)
+               return 1.0f / S;
+       else
+               return 0.0f;
+}
+
 /* Background Light */
 
 #ifdef __BACKGROUND_MIS__
@@ -46,7 +138,7 @@ ccl_device_noinline
 #else
 ccl_device
 #endif
-float3 background_light_sample(KernelGlobals *kg, float randu, float randv, float *pdf)
+float3 background_map_sample(KernelGlobals *kg, float randu, float randv, float *pdf)
 {
        /* for the following, the CDF values are actually a pair of floats, with the
         * function value as X and the actual CDF as Y.  The last entry's function
@@ -116,10 +208,8 @@ float3 background_light_sample(KernelGlobals *kg, float randu, float randv, floa
        else
                *pdf = (cdf_u.x * cdf_v.x)/(M_2PI_F * M_PI_F * sin_theta * denom);
 
-       *pdf *= kernel_data.integrator.pdf_lights;
-
        /* compute direction */
-       return -equirectangular_to_direction(u, v);
+       return equirectangular_to_direction(u, v);
 }
 
 /* TODO(sergey): Same as above, after the release we should consider using
@@ -130,7 +220,7 @@ ccl_device_noinline
 #else
 ccl_device
 #endif
-float background_light_pdf(KernelGlobals *kg, float3 direction)
+float background_map_pdf(KernelGlobals *kg, float3 direction)
 {
        float2 uv = direction_to_equirectangular(direction);
        int res = kernel_data.integrator.pdf_background_res;
@@ -156,9 +246,228 @@ float background_light_pdf(KernelGlobals *kg, float3 direction)
        float2 cdf_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * (res + 1) + index_u);
        float2 cdf_v = kernel_tex_fetch(__light_background_marginal_cdf, index_v);
 
-       float pdf = (cdf_u.x * cdf_v.x)/(M_2PI_F * M_PI_F * sin_theta * denom);
+       return (cdf_u.x * cdf_v.x)/(M_2PI_F * M_PI_F * sin_theta * denom);
+}
+
+ccl_device float background_portal_pdf(KernelGlobals *kg,
+                                       float3 P,
+                                       float3 direction,
+                                       int ignore_portal,
+                                       bool *is_possible)
+{
+       float portal_pdf = 0.0f;
+       if(is_possible)
+               *is_possible = false;
+
+       for(int p = 0; p < kernel_data.integrator.num_portals; p++) {
+               if(p == ignore_portal)
+                       continue;
+
+               /* TODO(sergey): Consider moving portal data fetch to a
+                * dedicated function.
+                */
+               float4 data0 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 0);
+               float4 data3 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 3);
+
+               float3 lightpos = make_float3(data0.y, data0.z, data0.w);
+               float3 dir = make_float3(data3.y, data3.z, data3.w);
+
+               if(dot(dir, P - lightpos) <= 1e-5f) {
+                       /* P is on the wrong side or too close. */
+                       continue;
+               }
+
+               if(is_possible) {
+                       /* There's a portal that could be sampled from this position. */
+                       *is_possible = true;
+               }
+
+               float t = -(dot(P, dir) - dot(lightpos, dir)) / dot(direction, dir);
+               if(t <= 1e-5f) {
+                       /* Either behind the portal or too close. */
+                       continue;
+               }
+
+               float4 data1 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 1);
+               float4 data2 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 2);
+
+               float3 axisu = make_float3(data1.y, data1.z, data1.w);
+               float3 axisv = make_float3(data2.y, data2.z, data2.w);
+
+               float3 hit = P + t*direction;
+               float3 inplane = hit - lightpos;
+               /* Skip if the the ray doesn't pass through portal. */
+               if(fabsf(dot(inplane, axisu) / dot(axisu, axisu)) > 0.5f)
+                       continue;
+               if(fabsf(dot(inplane, axisv) / dot(axisv, axisv)) > 0.5f)
+                       continue;
+
+               portal_pdf += area_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
+       }
+
+       return kernel_data.integrator.num_portals? portal_pdf / kernel_data.integrator.num_portals: 0.0f;
+}
+
+ccl_device int background_num_possible_portals(KernelGlobals *kg, float3 P)
+{
+       int num_possible_portals = 0;
+       for(int p = 0; p < kernel_data.integrator.num_portals; p++) {
+               float4 data0 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 0);
+               float4 data3 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 3);
+               float3 lightpos = make_float3(data0.y, data0.z, data0.w);
+               float3 dir = make_float3(data3.y, data3.z, data3.w);
+
+               /* Check whether portal is on the right side. */
+               if(dot(dir, P - lightpos) > 1e-5f)
+                       num_possible_portals++;
+       }
+       return num_possible_portals;
+}
+
+ccl_device float3 background_portal_sample(KernelGlobals *kg,
+                                           float3 P,
+                                           float randu,
+                                           float randv,
+                                           int num_possible,
+                                           int *sampled_portal,
+                                           float *pdf)
+{
+       /* Pick a portal, then re-normalize randv. */
+       randv *= num_possible;
+       int portal = (int)randv;
+       randv -= portal;
+
+       /* TODO(sergey): Some smarter way of finding portal to sample
+        * is welcome.
+        */
+       for(int p = 0; p < kernel_data.integrator.num_portals; p++) {
+               /* Search for the sampled portal. */
+               float4 data0 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 0);
+               float4 data3 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 3);
+               float3 lightpos = make_float3(data0.y, data0.z, data0.w);
+               float3 dir = make_float3(data3.y, data3.z, data3.w);
+
+               /* Check whether portal is on the right side. */
+               if(dot(dir, P - lightpos) <= 1e-5f)
+                       continue;
+
+               if(portal == 0) {
+                       /* p is the portal to be sampled. */
+                       float4 data1 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 1);
+                       float4 data2 = kernel_tex_fetch(__light_data, (p + kernel_data.integrator.portal_offset)*LIGHT_SIZE + 2);
+                       float3 axisu = make_float3(data1.y, data1.z, data1.w);
+                       float3 axisv = make_float3(data2.y, data2.z, data2.w);
+
+                       float3 lightPoint = lightpos;
+
+                       *pdf = area_light_sample(P, &lightPoint,
+                                                axisu, axisv,
+                                                randu, randv,
+                                                true);
+
+                       *pdf /= num_possible;
+                       *sampled_portal = p;
+                       return normalize(lightPoint - P);
+               }
+
+               portal--;
+       }
+
+       return make_float3(0.0f, 0.0f, 0.0f);
+}
+
+ccl_device float3 background_light_sample(KernelGlobals *kg, float3 P, float randu, float randv, float *pdf)
+{
+       /* Probability of sampling portals instead of the map. */
+       float portal_sampling_pdf = kernel_data.integrator.portal_pdf;
+
+       /* Check if there are portals in the scene which we can sample. */
+       if(portal_sampling_pdf > 0.0f) {
+               int num_portals = background_num_possible_portals(kg, P);
+               if(num_portals > 0) {
+                       if(portal_sampling_pdf == 1.0f || randu < portal_sampling_pdf) {
+                               if(portal_sampling_pdf < 1.0f) {
+                                       randu /= portal_sampling_pdf;
+                               }
+                               int portal;
+                               float3 D = background_portal_sample(kg, P, randu, randv, num_portals, &portal, pdf);
+                               if(num_portals > 1) {
+                                       /* Ignore the chosen portal, its pdf is already included. */
+                                       *pdf += background_portal_pdf(kg, P, D, portal, NULL);
+                               }
+                               /* We could also have sampled the map, so combine with MIS. */
+                               if(portal_sampling_pdf < 1.0f) {
+                                       float cdf_pdf = background_map_pdf(kg, D);
+                                       *pdf = (portal_sampling_pdf * (*pdf)
+                                            + (1.0f - portal_sampling_pdf) * cdf_pdf);
+                               }
+                               return D;
+                       } else {
+                               /* Sample map, but with nonzero portal_sampling_pdf for MIS. */
+                               randu = (randu - portal_sampling_pdf) / (1.0f - portal_sampling_pdf);
+                       }
+               } else {
+                       /* We can't sample a portal.
+                        * Check if we can sample the map instead.
+                        */
+                       if(portal_sampling_pdf == 1.0f) {
+                               /* Use uniform as a fallback if we can't sample the map. */
+                               *pdf = 1.0f / M_4PI_F;
+                               return sample_uniform_sphere(randu, randv);
+                       }
+                       else {
+                               portal_sampling_pdf = 0.0f;
+                       }
+               }
+       }
+
+       float3 D = background_map_sample(kg, randu, randv, pdf);
+       /* Use MIS if portals could be sampled as well. */
+       if(portal_sampling_pdf > 0.0f) {
+               float portal_pdf = background_portal_pdf(kg, P, D, -1, NULL);
+               *pdf = (portal_sampling_pdf * portal_pdf
+                    + (1.0f - portal_sampling_pdf) * (*pdf));
+       }
+       return D;
+}
 
-       return pdf * kernel_data.integrator.pdf_lights;
+ccl_device float background_light_pdf(KernelGlobals *kg, float3 P, float3 direction)
+{
+       /* Probability of sampling portals instead of the map. */
+       float portal_sampling_pdf = kernel_data.integrator.portal_pdf;
+
+       if(portal_sampling_pdf > 0.0f) {
+               bool is_possible;
+               float portal_pdf = background_portal_pdf(kg, P, direction, -1, &is_possible);
+               if(portal_pdf == 0.0f) {
+                       if(portal_sampling_pdf == 1.0f) {
+                               /* If there are no possible portals at this point,
+                                * the fallback sampling would have been used.
+                                * Otherwise, the direction would not be sampled at all => pdf = 0
+                                */
+                               return is_possible? 0.0f: kernel_data.integrator.pdf_lights / M_4PI_F;
+                       }
+                       else {
+                               /* We can only sample the map. */
+                               return background_map_pdf(kg, direction) * kernel_data.integrator.pdf_lights;
+                       }
+               } else {
+                       if(portal_sampling_pdf == 1.0f) {
+                               /* We can only sample portals. */
+                               return portal_pdf * kernel_data.integrator.pdf_lights;
+                       }
+                       else {
+                               /* We can sample both, so combine with MIS. */
+                               return (background_map_pdf(kg, direction) * (1.0f - portal_sampling_pdf)
+                                     + portal_pdf * portal_sampling_pdf) * kernel_data.integrator.pdf_lights;
+                       }
+               }
+       }
+
+       /* No portals in the scene, so must sample the map.
+        * At least one of them is always possible if we have a LIGHT_BACKGROUND.
+        */
+       return background_map_pdf(kg, direction) * kernel_data.integrator.pdf_lights;
 }
 #endif
 
@@ -184,96 +493,6 @@ ccl_device float3 sphere_light_sample(float3 P, float3 center, float radius, flo
        return disk_light_sample(normalize(P - center), randu, randv)*radius;
 }
 
-/* Uses the following paper:
- *
- * Carlos Urena et al.
- * An Area-Preserving Parametrization for Spherical Rectangles.
- *
- * https://www.solidangle.com/research/egsr2013_spherical_rectangle.pdf
- *
- * Note: light_p is modified when sample_coord is true.
- */
-ccl_device float area_light_sample(float3 P,
-                                   float3 *light_p,
-                                   float3 axisu, float3 axisv,
-                                   float randu, float randv,
-                                   bool sample_coord)
-{
-       /* In our name system we're using P for the center,
-        * which is o in the paper.
-        */
-
-       float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f;
-       float axisu_len, axisv_len;
-       /* Compute local reference system R. */
-       float3 x = normalize_len(axisu, &axisu_len);
-       float3 y = normalize_len(axisv, &axisv_len);
-       float3 z = cross(x, y);
-       /* Compute rectangle coords in local reference system. */
-       float3 dir = corner - P;
-       float z0 = dot(dir, z);
-       /* Flip 'z' to make it point against Q. */
-       if(z0 > 0.0f) {
-               z *= -1.0f;
-               z0 *= -1.0f;
-       }
-       float x0 = dot(dir, x);
-       float y0 = dot(dir, y);
-       float x1 = x0 + axisu_len;
-       float y1 = y0 + axisv_len;
-       /* Create vectors to four vertices. */
-       float3 v00 = make_float3(x0, y0, z0);
-       float3 v01 = make_float3(x0, y1, z0);
-       float3 v10 = make_float3(x1, y0, z0);
-       float3 v11 = make_float3(x1, y1, z0);
-       /* Compute normals to edges. */
-       float3 n0 = normalize(cross(v00, v10));
-       float3 n1 = normalize(cross(v10, v11));
-       float3 n2 = normalize(cross(v11, v01));
-       float3 n3 = normalize(cross(v01, v00));
-       /* Compute internal angles (gamma_i). */
-       float g0 = safe_acosf(-dot(n0, n1));
-       float g1 = safe_acosf(-dot(n1, n2));
-       float g2 = safe_acosf(-dot(n2, n3));
-       float g3 = safe_acosf(-dot(n3, n0));
-       /* Compute predefined constants. */
-       float b0 = n0.z;
-       float b1 = n2.z;
-       float b0sq = b0 * b0;
-       float k = M_2PI_F - g2 - g3;
-       /* Compute solid angle from internal angles. */
-       float S = g0 + g1 - k;
-
-       if(sample_coord) {
-               /* Compute cu. */
-               float au = randu * S + k;
-               float fu = (cosf(au) * b0 - b1) / sinf(au);
-               float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
-               cu = clamp(cu, -1.0f, 1.0f);
-               /* Compute xu. */
-               float xu = -(cu * z0) / sqrtf(1.0f - cu * cu);
-               xu = clamp(xu, x0, x1);
-               /* Compute yv. */
-               float z0sq = z0 * z0;
-               float y0sq = y0 * y0;
-               float y1sq = y1 * y1;
-               float d = sqrtf(xu * xu + z0sq);
-               float h0 = y0 / sqrtf(d * d + y0sq);
-               float h1 = y1 / sqrtf(d * d + y1sq);
-               float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
-               float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
-
-               /* Transform (xu, yv, z0) to world coords. */
-               *light_p = P + xu * x + yv * y + z0 * z;
-       }
-
-       /* return pdf */
-       if(S != 0.0f)
-               return 1.0f / S;
-       else
-               return 0.0f;
-}
-
 ccl_device float spot_light_attenuation(float4 data1, float4 data2, LightSample *ls)
 {
        float3 dir = make_float3(data2.y, data2.z, data2.w);
@@ -344,13 +563,14 @@ ccl_device void lamp_light_sample(KernelGlobals *kg, int lamp,
 #ifdef __BACKGROUND_MIS__
        else if(type == LIGHT_BACKGROUND) {
                /* infinite area light (e.g. light dome or env light) */
-               float3 D = background_light_sample(kg, randu, randv, &ls->pdf);
+               float3 D = -background_light_sample(kg, P, randu, randv, &ls->pdf);
 
                ls->P = D;
                ls->Ng = D;
                ls->D = -D;
                ls->t = FLT_MAX;
                ls->eval_fac = 1.0f;
+               ls->pdf *= kernel_data.integrator.pdf_lights;
        }
 #endif
        else {
index 05cfb0adc71f25cee3fd37ed3454a88b7e5d35a3..f4f2e22edaa40dea94b2837e6e5f75d49ff26818 100644 (file)
@@ -896,6 +896,11 @@ typedef struct KernelIntegrator {
        float inv_pdf_lights;
        int pdf_background_res;
 
+       /* light portals */
+       float portal_pdf;
+       int num_portals;
+       int portal_offset;
+
        /* bounces */
        int min_bounce;
        int max_bounce;
@@ -948,6 +953,8 @@ typedef struct KernelIntegrator {
        int volume_max_steps;
        float volume_step_size;
        int volume_samples;
+
+       int pad;
 } KernelIntegrator;
 
 typedef struct KernelBVH {
index 91e45aea17a780dfb2aca2ac7e058eff6d725f7f..817e1f5806c576959e0c2420935fe66df671be3f 100644 (file)
@@ -129,6 +129,8 @@ Light::Light()
        shader = 0;
        samples = 1;
        max_bounces = 1024;
+
+       is_portal = false;
 }
 
 void Light::tag_update(Scene *scene)
@@ -153,10 +155,17 @@ void LightManager::device_update_distribution(Device *device, DeviceScene *dscen
        progress.set_status("Updating Lights", "Computing distribution");
 
        /* count */
-       size_t num_lights = scene->lights.size();
+       size_t num_lights = 0;
        size_t num_background_lights = 0;
        size_t num_triangles = 0;
 
+       bool background_mis = false;
+
+       foreach(Light *light, scene->lights) {
+               if(!light->is_portal)
+                       num_lights++;
+       }
+
        foreach(Object *object, scene->objects) {
                Mesh *mesh = object->mesh;
                bool have_emission = false;
@@ -287,22 +296,29 @@ void LightManager::device_update_distribution(Device *device, DeviceScene *dscen
        float trianglearea = totarea;
 
        /* point lights */
-       float lightarea = (totarea > 0.0f)? totarea/scene->lights.size(): 1.0f;
+       float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f;
        bool use_lamp_mis = false;
 
-       for(int i = 0; i < scene->lights.size(); i++, offset++) {
-               Light *light = scene->lights[i];
+       int light_index = 0;
+       foreach(Light *light, scene->lights) {
+               if(light->is_portal)
+                       continue;
 
                distribution[offset].x = totarea;
-               distribution[offset].y = __int_as_float(~(int)i);
+               distribution[offset].y = __int_as_float(~light_index);
                distribution[offset].z = 1.0f;
                distribution[offset].w = light->size;
                totarea += lightarea;
 
                if(light->size > 0.0f && light->use_mis)
                        use_lamp_mis = true;
-               if(light->type == LIGHT_BACKGROUND)
+               if(light->type == LIGHT_BACKGROUND) {
                        num_background_lights++;
+                       background_mis = light->use_mis;
+               }
+
+               light_index++;
+               offset++;
        }
 
        /* normalize cumulative distribution functions */
@@ -364,6 +380,18 @@ void LightManager::device_update_distribution(Device *device, DeviceScene *dscen
 
                /* CDF */
                device->tex_alloc("__light_distribution", dscene->light_distribution);
+
+               /* Portals */
+               if(num_background_lights > 0 && light_index != scene->lights.size()) {
+                       kintegrator->portal_offset = light_index;
+                       kintegrator->num_portals = scene->lights.size() - light_index;
+                       kintegrator->portal_pdf = background_mis? 0.5f: 1.0f;
+               }
+               else {
+                       kintegrator->num_portals = 0;
+                       kintegrator->portal_offset = 0;
+                       kintegrator->portal_pdf = 0.0f;
+               }
        }
        else {
                dscene->light_distribution.clear();
@@ -374,6 +402,10 @@ void LightManager::device_update_distribution(Device *device, DeviceScene *dscen
                kintegrator->pdf_lights = 0.0f;
                kintegrator->inv_pdf_lights = 0.0f;
                kintegrator->use_lamp_mis = false;
+               kintegrator->num_portals = 0;
+               kintegrator->portal_offset = 0;
+               kintegrator->portal_pdf = 0.0f;
+
                kfilm->pass_shadow_scale = 1.0f;
        }
 }
@@ -515,8 +547,8 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
 
        float4 *light_data = dscene->light_data.resize(scene->lights.size()*LIGHT_SIZE);
 
-       if(!device->info.advanced_shading) {
-               /* remove unsupported light */
+       /* remove background light? */
+       if(!(device->info.advanced_shading)) {
                foreach(Light *light, scene->lights) {
                        if(light->type == LIGHT_BACKGROUND) {
                                scene->lights.erase(std::remove(scene->lights.begin(), scene->lights.end(), light), scene->lights.end());
@@ -525,10 +557,14 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                }
        }
 
-       for(size_t i = 0; i < scene->lights.size(); i++) {
-               Light *light = scene->lights[i];
+       int light_index = 0;
+
+       foreach(Light *light, scene->lights) {
+               if(light->is_portal)
+                       continue;
+
                float3 co = light->co;
-               int shader_id = scene->shader_manager->get_shader_id(scene->lights[i]->shader);
+               int shader_id = scene->shader_manager->get_shader_id(light->shader);
                float samples = __int_as_float(light->samples);
                float max_bounces = __int_as_float(light->max_bounces);
 
@@ -561,11 +597,11 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                        if(light->use_mis && radius > 0.0f)
                                shader_id |= SHADER_USE_MIS;
 
-                       light_data[i*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
-                       light_data[i*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, invarea, 0.0f);
-                       light_data[i*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
+                       light_data[light_index*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, invarea, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
                }
                else if(light->type == LIGHT_DISTANT) {
                        shader_id &= ~SHADER_AREA_LIGHT;
@@ -582,11 +618,11 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                        if(light->use_mis && area > 0.0f)
                                shader_id |= SHADER_USE_MIS;
 
-                       light_data[i*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), dir.x, dir.y, dir.z);
-                       light_data[i*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, cosangle, invarea);
-                       light_data[i*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), dir.x, dir.y, dir.z);
+                       light_data[light_index*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, cosangle, invarea);
+                       light_data[light_index*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
                }
                else if(light->type == LIGHT_BACKGROUND) {
                        uint visibility = scene->background->visibility;
@@ -611,11 +647,11 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                                use_light_visibility = true;
                        }
 
-                       light_data[i*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 2] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
                }
                else if(light->type == LIGHT_AREA) {
                        float3 axisu = light->axisu*(light->sizeu*light->size);
@@ -629,11 +665,11 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                        if(light->use_mis && area > 0.0f)
                                shader_id |= SHADER_USE_MIS;
 
-                       light_data[i*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
-                       light_data[i*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), axisu.x, axisu.y, axisu.z);
-                       light_data[i*LIGHT_SIZE + 2] = make_float4(invarea, axisv.x, axisv.y, axisv.z);
-                       light_data[i*LIGHT_SIZE + 3] = make_float4(samples, dir.x, dir.y, dir.z);
-                       light_data[i*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
+                       light_data[light_index*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), axisu.x, axisu.y, axisu.z);
+                       light_data[light_index*LIGHT_SIZE + 2] = make_float4(invarea, axisv.x, axisv.y, axisv.z);
+                       light_data[light_index*LIGHT_SIZE + 3] = make_float4(samples, dir.x, dir.y, dir.z);
+                       light_data[light_index*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
                }
                else if(light->type == LIGHT_SPOT) {
                        shader_id &= ~SHADER_AREA_LIGHT;
@@ -649,14 +685,44 @@ void LightManager::device_update_points(Device *device, DeviceScene *dscene, Sce
                        if(light->use_mis && radius > 0.0f)
                                shader_id |= SHADER_USE_MIS;
 
-                       light_data[i*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
-                       light_data[i*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, invarea, spot_angle);
-                       light_data[i*LIGHT_SIZE + 2] = make_float4(spot_smooth, dir.x, dir.y, dir.z);
-                       light_data[i*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
-                       light_data[i*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
+                       light_data[light_index*LIGHT_SIZE + 1] = make_float4(__int_as_float(shader_id), radius, invarea, spot_angle);
+                       light_data[light_index*LIGHT_SIZE + 2] = make_float4(spot_smooth, dir.x, dir.y, dir.z);
+                       light_data[light_index*LIGHT_SIZE + 3] = make_float4(samples, 0.0f, 0.0f, 0.0f);
+                       light_data[light_index*LIGHT_SIZE + 4] = make_float4(max_bounces, 0.0f, 0.0f, 0.0f);
                }
+
+               light_index++;
        }
-       
+
+       /* TODO(sergey): Consider moving portals update to their own function
+        * keeping this one more manageable.
+        */
+       foreach(Light *light, scene->lights) {
+               if(!light->is_portal)
+                       continue;
+               assert(light->type == LIGHT_AREA);
+
+               float3 co = light->co;
+               float3 axisu = light->axisu*(light->sizeu*light->size);
+               float3 axisv = light->axisv*(light->sizev*light->size);
+               float area = len(axisu)*len(axisv);
+               float invarea = (area > 0.0f) ? 1.0f / area : 1.0f;
+               float3 dir = light->dir;
+
+               dir = safe_normalize(dir);
+
+               light_data[light_index*LIGHT_SIZE + 0] = make_float4(__int_as_float(light->type), co.x, co.y, co.z);
+               light_data[light_index*LIGHT_SIZE + 1] = make_float4(area, axisu.x, axisu.y, axisu.z);
+               light_data[light_index*LIGHT_SIZE + 2] = make_float4(invarea, axisv.x, axisv.y, axisv.z);
+               light_data[light_index*LIGHT_SIZE + 3] = make_float4(-1, dir.x, dir.y, dir.z);
+               light_data[light_index*LIGHT_SIZE + 4] = make_float4(-1, 0.0f, 0.0f, 0.0f);
+
+               light_index++;
+       }
+
+       assert(light_index == scene->lights.size());
+
        device->tex_alloc("__light_data", dscene->light_data);
 }
 
index 1f8eac6a97fee1c0887e64494d4e7260d8aafc04..824d99aeb9360604c02f35a2d9a572f1598b9ea7 100644 (file)
@@ -56,6 +56,8 @@ public:
        bool use_transmission;
        bool use_scatter;
 
+       bool is_portal;
+
        int shader;
        int samples;
        int max_bounces;