* Split light types into own files, move light type specific code from light tree and MNEE. * Move flat light distribution code into own kernel file and host side building function, in preparation of light tree addition. Add light/sample.h as main entry point to kernel light sampling. * Better separate calculation of pdf for selecting a light, and pdf for sampling a point on the light. The selection pdf is now also stored in LightSampling for MNEE to correctly recalculate the full pdf when the shading position changes but the point on the light remains fixed. * Improvement to kernel light storage, using packed_float3, better variable names, etc. Includes contributions by Brecht Van Lommel and Weizhen Huang. Ref T77889
1096 lines
34 KiB
C++
1096 lines
34 KiB
C++
/* 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<float3> &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<KernelShaderEvalInput> &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<float> &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<Shader *>(node);
|
|
if (shader->get_use_mis() && shader->has_surface_emission) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LightManager::device_update_distribution(Device *device,
|
|
DeviceScene *dscene,
|
|
Scene *scene,
|
|
Progress &progress)
|
|
{
|
|
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
|
KernelFilm *kfilm = &dscene->data.film;
|
|
|
|
/* Update CDF over lights. */
|
|
progress.set_status("Updating Lights", "Computing distribution");
|
|
|
|
/* Counts emissive triangles in the scene. */
|
|
size_t num_triangles = 0;
|
|
|
|
foreach (Object *object, scene->objects) {
|
|
if (progress.get_cancel())
|
|
return;
|
|
|
|
if (!object_usable_as_light(object)) {
|
|
continue;
|
|
}
|
|
|
|
/* Count emissive triangles. */
|
|
Mesh *mesh = static_cast<Mesh *>(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<Shader *>(mesh->get_used_shaders()[shader_index]) :
|
|
scene->default_surface;
|
|
|
|
if (shader->get_use_mis() && shader->has_surface_emission) {
|
|
num_triangles++;
|
|
}
|
|
}
|
|
}
|
|
|
|
const size_t num_lights = kintegrator->num_lights;
|
|
const size_t num_background_lights = kintegrator->num_background_lights;
|
|
const size_t num_distribution = num_triangles + num_lights;
|
|
|
|
/* Distribution size. */
|
|
kintegrator->num_distribution = num_distribution;
|
|
|
|
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<Mesh *>(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<Shader *>(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++;
|
|
}
|
|
|
|
const float trianglearea = totarea;
|
|
|
|
/* Lights. */
|
|
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;
|
|
|
|
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 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;
|
|
}
|
|
}
|
|
|
|
/* 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->distribution_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;
|
|
}
|
|
|
|
/* Copy distribution to device. */
|
|
dscene->light_distribution.copy_to_device();
|
|
}
|
|
|
|
static void background_cdf(
|
|
int start, int end, int res_x, int res_y, const vector<float3> *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)
|
|
{
|
|
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
|
KernelBackground *kbackground = &dscene->data.background;
|
|
Light *background_light = NULL;
|
|
|
|
bool background_mis = false;
|
|
|
|
/* find background light */
|
|
foreach (Light *light, scene->lights) {
|
|
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;
|
|
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);
|
|
|
|
/* empirical value */
|
|
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<float3> 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<size_t>(0, res.y, rows_per_task),
|
|
[&](const blocked_range<size_t> &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_lights(Device *, 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;
|
|
|
|
foreach (Light *light, scene->lights) {
|
|
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->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;
|
|
|
|
foreach (Light *light, scene->lights) {
|
|
/* Consider moving portals update to their own function
|
|
* keeping this one more manageable. */
|
|
if (light->is_portal) {
|
|
assert(light->light_type == LIGHT_AREA);
|
|
|
|
float3 extentu = light->axisu * (light->sizeu * light->size);
|
|
float3 extentv = light->axisv * (light->sizev * light->size);
|
|
float area = len(extentu) * len(extentv);
|
|
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[portal_index].co = light->co;
|
|
klights[portal_index].area.extentu = extentu;
|
|
klights[portal_index].area.extentv = extentv;
|
|
klights[portal_index].area.invarea = invarea;
|
|
klights[portal_index].area.dir = dir;
|
|
klights[portal_index].tfm = light->tfm;
|
|
klights[portal_index].itfm = transform_inverse(light->tfm);
|
|
|
|
portal_index++;
|
|
continue;
|
|
}
|
|
|
|
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 = co;
|
|
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 = dir;
|
|
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();
|
|
|
|
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) {
|
|
float3 extentu = light->axisu * (light->sizeu * light->size);
|
|
float3 extentv = light->axisv * (light->sizev * light->size);
|
|
float area = len(extentu) * len(extentv);
|
|
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 = co;
|
|
klights[light_index].area.extentu = extentu;
|
|
klights[light_index].area.extentv = extentv;
|
|
klights[light_index].area.invarea = invarea;
|
|
klights[light_index].area.dir = dir;
|
|
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 cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
|
|
float spot_smooth = (1.0f - cos_half_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 = co;
|
|
klights[light_index].spot.radius = radius;
|
|
klights[light_index].spot.invarea = invarea;
|
|
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
|
|
klights[light_index].spot.spot_smooth = spot_smooth;
|
|
klights[light_index].spot.dir = dir;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
VLOG_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;
|
|
|
|
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_lights(device, 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_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
|