/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ #include "device/device.h" #include "scene/background.h" #include "scene/film.h" #include "scene/integrator.h" #include "scene/light.h" #include "scene/light_tree.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/hash.h" #include "util/log.h" #include "util/path.h" #include "util/progress.h" CCL_NAMESPACE_BEGIN static void shade_background_pixels(Device *device, DeviceScene *dscene, const int width, const 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 const u = (x + 0.5f) / width; float const 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, NodeType::NONE, Geometry::get_node_base_type()); 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_FLOAT(size, "Size", 0.0f); SOCKET_FLOAT(angle, "Angle", 0.0f); SOCKET_FLOAT(sizeu, "Size U", 1.0f); SOCKET_FLOAT(sizev, "Size V", 1.0f); SOCKET_BOOLEAN(ellipse, "Ellipse", false); SOCKET_FLOAT(spread, "Spread", M_PI_F); SOCKET_INT(map_resolution, "Map Resolution", 0); SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f); SOCKET_BOOLEAN(is_sphere, "Is Sphere", true); SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F); SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f); SOCKET_BOOLEAN(cast_shadow, "Cast Shadow", true); SOCKET_BOOLEAN(use_mis, "Use Mis", false); SOCKET_BOOLEAN(use_caustics, "Shadow Caustics", false); SOCKET_INT(max_bounces, "Max Bounces", 1024); SOCKET_BOOLEAN(is_portal, "Is Portal", false); SOCKET_BOOLEAN(is_enabled, "Is Enabled", true); SOCKET_BOOLEAN(normalize, "Normalize", true); return type; } Light::Light() : Geometry(get_node_type(), Geometry::LIGHT) { dereference_all_used_nodes(); } void Light::tag_update(Scene *scene) { if (is_modified()) { scene->light_manager->tag_update(scene, LightManager::LIGHT_MODIFIED); } else { for (const Node *node : get_used_shaders()) { if (node->is_modified()) { /* If the light shader is modified, the number of lights in the scene might change. * Tag light manager for update. */ scene->light_manager->tag_update(scene, LightManager::LIGHT_MODIFIED); break; } } } } bool Light::has_contribution(const Scene *scene, const Object *object) { if (strength == zero_float3()) { return false; } if (is_portal) { return false; } if (light_type == LIGHT_BACKGROUND) { /* Will be determined after finishing processing all the lights. */ return true; } if (light_type == LIGHT_AREA) { if ((get_sizeu() * get_sizev() * get_size() == 0.0f) || is_zero(transform_get_column(&object->get_tfm(), 0)) || is_zero(transform_get_column(&object->get_tfm(), 1))) { /* Area light with a size of zero does not contribute to the scene. */ return false; } } const Shader *effective_shader = (get_shader()) ? get_shader() : scene->default_light; return !is_zero(effective_shader->emission_estimate); } Shader *Light::get_shader() const { return (used_shaders.empty()) ? nullptr : static_cast(used_shaders[0]); } void Light::compute_bounds() { /* To be implemented when this becomes actual geometry. */ } void Light::apply_transform(const Transform & /*tfm*/, const bool /*apply_to_motion*/) { /* To be implemented when this becomes actual geometry. */ } void Light::get_uv_tiles(ustring /*map*/, unordered_set & /*tiles*/) { /* To be implemented when this becomes actual geometry. */ } PrimitiveType Light::primitive_type() const { return PRIMITIVE_LAMP; } float Light::area(const Transform &tfm) const { if (light_type == LIGHT_POINT || light_type == LIGHT_SPOT) { /* Sphere area. */ const float area = 4.0f * M_PI_F * size * size; return (area == 0.0f) ? 4.0f : area; } if (light_type == LIGHT_AREA) { /* Rectangle area. */ const float3 axisu = transform_get_column(&tfm, 0); const float3 axisv = transform_get_column(&tfm, 1); float area = len(axisu * sizeu * size) * len(axisv * sizev * size); if (ellipse) { area *= M_PI_4_F; } return area; } if (light_type == LIGHT_DISTANT) { /* Sun disk area. */ const float half_angle = angle / 2.0f; return (half_angle > 0.0f) ? M_PI_F * sqr(sinf(half_angle)) : 1.0f; } return 1.0f; } /* Light Manager */ LightManager::LightManager() { update_flags = UPDATE_ALL; need_update_background = true; last_background_enabled = false; last_background_resolution = 0; } bool LightManager::has_background_light(Scene *scene) { for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); 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). */ vector background_lights; size_t num_lights = 0; bool has_portal = false; for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); light->is_enabled = light->has_contribution(scene, object); has_portal |= light->is_portal; if (light->light_type == LIGHT_BACKGROUND) { background_lights.push_back(light); } num_lights += light->is_enabled; } LOG_INFO << "Total " << num_lights << " lights."; bool background_enabled = false; int background_resolution = 0; if (!background_lights.empty()) { /* 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); for (Light *light : background_lights) { light->is_enabled = has_portal || (light->use_mis && shader->has_surface_spatial_varying); if (light->is_enabled) { background_enabled = true; background_resolution = light->map_resolution; } } if (!background_enabled) { LOG_INFO << "Background MIS has been disabled."; } } 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; } } static uint light_object_shader_flags(Object *object) { const uint visibility = object->get_visibility(); uint shader_flag = 0; if (!(visibility & PATH_RAY_CAMERA)) { shader_flag |= SHADER_EXCLUDE_CAMERA; } if (!(visibility & PATH_RAY_DIFFUSE)) { shader_flag |= SHADER_EXCLUDE_DIFFUSE; } if (!(visibility & PATH_RAY_GLOSSY)) { shader_flag |= SHADER_EXCLUDE_GLOSSY; } if (!(visibility & PATH_RAY_TRANSMIT)) { shader_flag |= SHADER_EXCLUDE_TRANSMIT; } if (!(visibility & PATH_RAY_VOLUME_SCATTER)) { shader_flag |= SHADER_EXCLUDE_SCATTER; } if (!(object->get_is_shadow_catcher())) { shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER; } return shader_flag; } void LightManager::device_update_distribution(Device * /*unused*/, DeviceScene *dscene, Scene *scene, Progress &progress) { KernelIntegrator *kintegrator = &dscene->data.integrator; if (kintegrator->use_light_tree) { dscene->light_distribution.free(); return; } /* Update CDF over lights. */ progress.set_status("Updating Lights", "Computing distribution"); /* Counts emissive triangles in the scene. */ size_t num_triangles = 0; const int num_lights = kintegrator->num_lights; const size_t max_num_triangles = std::numeric_limits::max() - 1 - kintegrator->num_lights; for (Object *object : scene->objects) { if (progress.get_cancel()) { return; } if (!object->usable_as_light()) { continue; } /* Count emissive triangles. */ Mesh *mesh = static_cast(object->get_geometry()); const int mesh_num_triangles = static_cast(mesh->num_triangles()); for (int i = 0; i < mesh_num_triangles; i++) { const 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->emission_sampling != EMISSION_SAMPLING_NONE) { num_triangles++; } } if (num_triangles > max_num_triangles) { progress.set_error( "Number of emissive triangles exceeds the limit, consider using Light Tree or disabling " "Emission Sampling on some emissive materials"); } } const size_t num_distribution = num_triangles + num_lights; /* Distribution size. */ kintegrator->num_distribution = num_distribution; LOG_INFO << "Use light distribution with " << num_distribution << " emitters."; /* Emission area. */ KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1); float totarea = 0.0f; /* Triangles. */ size_t offset = 0; for (Object *object : scene->objects) { if (progress.get_cancel()) { return; } if (!object->usable_as_light()) { continue; } /* Sum area. */ Mesh *mesh = static_cast(object->get_geometry()); const bool transform_applied = mesh->transform_applied; const Transform tfm = object->get_tfm(); const int shader_flag = light_object_shader_flags(object); const size_t mesh_num_triangles = mesh->num_triangles(); for (size_t i = 0; i < mesh_num_triangles; i++) { const 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->emission_sampling != EMISSION_SAMPLING_NONE) { distribution[offset].totarea = totarea; distribution[offset].prim = i + mesh->prim_offset; distribution[offset].shader_flag = shader_flag; distribution[offset].object_id = object->index; offset++; const Mesh::Triangle t = mesh->get_triangle(i); if (!t.valid(mesh->get_verts().data())) { 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); } } } const float trianglearea = totarea; /* Lights. */ int light_index = 0; if (num_lights > 0) { const float lightarea = (totarea > 0.0f) ? totarea / num_lights : 1.0f; for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); if (!light->is_enabled) { continue; } distribution[offset].totarea = totarea; distribution[offset].prim = ~light_index; distribution[offset].object_id = object->index; distribution[offset].shader_flag = 0; totarea += lightarea; light_index++; offset++; } } /* normalize cumulative distribution functions */ distribution[num_distribution].totarea = totarea; distribution[num_distribution].prim = 0; distribution[num_distribution].object_id = OBJECT_NONE; distribution[num_distribution].shader_flag = 0; 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 integrator state. */ kintegrator->use_direct_light = (totarea > 0.0f); /* Precompute pdfs for distribution sampling. * Sample one, with 0.5 probability of light or triangle. */ kintegrator->distribution_pdf_triangles = 0.0f; kintegrator->distribution_pdf_lights = 0.0f; if (trianglearea > 0.0f) { kintegrator->distribution_pdf_triangles = 1.0f / trianglearea; if (num_lights) { kintegrator->distribution_pdf_triangles *= 0.5f; } } if (num_lights) { kintegrator->distribution_pdf_lights = 1.0f / num_lights; if (trianglearea > 0.0f) { kintegrator->distribution_pdf_lights *= 0.5f; } } /* Copy distribution to device. */ dscene->light_distribution.copy_to_device(); } /* Arguments for functions to convert the light tree to the kernel representation. */ struct LightTreeFlatten { const Scene *scene; const LightTreeEmitter *emitters; const uint *object_lookup_offset; uint *light_array; uint *mesh_array; uint *triangle_array; /* Map from instance node to its node index. */ std::unordered_map instances; }; static void light_tree_node_copy_to_device(KernelLightTreeNode &knode, const LightTreeNode &node, const int left_child, const int right_child) { /* Convert node to kernel representation. */ knode.energy = node.measure.energy; knode.bbox.min = node.measure.bbox.min; knode.bbox.max = node.measure.bbox.max; knode.bcone.axis = node.measure.bcone.axis; knode.bcone.theta_o = node.measure.bcone.theta_o; knode.bcone.theta_e = node.measure.bcone.theta_e; knode.bit_trail = node.bit_trail; knode.bit_skip = 0; knode.type = static_cast(node.type); if (node.is_leaf() || node.is_distant()) { knode.num_emitters = node.get_leaf().num_emitters; knode.leaf.first_emitter = node.get_leaf().first_emitter_index; } else if (node.is_inner()) { knode.num_emitters = -1; knode.inner.left_child = left_child; knode.inner.right_child = right_child; } } static int light_tree_flatten(LightTreeFlatten &flatten, const LightTreeNode *node, KernelLightTreeNode *knodes, KernelLightTreeEmitter *kemitters, int &next_node_index); static void light_tree_leaf_emitters_copy_and_flatten(LightTreeFlatten &flatten, const LightTreeNode &node, KernelLightTreeNode *knodes, KernelLightTreeEmitter *kemitters, int &next_node_index) { /* Convert emitters to kernel representation. */ for (int i = 0; i < node.get_leaf().num_emitters; i++) { const int emitter_index = i + node.get_leaf().first_emitter_index; const LightTreeEmitter &emitter = flatten.emitters[emitter_index]; KernelLightTreeEmitter &kemitter = kemitters[emitter_index]; kemitter.energy = emitter.measure.energy; kemitter.theta_o = emitter.measure.bcone.theta_o; kemitter.theta_e = emitter.measure.bcone.theta_e; if (emitter.is_triangle()) { /* Triangle. */ Object *object = flatten.scene->objects[emitter.object_id]; Mesh *mesh = static_cast(object->get_geometry()); Shader *shader = static_cast( mesh->get_used_shaders()[mesh->get_shader()[emitter.prim_id]]); kemitter.triangle.id = emitter.prim_id + mesh->prim_offset; kemitter.shader_flag = light_object_shader_flags(object); kemitter.object_id = emitter.object_id; kemitter.triangle.emission_sampling = shader->emission_sampling; flatten.triangle_array[emitter.prim_id + flatten.object_lookup_offset[emitter.object_id]] = emitter_index; } else if (emitter.is_light()) { /* Light object. */ kemitter.light.id = emitter.light_id; kemitter.shader_flag = 0; kemitter.object_id = emitter.object_id; flatten.light_array[~emitter.light_id] = emitter_index; } else { /* Mesh instance. */ assert(emitter.is_mesh()); kemitter.mesh.object_id = emitter.object_id; kemitter.shader_flag = 0; kemitter.object_id = OBJECT_NONE; flatten.mesh_array[emitter.object_id] = emitter_index; /* Create instance node. One instance node will be the same as the * reference node, and for that it will recursively build the subtree. */ LightTreeNode *instance_node = emitter.root.get(); LightTreeNode *reference_node = instance_node->get_reference(); auto map_it = flatten.instances.find(reference_node); if (map_it == flatten.instances.end()) { if (instance_node != reference_node) { /* Flatten the node with the subtree first so the subsequent instances know the index. */ std::swap(instance_node->type, reference_node->type); std::swap(instance_node->variant_type, reference_node->variant_type); } instance_node->type &= ~LIGHT_TREE_INSTANCE; } kemitter.mesh.node_id = light_tree_flatten( flatten, instance_node, knodes, kemitters, next_node_index); KernelLightTreeNode &kinstance_node = knodes[kemitter.mesh.node_id]; kinstance_node.bit_trail = node.bit_trail; if (map_it != flatten.instances.end()) { kinstance_node.instance.reference = map_it->second; } else { flatten.instances[reference_node] = kemitter.mesh.node_id; } } kemitter.bit_trail = node.bit_trail; } } static int light_tree_flatten(LightTreeFlatten &flatten, const LightTreeNode *node, KernelLightTreeNode *knodes, KernelLightTreeEmitter *kemitters, int &next_node_index) { /* Convert both inner nodes and primitives to device representation. */ const int node_index = next_node_index++; int left_child = -1; int right_child = -1; if (node->is_leaf() || node->is_distant()) { light_tree_leaf_emitters_copy_and_flatten(flatten, *node, knodes, kemitters, next_node_index); } else if (node->is_inner()) { left_child = light_tree_flatten(flatten, node->get_inner().children[LightTree::left].get(), knodes, kemitters, next_node_index); right_child = light_tree_flatten(flatten, node->get_inner().children[LightTree::right].get(), knodes, kemitters, next_node_index); } else { /* Instance node that is not inner or leaf, but just references another. */ assert(node->is_instance()); } light_tree_node_copy_to_device(knodes[node_index], *node, left_child, right_child); return node_index; } static void light_tree_emitters_copy_and_flatten(LightTreeFlatten &flatten, const LightTreeNode *node, KernelLightTreeNode *knodes, KernelLightTreeEmitter *kemitters, int &next_node_index) { /* Convert only emitters to device representation. */ if (node->is_leaf() || node->is_distant()) { light_tree_leaf_emitters_copy_and_flatten(flatten, *node, knodes, kemitters, next_node_index); } else { assert(node->is_inner()); light_tree_emitters_copy_and_flatten(flatten, node->get_inner().children[LightTree::left].get(), knodes, kemitters, next_node_index); light_tree_emitters_copy_and_flatten(flatten, node->get_inner().children[LightTree::right].get(), knodes, kemitters, next_node_index); } } static std::pair light_tree_specialize_nodes_flatten( const LightTreeFlatten &flatten, LightTreeNode *node, const uint64_t light_link_mask, const int depth, vector &knodes, int &next_node_index, bool can_share = true) { assert(!node->is_instance()); /* Convert inner nodes to device representation, specialized for light linking. */ int node_index; int left_child = -1; int right_child = -1; LightTreeNode new_node(LightTreeMeasure::empty, node->bit_trail); if (depth == 0 && !(node->light_link.set_membership & light_link_mask)) { /* Ensure there is always a root node. */ node_index = next_node_index++; new_node.make_leaf(-1, 0); } else if (can_share && node->light_link.shareable && node->light_link.shared_node_index != -1) { /* Share subtree already built for another light link set. */ return std::make_pair(node->light_link.shared_node_index, node->measure); } else if (node->is_leaf() || node->is_distant()) { /* Specialize leaf node. */ node_index = next_node_index++; int first_emitter = -1; int num_emitters = 0; for (int i = 0; i < node->get_leaf().num_emitters; i++) { const LightTreeEmitter &emitter = flatten.emitters[node->get_leaf().first_emitter_index + i]; if (emitter.light_set_membership & light_link_mask) { /* Assumes emitters are consecutive due to LighTree::sort_leaf. */ if (first_emitter == -1) { first_emitter = node->get_leaf().first_emitter_index + i; } num_emitters++; new_node.measure.add(emitter.measure); } } assert(first_emitter != -1); /* Preserve the type of the node, so that the kernel can do proper decision when sampling * node with multiple distant lights in it. */ if (node->is_leaf()) { new_node.make_leaf(first_emitter, num_emitters); } else { new_node.make_distant(first_emitter, num_emitters); } } else { assert(node->is_inner()); assert(new_node.is_inner()); /* Specialize inner node. */ LightTreeNode *left_node = node->get_inner().children[LightTree::left].get(); LightTreeNode *right_node = node->get_inner().children[LightTree::right].get(); /* Skip nodes that have only one child. We have a single bit trail for each * primitive, bit_skip is incremented in the child node to skip the bit for * this parent node. */ LightTreeNode *only_node = nullptr; if (!(left_node->light_link.set_membership & light_link_mask)) { only_node = right_node; } else if (!(right_node->light_link.set_membership & light_link_mask)) { only_node = left_node; } if (only_node) { /* Can not share the node as its bit_skip will be modified. * Also don't store shareable_index so other branches of the tree that do not skip any nodes * do not share node created here. */ const auto [only_index, only_measure] = light_tree_specialize_nodes_flatten( flatten, only_node, light_link_mask, depth + 1, knodes, next_node_index, false); assert(only_index != -1); knodes[only_index].bit_skip++; return std::make_pair(only_index, only_measure); } /* Create inner node. */ node_index = next_node_index++; const auto [left_index, left_measure] = light_tree_specialize_nodes_flatten( flatten, left_node, light_link_mask, depth + 1, knodes, next_node_index); const auto [right_index, right_measure] = light_tree_specialize_nodes_flatten( flatten, right_node, light_link_mask, depth + 1, knodes, next_node_index); new_node.measure = left_measure; new_node.measure.add(right_measure); left_child = left_index; right_child = right_index; } /* Convert to kernel node. */ if (knodes.size() <= node_index) { knodes.resize(node_index + 1); } light_tree_node_copy_to_device(knodes[node_index], new_node, left_child, right_child); if (node->light_link.shareable) { node->light_link.shared_node_index = node_index; } return std::make_pair(node_index, new_node.measure); } void LightManager::device_update_tree(Device * /*unused*/, DeviceScene *dscene, Scene *scene, Progress &progress) { KernelIntegrator *kintegrator = &dscene->data.integrator; if (!kintegrator->use_light_tree) { return; } /* Update light tree. */ progress.set_status("Updating Lights", "Computing tree"); /* TODO: For now, we'll start with a smaller number of max lights in a node. * More benchmarking is needed to determine what number works best. */ LightTree light_tree(scene, dscene, progress, 8); LightTreeNode *root = light_tree.build(scene, dscene); if (progress.get_cancel()) { return; } /* Create arguments for recursive tree flatten. */ LightTreeFlatten flatten; flatten.scene = scene; flatten.emitters = light_tree.get_emitters(); flatten.object_lookup_offset = dscene->object_lookup_offset.data(); /* We want to create separate arrays corresponding to triangles and lights, * which will be used to index back into the light tree for PDF calculations. */ flatten.light_array = dscene->light_to_tree.alloc(kintegrator->num_lights); flatten.mesh_array = dscene->object_to_tree.alloc(scene->objects.size()); flatten.triangle_array = dscene->triangle_to_tree.alloc(light_tree.num_triangles); /* Allocate emitters */ const size_t num_emitters = light_tree.num_emitters(); KernelLightTreeEmitter *kemitters = dscene->light_tree_emitters.alloc(num_emitters); /* Update integrator state. */ kintegrator->use_direct_light = num_emitters > 0; /* Test if light linking is used. */ const bool use_light_linking = root && (light_tree.light_link_receiver_used != 1); KernelLightLinkSet *klight_link_sets = dscene->data.light_link_sets; memset(klight_link_sets, 0, sizeof(dscene->data.light_link_sets)); LOG_INFO << "Use light tree with " << num_emitters << " emitters and " << light_tree.num_nodes << " nodes."; if (!use_light_linking) { /* Regular light tree without linking. */ KernelLightTreeNode *knodes = dscene->light_tree_nodes.alloc(light_tree.num_nodes); if (root) { int next_node_index = 0; light_tree_flatten(flatten, root, knodes, kemitters, next_node_index); } } else { int next_node_index = 0; vector light_link_nodes; /* Write primitives, and any subtrees for instances. */ if (root) { /* Reserve enough size of all instance subtrees, then shrink back to * actual number of nodes used. */ light_link_nodes.resize(light_tree.num_nodes); light_tree_emitters_copy_and_flatten( flatten, root, light_link_nodes.data(), kemitters, next_node_index); light_link_nodes.resize(next_node_index); } /* Specialized light trees for linking. */ for (uint64_t tree_index = 0; tree_index < LIGHT_LINK_SET_MAX; tree_index++) { const uint64_t tree_mask = uint64_t(1) << tree_index; if (!(light_tree.light_link_receiver_used & tree_mask)) { continue; } if (root) { klight_link_sets[tree_index].light_tree_root = light_tree_specialize_nodes_flatten( flatten, root, tree_mask, 0, light_link_nodes, next_node_index) .first; } } /* Allocate and copy nodes into device array. */ KernelLightTreeNode *knodes = dscene->light_tree_nodes.alloc(light_link_nodes.size()); memcpy(knodes, light_link_nodes.data(), light_link_nodes.size() * sizeof(*knodes)); LOG_INFO << "Specialized light tree for light linking, with " << light_link_nodes.size() - light_tree.num_nodes << " additional nodes."; } /* Copy arrays to device. */ dscene->light_tree_nodes.copy_to_device(); dscene->light_tree_emitters.copy_to_device(); dscene->light_to_tree.copy_to_device(); dscene->object_to_tree.copy_to_device(); dscene->object_lookup_offset.copy_to_device(); dscene->triangle_to_tree.copy_to_device(); } static void background_cdf(int start, const int end, const int res_x, const int res_y, const vector *pixels, float2 *cond_cdf) { const int cdf_width = res_x + 1; /* Conditional CDFs (rows, U direction). */ for (int i = start; i < end; i++) { const float sin_theta = sinf(M_PI_F * (i + 0.5f) / res_y); float3 env_color = (*pixels)[i * res_x]; float ave_luminance = average(fabs(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(fabs(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) { KernelIntegrator *kintegrator = &dscene->data.integrator; KernelBackground *kbackground = &dscene->data.background; Light *background_light = nullptr; bool background_mis = false; /* find background light */ for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); if (light->light_type == LIGHT_BACKGROUND && light->is_enabled) { background_light = light; background_mis |= light->use_mis; } } kbackground->portal_weight = kintegrator->num_portals > 0 ? 1.0f : 0.0f; kbackground->map_weight = background_mis ? 1.0f : 0.0f; kbackground->sun_weight = 0.0f; /* 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->use_mis = (kbackground->portal_weight > 0.0f); return; } progress.set_status("Updating Lights", "Importance map"); int2 environment_res = make_int2(0, 0); Shader *shader = scene->background->get_shader(scene); int num_suns = 0; float sun_average_radiance = 0.0f; for (ShaderNode *node : shader->graph->nodes) { if (node->type == EnvironmentTextureNode::get_node_type()) { EnvironmentTextureNode *env = (EnvironmentTextureNode *)node; if (!env->handle.empty()) { const 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_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. */ const float latitude = sky->get_sun_elevation(); const float longitude = sky->get_sun_rotation() + M_PI_2_F; float3 sun_direction = make_float3( cosf(latitude) * cosf(longitude), cosf(latitude) * sinf(longitude), sinf(latitude)); const Transform sky_transform = transform_inverse(sky->tex_mapping.compute_transform()); sun_direction = transform_direction(&sky_transform, sun_direction); /* Pack Sun direction and size. */ const float half_angle = sky->get_sun_size() * 0.5f; kbackground->sun = make_float4(sun_direction, half_angle); /* Empirical value */ kbackground->sun_weight = 4.0f; sun_average_radiance = sky->get_sun_average_radiance(); 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. */ kbackground->use_sun_guiding = (num_suns == 1); if (!kbackground->use_sun_guiding) { 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) { LOG_INFO << "Automatically set World MIS resolution to " << res.x << " by " << res.y; } } /* If it's still unknown, just use the default. */ if (res.x == 0 || res.y == 0) { res = make_int2(1024, 512); LOG_INFO << "Setting World MIS resolution to default"; } 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 */ const 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); const 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; } const float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y; marg_cdf[res.y].x = cdf_total; const float map_average_radiance = cdf_total * M_PI_2_F; if (sun_average_radiance > 0.0f) { /* The weighting here is just a heuristic that was empirically determined. * The Sun's average radiance is much higher than the map's average radiance, * but we don't want to weight the background light too much because * visibility is not accounted for anyway. */ background_light->set_average_radiance(0.8f * map_average_radiance + 0.2f * sun_average_radiance); } else { background_light->set_average_radiance(map_average_radiance); } 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; LOG_DEBUG << "Background MIS build time " << time_dt() - time_start; /* update device */ dscene->light_background_marginal_cdf.copy_to_device(); dscene->light_background_conditional_cdf.copy_to_device(); } void LightManager::device_update_lights(DeviceScene *dscene, Scene *scene) { /* Counts lights in the scene. */ size_t num_lights = 0; size_t num_portals = 0; size_t num_background_lights = 0; size_t num_distant_lights = 0; bool use_light_mis = false; for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); if (light->is_enabled) { num_lights++; if (light->light_type == LIGHT_DISTANT) { num_distant_lights++; } else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { use_light_mis |= (light->size > 0.0f && light->use_mis); } else if (light->light_type == LIGHT_AREA) { use_light_mis |= light->use_mis; } else if (light->light_type == LIGHT_BACKGROUND) { num_distant_lights++; num_background_lights++; } } if (light->is_portal) { num_portals++; } } /* Update integrator settings. */ KernelIntegrator *kintegrator = &dscene->data.integrator; kintegrator->use_light_tree = scene->integrator->get_use_light_tree(); kintegrator->num_lights = num_lights; kintegrator->num_distant_lights = num_distant_lights; kintegrator->num_background_lights = num_background_lights; kintegrator->use_light_mis = use_light_mis; kintegrator->num_portals = num_portals; kintegrator->portal_offset = num_lights; /* Create KernelLight for every portal and enabled light in the scene. */ KernelLight *klights = dscene->lights.alloc(num_lights + num_portals); int light_index = 0; int portal_index = num_lights; for (Object *object : scene->objects) { if (!object->get_geometry()->is_light()) { continue; } Light *light = static_cast(object->get_geometry()); const float3 axisu = transform_get_column(&object->get_tfm(), 0); const float3 axisv = transform_get_column(&object->get_tfm(), 1); const float3 dir = -transform_get_column(&object->get_tfm(), 2); const float3 co = transform_get_column(&object->get_tfm(), 3); /* Consider moving portals update to their own function * keeping this one more manageable. */ if (light->is_portal) { assert(light->light_type == LIGHT_AREA); const float3 extentu = axisu * (light->sizeu * light->size); const float3 extentv = axisv * (light->sizev * light->size); float len_u; float len_v; const float3 axis_u = normalize_len(extentu, &len_u); const float3 axis_v = normalize_len(extentv, &len_v); const float area = light->area(object->get_tfm()); float invarea = (area != 0.0f) ? 1.0f / area : 1.0f; if (light->ellipse) { /* Negative inverse area indicates ellipse. */ invarea = -invarea; } klights[portal_index].co = co; klights[portal_index].area.axis_u = axis_u; klights[portal_index].area.len_u = len_u; klights[portal_index].area.axis_v = axis_v; klights[portal_index].area.len_v = len_v; klights[portal_index].area.invarea = invarea; klights[portal_index].area.dir = safe_normalize(dir); klights[portal_index].object_id = object->index; portal_index++; continue; } if (!light->is_enabled) { continue; } Shader *shader = (light->get_shader()) ? light->get_shader() : scene->default_light; int shader_id = scene->shader_manager->get_shader_id(shader); if (!light->cast_shadow) { shader_id &= ~SHADER_CAST_SHADOW; } shader_id |= light_object_shader_flags(object); 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 || light->light_type == LIGHT_SPOT) { shader_id &= ~SHADER_AREA_LIGHT; const float radius = light->size; const float invarea = (light->normalize) ? 1.0f / light->area(object->get_tfm()) : 1.0f; /* Convert radiant flux to radiance or radiant intensity. */ const float eval_fac = invarea * M_1_PI_F; if (light->use_mis && radius > 0.0f) { shader_id |= SHADER_USE_MIS; } klights[light_index].co = co; klights[light_index].spot.radius = radius; klights[light_index].spot.eval_fac = eval_fac; klights[light_index].spot.is_sphere = light->get_is_sphere() && radius != 0.0f; } else if (light->light_type == LIGHT_DISTANT) { shader_id &= ~SHADER_AREA_LIGHT; const float angle = light->angle / 2.0f; if (light->use_mis && angle > 0.0f) { shader_id |= SHADER_USE_MIS; } const float one_minus_cosangle = 2.0f * sqr(sinf(0.5f * angle)); const float pdf = (angle > 0.0f) ? (M_1_2PI_F / one_minus_cosangle) : 1.0f; klights[light_index].co = safe_normalize(dir); klights[light_index].distant.angle = angle; klights[light_index].distant.one_minus_cosangle = one_minus_cosangle; klights[light_index].distant.pdf = pdf; klights[light_index].distant.eval_fac = (light->normalize) ? 1.0f / light->area(object->get_tfm()) : 1.0f; klights[light_index].distant.half_inv_sin_half_angle = (angle == 0.0f) ? 0.0f : 0.5f / sinf(0.5f * angle); } else if (light->light_type == LIGHT_BACKGROUND) { const uint visibility = scene->background->get_visibility(); dscene->data.background.light_index = light_index; 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) { const float light_size = light->size; const float3 extentu = axisu * (light->sizeu * light_size); const float3 extentv = axisv * (light->sizev * light_size); float len_u; float len_v; const float3 axis_u = normalize_len(extentu, &len_u); const float3 axis_v = normalize_len(extentv, &len_v); const float area = light->area(object->get_tfm()); float invarea = light->normalize ? 1.0f / area : 1.0f; if (light->ellipse) { /* Negative inverse area indicates ellipse. */ invarea = -invarea; } const float half_spread = 0.5f * fmaxf(light->spread, 0.0f); const float tan_half_spread = light->spread == M_PI_F ? FLT_MAX : tanf(half_spread); /* Normalization computed using: * integrate cos(x) * (1 - tan(x) / tan(a)) * sin(x) from x = 0 to a, a being half_spread. * Divided by tan_half_spread to simplify the attenuation computation in `area.h`. */ /* Using third-order Taylor expansion at small angles for better accuracy. */ const float normalize_spread = (half_spread > 0.0f) ? (half_spread > 0.05f ? 1.0f / (tan_half_spread - half_spread) : 3.0f / powf(half_spread, 3.0f)) : FLT_MAX; if (light->use_mis && area != 0.0f && light->spread > 0.0f) { shader_id |= SHADER_USE_MIS; } klights[light_index].co = co; klights[light_index].area.axis_u = axis_u; klights[light_index].area.len_u = len_u; klights[light_index].area.axis_v = axis_v; klights[light_index].area.len_v = len_v; klights[light_index].area.invarea = invarea; klights[light_index].area.dir = safe_normalize(dir); klights[light_index].area.tan_half_spread = tan_half_spread; klights[light_index].area.normalize_spread = normalize_spread; } if (light->light_type == LIGHT_SPOT) { const float cos_half_spot_angle = cosf(light->spot_angle * 0.5f); const float spot_smooth = 1.0f / ((1.0f - cos_half_spot_angle) * light->spot_smooth); const float tan_half_spot_angle = tanf(light->spot_angle * 0.5f); const float len_w_sq = len_squared(dir); const float len_u_sq = len_squared(axisu); const float len_v_sq = len_squared(axisv); const float tan_sq = sqr(tan_half_spot_angle); klights[light_index].spot.dir = safe_normalize(dir); klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle; klights[light_index].spot.half_cot_half_spot_angle = 0.5f / tan_half_spot_angle; klights[light_index].spot.spot_smooth = spot_smooth; /* Choose the angle which spans a larger cone. */ klights[light_index].spot.cos_half_larger_spread = inversesqrtf( 1.0f + tan_sq * fmaxf(len_u_sq, len_v_sq) / len_w_sq); /* radius / sin(half_angle_small) */ klights[light_index].spot.ray_segment_dp = light->size * sqrtf(1.0f + len_w_sq / (tan_sq * fminf(len_u_sq, len_v_sq))); } klights[light_index].shader_id = shader_id; klights[light_index].object_id = object->index; klights[light_index].max_bounces = light->max_bounces; klights[light_index].use_caustics = light->use_caustics; light_index++; } LOG_INFO << "Number of lights sent to the device: " << num_lights; dscene->lights.copy_to_device(); } void LightManager::device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress) { if (!need_update()) { return; } const scoped_callback_timer timer([scene](double time) { if (scene->update_stats) { scene->update_stats->light.times.add_entry({"device_update", time}); } }); /* 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_lights(dscene, scene); if (progress.get_cancel()) { return; } if (need_update_background) { device_update_background(device, dscene, scene, progress); if (progress.get_cancel()) { return; } } device_update_distribution(device, dscene, scene, progress); if (progress.get_cancel()) { return; } device_update_tree(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 * /*unused*/, DeviceScene *dscene, const bool free_background) { dscene->light_tree_nodes.free(); dscene->light_tree_emitters.free(); dscene->light_to_tree.free(); dscene->object_to_tree.free(); dscene->object_lookup_offset.free(); dscene->triangle_to_tree.free(); 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, content)) { content = "\n"; } return add_ies(content); } int LightManager::add_ies(const string &content) { const uint hash = hash_string(content.c_str()); const 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(make_unique()); } 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(const int slot) { const 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. */ for (const unique_ptr &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; } } ies_slots.resize(slot_end); if (!ies_slots.empty()) { int packed_size = 0; for (const unique_ptr &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++) { const 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