Cycles: Added support for light portals

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
This commit is contained in:
Lukas Stockner 2015-04-28 00:51:55 +05:00 committed by Sergey Sharybin
parent 84836e8952
commit f478c2cfbd
9 changed files with 540 additions and 213 deletions

View 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):

View 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

View 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())

View 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);

View 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;

View File

@ -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 {

View 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 {

View 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);
}

View File

@ -56,6 +56,8 @@ public:
bool use_transmission;
bool use_scatter;
bool is_portal;
int shader;
int samples;
int max_bounces;