diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 875b5d384e3..538876a71e2 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -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): diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 74de31850ee..cdacad75221 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -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 diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index 7364a8b170a..bcc366732df 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -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 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()) diff --git a/intern/cycles/blender/blender_sync.h b/intern/cycles/blender/blender_sync.h index 5fbf2c3011f..89d93e19e9f 100644 --- a/intern/cycles/blender/blender_sync.h +++ b/intern/cycles/blender/blender_sync.h @@ -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); diff --git a/intern/cycles/kernel/kernel_emission.h b/intern/cycles/kernel/kernel_emission.h index d3cbc5cff06..6c5a5fac8c5 100644 --- a/intern/cycles/kernel/kernel_emission.h +++ b/intern/cycles/kernel/kernel_emission.h @@ -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; diff --git a/intern/cycles/kernel/kernel_light.h b/intern/cycles/kernel/kernel_light.h index 76fa754b5fa..d1b8db2c018 100644 --- a/intern/cycles/kernel/kernel_light.h +++ b/intern/cycles/kernel/kernel_light.h @@ -33,156 +33,7 @@ typedef struct LightSample { LightType type; /* type of light */ } LightSample; -/* Background Light */ - -#ifdef __BACKGROUND_MIS__ - -/* TODO(sergey): In theory it should be all fine to use noinline for all - * devices, but we're so close to the release so better not screw things - * up for CPU at least. - */ -#ifdef __KERNEL_GPU__ -ccl_device_noinline -#else -ccl_device -#endif -float3 background_light_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 - * value is the CDF total. */ - int res = kernel_data.integrator.pdf_background_res; - int cdf_count = res + 1; - - /* this is basically std::lower_bound as used by pbrt */ - int first = 0; - int count = res; - - while(count > 0) { - int step = count >> 1; - int middle = first + step; - - if(kernel_tex_fetch(__light_background_marginal_cdf, middle).y < randv) { - first = middle + 1; - count -= step + 1; - } - else - count = step; - } - - int index_v = max(0, first - 1); - kernel_assert(index_v >= 0 && index_v < res); - - float2 cdf_v = kernel_tex_fetch(__light_background_marginal_cdf, index_v); - float2 cdf_next_v = kernel_tex_fetch(__light_background_marginal_cdf, index_v + 1); - float2 cdf_last_v = kernel_tex_fetch(__light_background_marginal_cdf, res); - - /* importance-sampled V direction */ - float dv = (randv - cdf_v.y) / (cdf_next_v.y - cdf_v.y); - float v = (index_v + dv) / res; - - /* this is basically std::lower_bound as used by pbrt */ - first = 0; - count = res; - while(count > 0) { - int step = count >> 1; - int middle = first + step; - - if(kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + middle).y < randu) { - first = middle + 1; - count -= step + 1; - } - else - count = step; - } - - int index_u = max(0, first - 1); - kernel_assert(index_u >= 0 && index_u < res); - - float2 cdf_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + index_u); - float2 cdf_next_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + index_u + 1); - float2 cdf_last_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + res); - - /* importance-sampled U direction */ - float du = (randu - cdf_u.y) / (cdf_next_u.y - cdf_u.y); - float u = (index_u + du) / res; - - /* compute pdf */ - float denom = cdf_last_u.x * cdf_last_v.x; - float sin_theta = sinf(M_PI_F * v); - - if(sin_theta == 0.0f || denom == 0.0f) - *pdf = 0.0f; - 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); -} - -/* TODO(sergey): Same as above, after the release we should consider using - * 'noinline' for all devices. - */ -#ifdef __KERNEL_GPU__ -ccl_device_noinline -#else -ccl_device -#endif -float background_light_pdf(KernelGlobals *kg, float3 direction) -{ - float2 uv = direction_to_equirectangular(direction); - int res = kernel_data.integrator.pdf_background_res; - - float sin_theta = sinf(uv.y * M_PI_F); - - if(sin_theta == 0.0f) - return 0.0f; - - int index_u = clamp(float_to_int(uv.x * res), 0, res - 1); - int index_v = clamp(float_to_int(uv.y * res), 0, res - 1); - - /* pdfs in V direction */ - float2 cdf_last_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * (res + 1) + res); - float2 cdf_last_v = kernel_tex_fetch(__light_background_marginal_cdf, res); - - float denom = cdf_last_u.x * cdf_last_v.x; - - if(denom == 0.0f) - return 0.0f; - - /* pdfs in U 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 pdf * kernel_data.integrator.pdf_lights; -} -#endif - -/* Regular Light */ - -ccl_device float3 disk_light_sample(float3 v, float randu, float randv) -{ - float3 ru, rv; - - make_orthonormals(v, &ru, &rv); - to_unit_disk(&randu, &randv); - - return ru*randu + rv*randv; -} - -ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv) -{ - return normalize(D + disk_light_sample(D, randu, randv)*radius); -} - -ccl_device float3 sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv) -{ - return disk_light_sample(normalize(P - center), randu, randv)*radius; -} +/* Area light sampling */ /* Uses the following paper: * @@ -200,8 +51,8 @@ ccl_device float area_light_sample(float3 P, bool sample_coord) { /* In our name system we're using P for the center, - * which is o in the paper. - */ + * which is o in the paper. + */ float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f; float axisu_len, axisv_len; @@ -274,6 +125,374 @@ ccl_device float area_light_sample(float3 P, return 0.0f; } +/* Background Light */ + +#ifdef __BACKGROUND_MIS__ + +/* TODO(sergey): In theory it should be all fine to use noinline for all + * devices, but we're so close to the release so better not screw things + * up for CPU at least. + */ +#ifdef __KERNEL_GPU__ +ccl_device_noinline +#else +ccl_device +#endif +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 + * value is the CDF total. */ + int res = kernel_data.integrator.pdf_background_res; + int cdf_count = res + 1; + + /* this is basically std::lower_bound as used by pbrt */ + int first = 0; + int count = res; + + while(count > 0) { + int step = count >> 1; + int middle = first + step; + + if(kernel_tex_fetch(__light_background_marginal_cdf, middle).y < randv) { + first = middle + 1; + count -= step + 1; + } + else + count = step; + } + + int index_v = max(0, first - 1); + kernel_assert(index_v >= 0 && index_v < res); + + float2 cdf_v = kernel_tex_fetch(__light_background_marginal_cdf, index_v); + float2 cdf_next_v = kernel_tex_fetch(__light_background_marginal_cdf, index_v + 1); + float2 cdf_last_v = kernel_tex_fetch(__light_background_marginal_cdf, res); + + /* importance-sampled V direction */ + float dv = (randv - cdf_v.y) / (cdf_next_v.y - cdf_v.y); + float v = (index_v + dv) / res; + + /* this is basically std::lower_bound as used by pbrt */ + first = 0; + count = res; + while(count > 0) { + int step = count >> 1; + int middle = first + step; + + if(kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + middle).y < randu) { + first = middle + 1; + count -= step + 1; + } + else + count = step; + } + + int index_u = max(0, first - 1); + kernel_assert(index_u >= 0 && index_u < res); + + float2 cdf_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + index_u); + float2 cdf_next_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + index_u + 1); + float2 cdf_last_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + res); + + /* importance-sampled U direction */ + float du = (randu - cdf_u.y) / (cdf_next_u.y - cdf_u.y); + float u = (index_u + du) / res; + + /* compute pdf */ + float denom = cdf_last_u.x * cdf_last_v.x; + float sin_theta = sinf(M_PI_F * v); + + if(sin_theta == 0.0f || denom == 0.0f) + *pdf = 0.0f; + else + *pdf = (cdf_u.x * cdf_v.x)/(M_2PI_F * M_PI_F * sin_theta * denom); + + /* compute direction */ + return equirectangular_to_direction(u, v); +} + +/* TODO(sergey): Same as above, after the release we should consider using + * 'noinline' for all devices. + */ +#ifdef __KERNEL_GPU__ +ccl_device_noinline +#else +ccl_device +#endif +float background_map_pdf(KernelGlobals *kg, float3 direction) +{ + float2 uv = direction_to_equirectangular(direction); + int res = kernel_data.integrator.pdf_background_res; + + float sin_theta = sinf(uv.y * M_PI_F); + + if(sin_theta == 0.0f) + return 0.0f; + + int index_u = clamp(float_to_int(uv.x * res), 0, res - 1); + int index_v = clamp(float_to_int(uv.y * res), 0, res - 1); + + /* pdfs in V direction */ + float2 cdf_last_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * (res + 1) + res); + float2 cdf_last_v = kernel_tex_fetch(__light_background_marginal_cdf, res); + + float denom = cdf_last_u.x * cdf_last_v.x; + + if(denom == 0.0f) + return 0.0f; + + /* pdfs in U 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); + + 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; +} + +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 + +/* Regular Light */ + +ccl_device float3 disk_light_sample(float3 v, float randu, float randv) +{ + float3 ru, rv; + + make_orthonormals(v, &ru, &rv); + to_unit_disk(&randu, &randv); + + return ru*randu + rv*randv; +} + +ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv) +{ + return normalize(D + disk_light_sample(D, randu, randv)*radius); +} + +ccl_device float3 sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv) +{ + return disk_light_sample(normalize(P - center), randu, randv)*radius; +} + 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 { diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 05cfb0adc71..f4f2e22edaa 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -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 { diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 91e45aea17a..817e1f5806c 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -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); } diff --git a/intern/cycles/render/light.h b/intern/cycles/render/light.h index 1f8eac6a97f..824d99aeb93 100644 --- a/intern/cycles/render/light.h +++ b/intern/cycles/render/light.h @@ -56,6 +56,8 @@ public: bool use_transmission; bool use_scatter; + bool is_portal; + int shader; int samples; int max_bounces;