/* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ #include "device/device.h" #include "scene/background.h" #include "scene/film.h" #include "scene/integrator.h" #include "scene/light.h" #include "scene/mesh.h" #include "scene/object.h" #include "scene/scene.h" #include "scene/shader.h" #include "scene/shader_graph.h" #include "scene/shader_nodes.h" #include "scene/stats.h" #include "integrator/shader_eval.h" #include "util/foreach.h" #include "util/hash.h" #include "util/log.h" #include "util/path.h" #include "util/progress.h" #include "util/task.h" CCL_NAMESPACE_BEGIN static void shade_background_pixels(Device *device, DeviceScene *dscene, int width, int height, vector &pixels, Progress &progress) { /* Needs to be up to data for attribute access. */ device->const_copy_to("data", &dscene->data, sizeof(dscene->data)); const int size = width * height; const int num_channels = 3; pixels.resize(size); /* Evaluate shader on device. */ ShaderEval shader_eval(device, progress); shader_eval.eval( SHADER_EVAL_BACKGROUND, size, num_channels, [&](device_vector &d_input) { /* Fill coordinates for shading. */ KernelShaderEvalInput *d_input_data = d_input.data(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float u = (x + 0.5f) / width; float v = (y + 0.5f) / height; KernelShaderEvalInput in; in.object = OBJECT_NONE; in.prim = PRIM_NONE; in.u = u; in.v = v; d_input_data[x + y * width] = in; } } return size; }, [&](device_vector &d_output) { /* Copy output to pixel buffer. */ float *d_output_data = d_output.data(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { pixels[y * width + x].x = d_output_data[(y * width + x) * num_channels + 0]; pixels[y * width + x].y = d_output_data[(y * width + x) * num_channels + 1]; pixels[y * width + x].z = d_output_data[(y * width + x) * num_channels + 2]; } } }); } /* Light */ NODE_DEFINE(Light) { NodeType *type = NodeType::add("light", create); static NodeEnum type_enum; type_enum.insert("point", LIGHT_POINT); type_enum.insert("distant", LIGHT_DISTANT); type_enum.insert("background", LIGHT_BACKGROUND); type_enum.insert("area", LIGHT_AREA); type_enum.insert("spot", LIGHT_SPOT); SOCKET_ENUM(light_type, "Type", type_enum, LIGHT_POINT); SOCKET_COLOR(strength, "Strength", one_float3()); SOCKET_POINT(co, "Co", zero_float3()); SOCKET_VECTOR(dir, "Dir", zero_float3()); SOCKET_FLOAT(size, "Size", 0.0f); SOCKET_FLOAT(angle, "Angle", 0.0f); SOCKET_VECTOR(axisu, "Axis U", zero_float3()); SOCKET_FLOAT(sizeu, "Size U", 1.0f); SOCKET_VECTOR(axisv, "Axis V", zero_float3()); SOCKET_FLOAT(sizev, "Size V", 1.0f); SOCKET_BOOLEAN(round, "Round", false); SOCKET_FLOAT(spread, "Spread", M_PI_F); SOCKET_INT(map_resolution, "Map Resolution", 0); SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F); SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f); SOCKET_TRANSFORM(tfm, "Transform", transform_identity()); SOCKET_BOOLEAN(cast_shadow, "Cast Shadow", true); SOCKET_BOOLEAN(use_mis, "Use Mis", false); SOCKET_BOOLEAN(use_camera, "Use Camera", true); SOCKET_BOOLEAN(use_diffuse, "Use Diffuse", true); SOCKET_BOOLEAN(use_glossy, "Use Glossy", true); SOCKET_BOOLEAN(use_transmission, "Use Transmission", true); SOCKET_BOOLEAN(use_scatter, "Use Scatter", true); SOCKET_BOOLEAN(use_caustics, "Shadow Caustics", false); SOCKET_INT(max_bounces, "Max Bounces", 1024); SOCKET_UINT(random_id, "Random ID", 0); SOCKET_BOOLEAN(is_shadow_catcher, "Shadow Catcher", true); SOCKET_BOOLEAN(is_portal, "Is Portal", false); SOCKET_BOOLEAN(is_enabled, "Is Enabled", true); SOCKET_NODE(shader, "Shader", Shader::get_node_type()); SOCKET_STRING(lightgroup, "Light Group", ustring()); return type; } Light::Light() : Node(get_node_type()) { dereference_all_used_nodes(); } void Light::tag_update(Scene *scene) { if (is_modified()) { scene->light_manager->tag_update(scene, LightManager::LIGHT_MODIFIED); } } bool Light::has_contribution(Scene *scene) { if (strength == zero_float3()) { return false; } if (is_portal) { return false; } if (light_type == LIGHT_BACKGROUND) { return true; } return (shader) ? shader->has_surface_emission : scene->default_light->has_surface_emission; } /* Light Manager */ LightManager::LightManager() { update_flags = UPDATE_ALL; need_update_background = true; last_background_enabled = false; last_background_resolution = 0; } LightManager::~LightManager() { foreach (IESSlot *slot, ies_slots) { delete slot; } } bool LightManager::has_background_light(Scene *scene) { foreach (Light *light, scene->lights) { if (light->light_type == LIGHT_BACKGROUND && light->is_enabled) { return true; } } return false; } void LightManager::test_enabled_lights(Scene *scene) { /* Make all lights enabled by default, and perform some preliminary checks * needed for finer-tuning of settings (for example, check whether we've * got portals or not). */ bool has_portal = false, has_background = false; foreach (Light *light, scene->lights) { light->is_enabled = light->has_contribution(scene); has_portal |= light->is_portal; has_background |= light->light_type == LIGHT_BACKGROUND; } bool background_enabled = false; int background_resolution = 0; if (has_background) { /* Ignore background light if: * - If unsupported on a device * - If we don't need it (no HDRs etc.) */ Shader *shader = scene->background->get_shader(scene); const bool disable_mis = !(has_portal || shader->has_surface_spatial_varying); if (disable_mis) { VLOG_INFO << "Background MIS has been disabled.\n"; } foreach (Light *light, scene->lights) { if (light->light_type == LIGHT_BACKGROUND) { light->is_enabled = !disable_mis; background_enabled = !disable_mis; background_resolution = light->map_resolution; } } } if (last_background_enabled != background_enabled || last_background_resolution != background_resolution) { last_background_enabled = background_enabled; last_background_resolution = background_resolution; need_update_background = true; } } bool LightManager::object_usable_as_light(Object *object) { Geometry *geom = object->get_geometry(); if (geom->geometry_type != Geometry::MESH && geom->geometry_type != Geometry::VOLUME) { return false; } /* Skip objects with NaNs */ if (!object->bounds.valid()) { return false; } /* Skip if we are not visible for BSDFs. */ if (!(object->get_visibility() & (PATH_RAY_DIFFUSE | PATH_RAY_GLOSSY | PATH_RAY_TRANSMIT))) { return false; } /* Skip if we have no emission shaders. */ /* TODO(sergey): Ideally we want to avoid such duplicated loop, since it'll * iterate all geometry shaders twice (when counting and when calculating * triangle area. */ foreach (Node *node, geom->get_used_shaders()) { Shader *shader = static_cast(node); if (shader->get_use_mis() && shader->has_surface_emission) { return true; } } return false; } void LightManager::device_update_distribution(Device *, DeviceScene *dscene, Scene *scene, Progress &progress) { progress.set_status("Updating Lights", "Computing distribution"); /* count */ size_t num_lights = 0; size_t num_portals = 0; size_t num_background_lights = 0; size_t num_triangles = 0; bool background_mis = false; foreach (Light *light, scene->lights) { if (light->is_enabled) { num_lights++; } if (light->is_portal) { num_portals++; } } foreach (Object *object, scene->objects) { if (progress.get_cancel()) return; if (!object_usable_as_light(object)) { continue; } /* Count triangles. */ Mesh *mesh = static_cast(object->get_geometry()); size_t mesh_num_triangles = mesh->num_triangles(); for (size_t i = 0; i < mesh_num_triangles; i++) { int shader_index = mesh->get_shader()[i]; Shader *shader = (shader_index < mesh->get_used_shaders().size()) ? static_cast(mesh->get_used_shaders()[shader_index]) : scene->default_surface; if (shader->get_use_mis() && shader->has_surface_emission) { num_triangles++; } } } size_t num_distribution = num_triangles + num_lights; VLOG_INFO << "Total " << num_distribution << " of light distribution primitives."; /* emission area */ KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1); float totarea = 0.0f; /* triangles */ size_t offset = 0; int j = 0; foreach (Object *object, scene->objects) { if (progress.get_cancel()) return; if (!object_usable_as_light(object)) { j++; continue; } /* Sum area. */ Mesh *mesh = static_cast(object->get_geometry()); bool transform_applied = mesh->transform_applied; Transform tfm = object->get_tfm(); int object_id = j; int shader_flag = 0; if (!(object->get_visibility() & PATH_RAY_CAMERA)) { shader_flag |= SHADER_EXCLUDE_CAMERA; } if (!(object->get_visibility() & PATH_RAY_DIFFUSE)) { shader_flag |= SHADER_EXCLUDE_DIFFUSE; } if (!(object->get_visibility() & PATH_RAY_GLOSSY)) { shader_flag |= SHADER_EXCLUDE_GLOSSY; } if (!(object->get_visibility() & PATH_RAY_TRANSMIT)) { shader_flag |= SHADER_EXCLUDE_TRANSMIT; } if (!(object->get_visibility() & PATH_RAY_VOLUME_SCATTER)) { shader_flag |= SHADER_EXCLUDE_SCATTER; } if (!(object->get_is_shadow_catcher())) { shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER; } size_t mesh_num_triangles = mesh->num_triangles(); for (size_t i = 0; i < mesh_num_triangles; i++) { int shader_index = mesh->get_shader()[i]; Shader *shader = (shader_index < mesh->get_used_shaders().size()) ? static_cast(mesh->get_used_shaders()[shader_index]) : scene->default_surface; if (shader->get_use_mis() && shader->has_surface_emission) { distribution[offset].totarea = totarea; distribution[offset].prim = i + mesh->prim_offset; distribution[offset].mesh_light.shader_flag = shader_flag; distribution[offset].mesh_light.object_id = object_id; offset++; Mesh::Triangle t = mesh->get_triangle(i); if (!t.valid(&mesh->get_verts()[0])) { continue; } float3 p1 = mesh->get_verts()[t.v[0]]; float3 p2 = mesh->get_verts()[t.v[1]]; float3 p3 = mesh->get_verts()[t.v[2]]; if (!transform_applied) { p1 = transform_point(&tfm, p1); p2 = transform_point(&tfm, p2); p3 = transform_point(&tfm, p3); } totarea += triangle_area(p1, p2, p3); } } j++; } float trianglearea = totarea; /* point lights */ bool use_lamp_mis = false; int light_index = 0; if (num_lights > 0) { float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; foreach (Light *light, scene->lights) { if (!light->is_enabled) continue; distribution[offset].totarea = totarea; distribution[offset].prim = ~light_index; distribution[offset].lamp.pad = 1.0f; distribution[offset].lamp.size = light->size; totarea += lightarea; if (light->light_type == LIGHT_DISTANT) { use_lamp_mis |= (light->angle > 0.0f && light->use_mis); } else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { use_lamp_mis |= (light->size > 0.0f && light->use_mis); } else if (light->light_type == LIGHT_AREA) { use_lamp_mis |= light->use_mis; } else if (light->light_type == LIGHT_BACKGROUND) { num_background_lights++; background_mis |= light->use_mis; } light_index++; offset++; } } /* normalize cumulative distribution functions */ distribution[num_distribution].totarea = totarea; distribution[num_distribution].prim = 0.0f; distribution[num_distribution].lamp.pad = 0.0f; distribution[num_distribution].lamp.size = 0.0f; if (totarea > 0.0f) { for (size_t i = 0; i < num_distribution; i++) distribution[i].totarea /= totarea; distribution[num_distribution].totarea = 1.0f; } if (progress.get_cancel()) return; /* update device */ KernelIntegrator *kintegrator = &dscene->data.integrator; KernelBackground *kbackground = &dscene->data.background; KernelFilm *kfilm = &dscene->data.film; kintegrator->use_direct_light = (totarea > 0.0f); if (kintegrator->use_direct_light) { /* number of emissives */ kintegrator->num_distribution = num_distribution; /* precompute pdfs */ kintegrator->pdf_triangles = 0.0f; kintegrator->pdf_lights = 0.0f; /* sample one, with 0.5 probability of light or triangle */ kintegrator->num_all_lights = num_lights; if (trianglearea > 0.0f) { kintegrator->pdf_triangles = 1.0f / trianglearea; if (num_lights) kintegrator->pdf_triangles *= 0.5f; } if (num_lights) { kintegrator->pdf_lights = 1.0f / num_lights; if (trianglearea > 0.0f) kintegrator->pdf_lights *= 0.5f; } kintegrator->use_lamp_mis = use_lamp_mis; /* bit of an ugly hack to compensate for emitting triangles influencing * amount of samples we get for this pass */ kfilm->pass_shadow_scale = 1.0f; if (kintegrator->pdf_triangles != 0.0f) kfilm->pass_shadow_scale /= 0.5f; if (num_background_lights < num_lights) kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights; /* CDF */ dscene->light_distribution.copy_to_device(); /* Portals */ if (num_portals > 0) { kbackground->portal_offset = light_index; kbackground->num_portals = num_portals; kbackground->portal_weight = 1.0f; } else { kbackground->num_portals = 0; kbackground->portal_offset = 0; kbackground->portal_weight = 0.0f; } /* Map */ kbackground->map_weight = background_mis ? 1.0f : 0.0f; } else { dscene->light_distribution.free(); kintegrator->num_distribution = 0; kintegrator->num_all_lights = 0; kintegrator->pdf_triangles = 0.0f; kintegrator->pdf_lights = 0.0f; kintegrator->use_lamp_mis = false; kbackground->num_portals = 0; kbackground->portal_offset = 0; kbackground->portal_weight = 0.0f; kbackground->sun_weight = 0.0f; kbackground->map_weight = 0.0f; kfilm->pass_shadow_scale = 1.0f; } } static void background_cdf( int start, int end, int res_x, int res_y, const vector *pixels, float2 *cond_cdf) { int cdf_width = res_x + 1; /* Conditional CDFs (rows, U direction). */ for (int i = start; i < end; i++) { float sin_theta = sinf(M_PI_F * (i + 0.5f) / res_y); float3 env_color = (*pixels)[i * res_x]; float ave_luminance = average(env_color); cond_cdf[i * cdf_width].x = ave_luminance * sin_theta; cond_cdf[i * cdf_width].y = 0.0f; for (int j = 1; j < res_x; j++) { env_color = (*pixels)[i * res_x + j]; ave_luminance = average(env_color); cond_cdf[i * cdf_width + j].x = ave_luminance * sin_theta; cond_cdf[i * cdf_width + j].y = cond_cdf[i * cdf_width + j - 1].y + cond_cdf[i * cdf_width + j - 1].x / res_x; } const float cdf_total = cond_cdf[i * cdf_width + res_x - 1].y + cond_cdf[i * cdf_width + res_x - 1].x / res_x; /* stuff the total into the brightness value for the last entry, because * we are going to normalize the CDFs to 0.0 to 1.0 afterwards */ cond_cdf[i * cdf_width + res_x].x = cdf_total; if (cdf_total > 0.0f) { const float cdf_total_inv = 1.0f / cdf_total; for (int j = 1; j < res_x; j++) { cond_cdf[i * cdf_width + j].y *= cdf_total_inv; } } cond_cdf[i * cdf_width + res_x].y = 1.0f; } } void LightManager::device_update_background(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress) { KernelBackground *kbackground = &dscene->data.background; Light *background_light = NULL; /* find background light */ foreach (Light *light, scene->lights) { if (light->light_type == LIGHT_BACKGROUND) { background_light = light; break; } } /* no background light found, signal renderer to skip sampling */ if (!background_light || !background_light->is_enabled) { kbackground->map_res_x = 0; kbackground->map_res_y = 0; kbackground->map_weight = 0.0f; kbackground->sun_weight = 0.0f; kbackground->use_mis = (kbackground->portal_weight > 0.0f); return; } progress.set_status("Updating Lights", "Importance map"); assert(dscene->data.integrator.use_direct_light); int2 environment_res = make_int2(0, 0); Shader *shader = scene->background->get_shader(scene); int num_suns = 0; foreach (ShaderNode *node, shader->graph->nodes) { if (node->type == EnvironmentTextureNode::get_node_type()) { EnvironmentTextureNode *env = (EnvironmentTextureNode *)node; ImageMetaData metadata; if (!env->handle.empty()) { ImageMetaData metadata = env->handle.metadata(); environment_res.x = max(environment_res.x, (int)metadata.width); environment_res.y = max(environment_res.y, (int)metadata.height); } } if (node->type == SkyTextureNode::get_node_type()) { SkyTextureNode *sky = (SkyTextureNode *)node; if (sky->get_sky_type() == NODE_SKY_NISHITA && sky->get_sun_disc()) { /* Ensure that the input coordinates aren't transformed before they reach the node. * If that is the case, the logic used for sampling the sun's location does not work * and we have to fall back to map-based sampling. */ const ShaderInput *vec_in = sky->input("Vector"); if (vec_in && vec_in->link && vec_in->link->parent) { ShaderNode *vec_src = vec_in->link->parent; if ((vec_src->type != TextureCoordinateNode::get_node_type()) || (vec_in->link != vec_src->output("Generated"))) { environment_res.x = max(environment_res.x, 4096); environment_res.y = max(environment_res.y, 2048); continue; } } /* Determine sun direction from lat/long and texture mapping. */ float latitude = sky->get_sun_elevation(); float longitude = M_2PI_F - sky->get_sun_rotation() + M_PI_2_F; float3 sun_direction = make_float3( cosf(latitude) * cosf(longitude), cosf(latitude) * sinf(longitude), sinf(latitude)); Transform sky_transform = transform_inverse(sky->tex_mapping.compute_transform()); sun_direction = transform_direction(&sky_transform, sun_direction); /* Pack sun direction and size. */ float half_angle = sky->get_sun_size() * 0.5f; kbackground->sun = make_float4( sun_direction.x, sun_direction.y, sun_direction.z, half_angle); kbackground->sun_weight = 4.0f; environment_res.x = max(environment_res.x, 512); environment_res.y = max(environment_res.y, 256); num_suns++; } } } /* If there's more than one sun, fall back to map sampling instead. */ if (num_suns != 1) { kbackground->sun_weight = 0.0f; environment_res.x = max(environment_res.x, 4096); environment_res.y = max(environment_res.y, 2048); } /* Enable MIS for background sampling if any strategy is active. */ kbackground->use_mis = (kbackground->portal_weight + kbackground->map_weight + kbackground->sun_weight) > 0.0f; /* get the resolution from the light's size (we stuff it in there) */ int2 res = make_int2(background_light->map_resolution, background_light->map_resolution / 2); /* If the resolution isn't set manually, try to find an environment texture. */ if (res.x == 0) { res = environment_res; if (res.x > 0 && res.y > 0) { VLOG_INFO << "Automatically set World MIS resolution to " << res.x << " by " << res.y << "\n"; } } /* If it's still unknown, just use the default. */ if (res.x == 0 || res.y == 0) { res = make_int2(1024, 512); VLOG_INFO << "Setting World MIS resolution to default\n"; } kbackground->map_res_x = res.x; kbackground->map_res_y = res.y; vector pixels; shade_background_pixels(device, dscene, res.x, res.y, pixels, progress); if (progress.get_cancel()) return; /* build row distributions and column distribution for the infinite area environment light */ int cdf_width = res.x + 1; float2 *marg_cdf = dscene->light_background_marginal_cdf.alloc(res.y + 1); float2 *cond_cdf = dscene->light_background_conditional_cdf.alloc(cdf_width * res.y); double time_start = time_dt(); /* Create CDF in parallel. */ const int rows_per_task = divide_up(10240, res.x); parallel_for(blocked_range(0, res.y, rows_per_task), [&](const blocked_range &r) { background_cdf(r.begin(), r.end(), res.x, res.y, &pixels, cond_cdf); }); /* marginal CDFs (column, V direction, sum of rows) */ marg_cdf[0].x = cond_cdf[res.x].x; marg_cdf[0].y = 0.0f; for (int i = 1; i < res.y; i++) { marg_cdf[i].x = cond_cdf[i * cdf_width + res.x].x; marg_cdf[i].y = marg_cdf[i - 1].y + marg_cdf[i - 1].x / res.y; } float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y; marg_cdf[res.y].x = cdf_total; if (cdf_total > 0.0f) for (int i = 1; i < res.y; i++) marg_cdf[i].y /= cdf_total; marg_cdf[res.y].y = 1.0f; VLOG_WORK << "Background MIS build time " << time_dt() - time_start << "\n"; /* update device */ dscene->light_background_marginal_cdf.copy_to_device(); dscene->light_background_conditional_cdf.copy_to_device(); } void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *scene) { int num_scene_lights = scene->lights.size(); int num_lights = 0; foreach (Light *light, scene->lights) { if (light->is_enabled || light->is_portal) { num_lights++; } } KernelLight *klights = dscene->lights.alloc(num_lights); if (num_lights == 0) { VLOG_WORK << "No effective light, ignoring points update."; return; } int light_index = 0; foreach (Light *light, scene->lights) { if (!light->is_enabled) { continue; } float3 co = light->co; Shader *shader = (light->shader) ? light->shader : scene->default_light; int shader_id = scene->shader_manager->get_shader_id(shader); int max_bounces = light->max_bounces; float random = (float)light->random_id * (1.0f / (float)0xFFFFFFFF); if (!light->cast_shadow) shader_id &= ~SHADER_CAST_SHADOW; if (!light->use_camera) { shader_id |= SHADER_EXCLUDE_CAMERA; } if (!light->use_diffuse) { shader_id |= SHADER_EXCLUDE_DIFFUSE; } if (!light->use_glossy) { shader_id |= SHADER_EXCLUDE_GLOSSY; } if (!light->use_transmission) { shader_id |= SHADER_EXCLUDE_TRANSMIT; } if (!light->use_scatter) { shader_id |= SHADER_EXCLUDE_SCATTER; } if (!light->is_shadow_catcher) { shader_id |= SHADER_EXCLUDE_SHADOW_CATCHER; } klights[light_index].type = light->light_type; klights[light_index].strength[0] = light->strength.x; klights[light_index].strength[1] = light->strength.y; klights[light_index].strength[2] = light->strength.z; if (light->light_type == LIGHT_POINT) { shader_id &= ~SHADER_AREA_LIGHT; float radius = light->size; float invarea = (radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : 1.0f; if (light->use_mis && radius > 0.0f) shader_id |= SHADER_USE_MIS; klights[light_index].co[0] = co.x; klights[light_index].co[1] = co.y; klights[light_index].co[2] = co.z; klights[light_index].spot.radius = radius; klights[light_index].spot.invarea = invarea; } else if (light->light_type == LIGHT_DISTANT) { shader_id &= ~SHADER_AREA_LIGHT; float angle = light->angle / 2.0f; float radius = tanf(angle); float cosangle = cosf(angle); float area = M_PI_F * radius * radius; float invarea = (area > 0.0f) ? 1.0f / area : 1.0f; float3 dir = light->dir; dir = safe_normalize(dir); if (light->use_mis && area > 0.0f) shader_id |= SHADER_USE_MIS; klights[light_index].co[0] = dir.x; klights[light_index].co[1] = dir.y; klights[light_index].co[2] = dir.z; klights[light_index].distant.invarea = invarea; klights[light_index].distant.radius = radius; klights[light_index].distant.cosangle = cosangle; } else if (light->light_type == LIGHT_BACKGROUND) { uint visibility = scene->background->get_visibility(); shader_id &= ~SHADER_AREA_LIGHT; shader_id |= SHADER_USE_MIS; if (!(visibility & PATH_RAY_DIFFUSE)) { shader_id |= SHADER_EXCLUDE_DIFFUSE; } if (!(visibility & PATH_RAY_GLOSSY)) { shader_id |= SHADER_EXCLUDE_GLOSSY; } if (!(visibility & PATH_RAY_TRANSMIT)) { shader_id |= SHADER_EXCLUDE_TRANSMIT; } if (!(visibility & PATH_RAY_VOLUME_SCATTER)) { shader_id |= SHADER_EXCLUDE_SCATTER; } } else if (light->light_type == LIGHT_AREA) { float3 axisu = light->axisu * (light->sizeu * light->size); float3 axisv = light->axisv * (light->sizev * light->size); float area = len(axisu) * len(axisv); if (light->round) { area *= -M_PI_4_F; } float invarea = (area != 0.0f) ? 1.0f / area : 1.0f; float3 dir = light->dir; /* Convert from spread angle 0..180 to 90..0, clamping to a minimum * angle to avoid excessive noise. */ const float min_spread_angle = 1.0f * M_PI_F / 180.0f; const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle)); /* Normalization computed using: * integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */ const float tan_spread = tanf(spread_angle); const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread); dir = safe_normalize(dir); if (light->use_mis && area != 0.0f) shader_id |= SHADER_USE_MIS; klights[light_index].co[0] = co.x; klights[light_index].co[1] = co.y; klights[light_index].co[2] = co.z; klights[light_index].area.axisu[0] = axisu.x; klights[light_index].area.axisu[1] = axisu.y; klights[light_index].area.axisu[2] = axisu.z; klights[light_index].area.axisv[0] = axisv.x; klights[light_index].area.axisv[1] = axisv.y; klights[light_index].area.axisv[2] = axisv.z; klights[light_index].area.invarea = invarea; klights[light_index].area.dir[0] = dir.x; klights[light_index].area.dir[1] = dir.y; klights[light_index].area.dir[2] = dir.z; klights[light_index].area.tan_spread = tan_spread; klights[light_index].area.normalize_spread = normalize_spread; } else if (light->light_type == LIGHT_SPOT) { shader_id &= ~SHADER_AREA_LIGHT; float radius = light->size; float invarea = (radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : 1.0f; float spot_angle = cosf(light->spot_angle * 0.5f); float spot_smooth = (1.0f - spot_angle) * light->spot_smooth; float3 dir = light->dir; dir = safe_normalize(dir); if (light->use_mis && radius > 0.0f) shader_id |= SHADER_USE_MIS; klights[light_index].co[0] = co.x; klights[light_index].co[1] = co.y; klights[light_index].co[2] = co.z; klights[light_index].spot.radius = radius; klights[light_index].spot.invarea = invarea; klights[light_index].spot.spot_angle = spot_angle; klights[light_index].spot.spot_smooth = spot_smooth; klights[light_index].spot.dir[0] = dir.x; klights[light_index].spot.dir[1] = dir.y; klights[light_index].spot.dir[2] = dir.z; } klights[light_index].shader_id = shader_id; klights[light_index].max_bounces = max_bounces; klights[light_index].random = random; klights[light_index].use_caustics = light->use_caustics; klights[light_index].tfm = light->tfm; klights[light_index].itfm = transform_inverse(light->tfm); auto it = scene->lightgroups.find(light->lightgroup); if (it != scene->lightgroups.end()) { klights[light_index].lightgroup = it->second; } else { klights[light_index].lightgroup = LIGHTGROUP_NONE; } 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->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); if (light->round) { area *= -M_PI_4_F; } float invarea = (area != 0.0f) ? 1.0f / area : 1.0f; float3 dir = light->dir; dir = safe_normalize(dir); klights[light_index].co[0] = co.x; klights[light_index].co[1] = co.y; klights[light_index].co[2] = co.z; klights[light_index].area.axisu[0] = axisu.x; klights[light_index].area.axisu[1] = axisu.y; klights[light_index].area.axisu[2] = axisu.z; klights[light_index].area.axisv[0] = axisv.x; klights[light_index].area.axisv[1] = axisv.y; klights[light_index].area.axisv[2] = axisv.z; klights[light_index].area.invarea = invarea; klights[light_index].area.dir[0] = dir.x; klights[light_index].area.dir[1] = dir.y; klights[light_index].area.dir[2] = dir.z; klights[light_index].tfm = light->tfm; klights[light_index].itfm = transform_inverse(light->tfm); light_index++; } VLOG_INFO << "Number of lights sent to the device: " << light_index; VLOG_INFO << "Number of lights without contribution: " << num_scene_lights - light_index; dscene->lights.copy_to_device(); } void LightManager::device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress) { if (!need_update()) return; scoped_callback_timer timer([scene](double time) { if (scene->update_stats) { scene->update_stats->light.times.add_entry({"device_update", time}); } }); VLOG_INFO << "Total " << scene->lights.size() << " lights."; /* Detect which lights are enabled, also determines if we need to update the background. */ test_enabled_lights(scene); device_free(device, dscene, need_update_background); device_update_points(device, dscene, scene); if (progress.get_cancel()) return; device_update_distribution(device, dscene, scene, progress); if (progress.get_cancel()) return; if (need_update_background) { device_update_background(device, dscene, scene, progress); if (progress.get_cancel()) return; } device_update_ies(dscene); if (progress.get_cancel()) return; update_flags = UPDATE_NONE; need_update_background = false; } void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background) { dscene->light_distribution.free(); dscene->lights.free(); if (free_background) { dscene->light_background_marginal_cdf.free(); dscene->light_background_conditional_cdf.free(); } dscene->ies_lights.free(); } void LightManager::tag_update(Scene * /*scene*/, uint32_t flag) { update_flags |= flag; } bool LightManager::need_update() const { return update_flags != UPDATE_NONE; } int LightManager::add_ies_from_file(const string &filename) { string content; /* If the file can't be opened, call with an empty line */ if (filename.empty() || !path_read_text(filename.c_str(), content)) { content = "\n"; } return add_ies(content); } int LightManager::add_ies(const string &content) { uint hash = hash_string(content.c_str()); thread_scoped_lock ies_lock(ies_mutex); /* Check whether this IES already has a slot. */ size_t slot; for (slot = 0; slot < ies_slots.size(); slot++) { if (ies_slots[slot]->hash == hash) { ies_slots[slot]->users++; return slot; } } /* Try to find an empty slot for the new IES. */ for (slot = 0; slot < ies_slots.size(); slot++) { if (ies_slots[slot]->users == 0 && ies_slots[slot]->hash == 0) { break; } } /* If there's no free slot, add one. */ if (slot == ies_slots.size()) { ies_slots.push_back(new IESSlot()); } ies_slots[slot]->ies.load(content); ies_slots[slot]->users = 1; ies_slots[slot]->hash = hash; update_flags = UPDATE_ALL; need_update_background = true; return slot; } void LightManager::remove_ies(int slot) { thread_scoped_lock ies_lock(ies_mutex); if (slot < 0 || slot >= ies_slots.size()) { assert(false); return; } assert(ies_slots[slot]->users > 0); ies_slots[slot]->users--; /* If the slot has no more users, update the device to remove it. */ if (ies_slots[slot]->users == 0) { update_flags |= UPDATE_ALL; need_update_background = true; } } void LightManager::device_update_ies(DeviceScene *dscene) { /* Clear empty slots. */ foreach (IESSlot *slot, ies_slots) { if (slot->users == 0) { slot->hash = 0; slot->ies.clear(); } } /* Shrink the slot table by removing empty slots at the end. */ int slot_end; for (slot_end = ies_slots.size(); slot_end; slot_end--) { if (ies_slots[slot_end - 1]->users > 0) { /* If the preceding slot has users, we found the new end of the table. */ break; } else { /* The slot will be past the new end of the table, so free it. */ delete ies_slots[slot_end - 1]; } } ies_slots.resize(slot_end); if (ies_slots.size() > 0) { int packed_size = 0; foreach (IESSlot *slot, ies_slots) { packed_size += slot->ies.packed_size(); } /* ies_lights starts with an offset table that contains the offset of every slot, * or -1 if the slot is invalid. * Following that table, the packed valid IES lights are stored. */ float *data = dscene->ies_lights.alloc(ies_slots.size() + packed_size); int offset = ies_slots.size(); for (int i = 0; i < ies_slots.size(); i++) { int size = ies_slots[i]->ies.packed_size(); if (size > 0) { data[i] = __int_as_float(offset); ies_slots[i]->ies.pack(data + offset); offset += size; } else { data[i] = __int_as_float(-1); } } dscene->ies_lights.copy_to_device(); } } CCL_NAMESPACE_END