diff --git a/src/appleseed/renderer/modeling/environmentedf/hosekenvironmentedf.cpp b/src/appleseed/renderer/modeling/environmentedf/hosekenvironmentedf.cpp index 79870027fa..cf8b2e82b6 100644 --- a/src/appleseed/renderer/modeling/environmentedf/hosekenvironmentedf.cpp +++ b/src/appleseed/renderer/modeling/environmentedf/hosekenvironmentedf.cpp @@ -39,6 +39,7 @@ #include "renderer/modeling/input/inputarray.h" #include "renderer/modeling/input/source.h" #include "renderer/modeling/input/sourceinputs.h" +#include "renderer/modeling/light/sunlight.h" #include "renderer/utility/transformsequence.h" // appleseed.foundation headers. @@ -109,6 +110,7 @@ namespace m_inputs.declare("luminance_gamma", InputFormat::Float, "1.0"); m_inputs.declare("saturation_multiplier", InputFormat::Float, "1.0"); m_inputs.declare("horizon_shift", InputFormat::Float, "0.0"); + m_inputs.declare("sun_light", InputFormat::Entity, ""); } void release() override @@ -133,6 +135,9 @@ namespace // Evaluate uniform values. m_inputs.evaluate_uniforms(&m_uniform_values); + // If there is a binded sun get it + m_sun = dynamic_cast(m_inputs.get_entity("sun_light")); + // Compute the sun direction. m_sun_theta = deg_to_rad(m_uniform_values.m_sun_theta); m_sun_phi = deg_to_rad(m_uniform_values.m_sun_phi); @@ -189,6 +194,10 @@ namespace { assert(is_normalized(outgoing)); + Spectrum sun_value(0.0f); + if (m_sun) + m_sun->evaluate(Vector3d(outgoing.x, outgoing.y, outgoing.z), sun_value); + Transformd scratch; const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch); const Vector3f local_outgoing = transform.vector_to_local(outgoing); @@ -200,6 +209,7 @@ namespace else radiance.set(0.0f); value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); + value += sun_value; } void evaluate( @@ -210,6 +220,10 @@ namespace { assert(is_normalized(outgoing)); + Spectrum sun_value(0.0f); + if (m_sun) + m_sun->evaluate(Vector3d(outgoing.x, outgoing.y, outgoing.z), sun_value); + Transformd scratch; const Transformd& transform = m_transform_sequence.evaluate(0.0f, scratch); const Vector3f local_outgoing = transform.vector_to_local(outgoing); @@ -221,6 +235,7 @@ namespace else radiance.set(0.0f); value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); + value += sun_value; probability = shifted_outgoing.y > 0.0f ? shifted_outgoing.y * RcpPi() : 0.0f; assert(probability >= 0.0f); } @@ -265,6 +280,8 @@ namespace float m_uniform_coeffs[3 * 9]; float m_uniform_master_Y[3]; + SunLight* m_sun; + // Compute the coefficients of the radiance distribution function and the master luminance value. static void compute_coefficients( const float turbidity, diff --git a/src/appleseed/renderer/modeling/light/sunlight.cpp b/src/appleseed/renderer/modeling/light/sunlight.cpp index a0f593d380..037e0a3bf6 100644 --- a/src/appleseed/renderer/modeling/light/sunlight.cpp +++ b/src/appleseed/renderer/modeling/light/sunlight.cpp @@ -81,462 +81,535 @@ namespace // Sun's radius, in millions of km. // Reference: https://en.wikipedia.org/wiki/Solar_radius - const float SunRadius = 0.6957f; + constexpr float SunRadius = 0.6957f; // The smallest valid turbidity value. - const float BaseTurbidity = 2.0f; + constexpr float BaseTurbidity = 2.0f; +} + +SunLight::SunLight( + const char* name, + const ParamArray& params) + : Light(name, params) +{ + m_inputs.declare("environment_edf", InputFormat::Entity, ""); + m_inputs.declare("turbidity", InputFormat::Float); + m_inputs.declare("radiance_multiplier", InputFormat::Float, "1.0"); + m_inputs.declare("size_multiplier", InputFormat::Float, "1.0"); + m_inputs.declare("distance", InputFormat::Float, "149.6"); +} + +void SunLight::release() +{ + delete this; +} + +const char* SunLight::get_model() const +{ + return Model; +} + +bool SunLight::on_frame_begin( + const Project& project, + const BaseGroup* parent, + OnFrameBeginRecorder& recorder, + IAbortSwitch* abort_switch) +{ + if (!Light::on_frame_begin(project, parent, recorder, abort_switch)) + return false; + + // Check if sun disk is visible. + m_visible = m_params.get_optional("visible", true); + + // Evaluate uniform inputs. + m_inputs.evaluate_uniforms(&m_values); + + // Warn if distance input is not uniform. + Source* distance_src = get_inputs().source("distance"); + assert(distance_src != nullptr); + if (!distance_src->is_uniform()) + { + RENDERER_LOG_WARNING( + "distance between sun and scene \"%s\" is not uniform, using default value of 149.6 million km.", + get_path().c_str()); + m_values.m_distance = 149.6f; + } + + // Warn if size multiplier input is not uniform. + const Source* size_multiplier_src = get_inputs().source("size_multiplier"); + assert(size_multiplier_src != nullptr); + if (!size_multiplier_src->is_uniform()) + { + RENDERER_LOG_WARNING( + "size multiplier of the sun light \"%s\" is not uniform.", + get_path().c_str()); + m_values.m_size_multiplier = 1.0f; + } + + // Compute the Sun's solid angle. + // Reference: https://en.wikipedia.org/wiki/Solid_angle#Sun_and_Moon + m_sun_solid_angle = TwoPi() * (1.0f - std::cos(std::atan(SunRadius * m_values.m_size_multiplier / m_values.m_distance))); + + + // If the Sun light is bound to an environment EDF, let it override the Sun's direction and turbidity. + EnvironmentEDF* env_edf = dynamic_cast(m_inputs.get_entity("environment_edf")); + if (env_edf != nullptr) + apply_env_edf_overrides(env_edf); + + // Apply turbidity bias. + m_values.m_turbidity += BaseTurbidity; + + const Scene::RenderData& scene_data = project.get_scene()->get_render_data(); + m_scene_center = Vector3d(scene_data.m_center); + m_scene_radius = scene_data.m_radius; + m_safe_scene_diameter = scene_data.m_safe_diameter; + + precompute_constants(); + + return true; +} - class SunLight - : public Light +void SunLight::sample( + const ShadingContext& shading_context, + const Transformd& light_transform, + const Vector2d& s, + Vector3d& position, + Vector3d& outgoing, + Spectrum& value, + float& probability) const +{ + // todo: we need to choose a random direction as well in order to get + // soft shadows when using photon mapping. + sample_disk( + light_transform, + s, + m_scene_center, + m_scene_radius, + position, + outgoing, + value, + probability); +} + +void SunLight::sample( + const ShadingContext& shading_context, + const Transformd& light_transform, + const Vector3d& target_point, + const Vector2d& s, + Vector3d& position, + Vector3d& outgoing, + Spectrum& value, + float& probability) const +{ + sample_sun_surface( + light_transform, + target_point, + s, + position, + outgoing, + value, + probability); +} + +void SunLight::sample( + const ShadingContext& shading_context, + const Transformd& light_transform, + const Vector2d& s, + const LightTargetArray& targets, + Vector3d& position, + Vector3d& outgoing, + Spectrum& value, + float& probability) const +{ + const size_t target_count = targets.size(); + + if (target_count > 0) + { + const double x = s[0] * target_count; + const size_t target_index = truncate(x); + const Vector2d target_s(x - target_index, s[1]); + const LightTarget& target = targets[target_index]; + + sample_disk( + light_transform, + target_s, + target.get_center(), + target.get_radius(), + position, + outgoing, + value, + probability); + } + else { - public: - SunLight( - const char* name, - const ParamArray& params) - : Light(name, params) - { - m_inputs.declare("environment_edf", InputFormat::Entity, ""); - m_inputs.declare("turbidity", InputFormat::Float); - m_inputs.declare("radiance_multiplier", InputFormat::Float, "1.0"); - m_inputs.declare("size_multiplier", InputFormat::Float, "1.0"); - m_inputs.declare("distance", InputFormat::Float, "149.6"); - } - - void release() override - { - delete this; - } - - const char* get_model() const override - { - return Model; - } - - bool on_frame_begin( - const Project& project, - const BaseGroup* parent, - OnFrameBeginRecorder& recorder, - IAbortSwitch* abort_switch) override - { - if (!Light::on_frame_begin(project, parent, recorder, abort_switch)) - return false; - - // Evaluate uniform inputs. - m_inputs.evaluate_uniforms(&m_values); - - // Warn if distance input is not uniform. - Source* distance_src = get_inputs().source("distance"); - assert(distance_src != nullptr); - if (!distance_src->is_uniform()) - { - RENDERER_LOG_WARNING( - "distance between sun and scene \"%s\" is not uniform, using default value of 149.6 million km.", - get_path().c_str()); - m_values.m_distance = 149.6f; - } - - // Warn if size multiplier input is not uniform. - const Source* size_multiplier_src = get_inputs().source("size_multiplier"); - assert(size_multiplier_src != nullptr); - if (!size_multiplier_src->is_uniform()) - { - RENDERER_LOG_WARNING( - "size multiplier of the sun light \"%s\" is not uniform.", - get_path().c_str()); - m_values.m_size_multiplier = 1.0f; - } - - // Compute the Sun's solid angle. - // Reference: https://en.wikipedia.org/wiki/Solid_angle#Sun_and_Moon - m_sun_solid_angle = TwoPi() * (1.0f - std::cos(std::atan(SunRadius / m_values.m_distance))); - - // If the Sun light is bound to an environment EDF, let it override the Sun's direction and turbidity. - const EnvironmentEDF* env_edf = dynamic_cast(m_inputs.get_entity("environment_edf")); - if (env_edf != nullptr) - apply_env_edf_overrides(env_edf); - - // Apply turbidity bias. - m_values.m_turbidity += BaseTurbidity; - - const Scene::RenderData& scene_data = project.get_scene()->get_render_data(); - m_scene_center = Vector3d(scene_data.m_center); - m_scene_radius = scene_data.m_radius; - m_safe_scene_diameter = scene_data.m_safe_diameter; - - precompute_constants(); - - return true; - } - - void sample( - const ShadingContext& shading_context, - const Transformd& light_transform, - const Vector2d& s, - Vector3d& position, - Vector3d& outgoing, - Spectrum& value, - float& probability) const override - { - // todo: we need to choose a random direction as well in order to get - // soft shadows when using photon mapping. - sample_disk( - light_transform, - s, - m_scene_center, - m_scene_radius, - position, - outgoing, - value, - probability); - } - - void sample( - const ShadingContext& shading_context, - const Transformd& light_transform, - const Vector3d& target_point, - const Vector2d& s, - Vector3d& position, - Vector3d& outgoing, - Spectrum& value, - float& probability) const override - { - sample_sun_surface( - light_transform, - target_point, - s, - position, - outgoing, - value, - probability); - } - - void sample( - const ShadingContext& shading_context, - const Transformd& light_transform, - const Vector2d& s, - const LightTargetArray& targets, - Vector3d& position, - Vector3d& outgoing, - Spectrum& value, - float& probability) const override - { - const size_t target_count = targets.size(); - - if (target_count > 0) - { - const double x = s[0] * target_count; - const size_t target_index = truncate(x); - const Vector2d target_s(x - target_index, s[1]); - const LightTarget& target = targets[target_index]; - - sample_disk( - light_transform, - target_s, - target.get_center(), - target.get_radius(), - position, - outgoing, - value, - probability); - } - else - { - sample_disk( - light_transform, - s, - m_scene_center, - m_scene_radius, - position, - outgoing, - value, - probability); - } - } - - float compute_distance_attenuation( - const Vector3d& target, - const Vector3d& position) const override - { - return 1.0f; - } - - private: - APPLESEED_DECLARE_INPUT_VALUES(InputValues) - { - float m_turbidity; // atmosphere turbidity - float m_radiance_multiplier; // emitted radiance multiplier - float m_size_multiplier; // Sun size multiplier - float m_distance; // distance between Sun and scene, in millions of km - }; - - Vector3d m_scene_center; // world space - double m_scene_radius; // world space - double m_safe_scene_diameter; // world space - float m_sun_solid_angle; // Sun's solid angle, in steradians - - InputValues m_values; - - RegularSpectrum31f m_k1; - RegularSpectrum31f m_k2; - - void apply_env_edf_overrides(const EnvironmentEDF* env_edf) - { - // Use the Sun direction from the EDF if it has one. - const Source* sun_theta_src = env_edf->get_inputs().source("sun_theta"); - const Source* sun_phi_src = env_edf->get_inputs().source("sun_phi"); - if (sun_theta_src != nullptr && - sun_theta_src->is_uniform() && - sun_phi_src != nullptr && - sun_phi_src->is_uniform()) - { - float sun_theta, sun_phi; - sun_theta_src->evaluate_uniform(sun_theta); - sun_phi_src->evaluate_uniform(sun_phi); - - Transformd scratch; - const Transformd& env_edf_transform = env_edf->transform_sequence().evaluate(0.0f, scratch); - - set_transform( - Transformd::from_local_to_parent( - Matrix4d::make_rotation( - Quaterniond::make_rotation( - Vector3d(0.0, 0.0, -1.0), // default emission direction of this light - -Vector3d::make_unit_vector(deg_to_rad(sun_theta), deg_to_rad(sun_phi))))) * - env_edf_transform); - } - - // Use the Sun turbidity from the EDF if it has one. - const Source* turbidity_src = env_edf->get_inputs().source("turbidity"); - const Source* turbidity_multiplier_src = env_edf->get_inputs().source("turbidity_multiplier"); - if (turbidity_src != nullptr && - turbidity_src->is_uniform() && - turbidity_multiplier_src != nullptr && - turbidity_multiplier_src->is_uniform()) - { - float turbidity_multiplier; - turbidity_multiplier_src->evaluate_uniform(turbidity_multiplier); - turbidity_src->evaluate_uniform(m_values.m_turbidity); - m_values.m_turbidity *= turbidity_multiplier; - } - } - - void precompute_constants() - { - for (size_t i = 0; i < 31; ++i) - m_k1[i] = -0.008735f * std::pow(g_light_wavelengths_um[i], -4.08f); - - const float Alpha = 1.3f; // ratio of small to large particle sizes (0 to 4, typically 1.3) - - for (size_t i = 0; i < 31; ++i) - m_k2[i] = std::pow(g_light_wavelengths_um[i], -Alpha); - } - - void compute_sun_radiance( - const Vector3d& outgoing, - const float turbidity, - const float radiance_multiplier, - RegularSpectrum31f& radiance) const - { - // Compute the relative optical mass. - const float cos_theta = -static_cast(outgoing.y); - const float theta = std::acos(cos_theta); - const float theta_delta = 93.885f - rad_to_deg(theta); - if (theta_delta < 0.0f) - { - radiance.set(0.0f); - return; - } - const float m = 1.0f / (cos_theta + 0.15f * std::pow(theta_delta, -1.253f)); - - // Compute transmittance due to Rayleigh scattering. - RegularSpectrum31f tau_r; - for (size_t i = 0; i < 31; ++i) - tau_r[i] = std::exp(m * m_k1[i]); - - // Compute transmittance due to aerosols. - const float beta = 0.04608f * turbidity - 0.04586f; - RegularSpectrum31f tau_a; - for (size_t i = 0; i < 31; ++i) - tau_a[i] = std::exp(-beta * m * m_k2[i]); - - // Compute transmittance due to ozone absorption. - const float L = 0.35f; // amount of ozone in cm - static const float Ko[31] = - { - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.003f, 0.006f, 0.009f, - 0.014f, 0.021f, 0.030f, 0.040f, - 0.048f, 0.063f, 0.075f, 0.085f, - 0.103f, 0.120f, 0.120f, 0.115f, - 0.125f, 0.120f, 0.105f, 0.090f, - 0.079f, 0.067f, 0.057f, 0.048f, - 0.036f, 0.028f, 0.023f - }; - RegularSpectrum31f tau_o; - for (size_t i = 0; i < 31; ++i) - tau_o[i] = std::exp(-Ko[i] * L * m); + sample_disk( + light_transform, + s, + m_scene_center, + m_scene_radius, + position, + outgoing, + value, + probability); + } +} + +float SunLight::compute_distance_attenuation( + const Vector3d& target, + const Vector3d& position) const +{ + return 1.0f; +} + +void SunLight::evaluate( + const Vector3d& outgoing, + Spectrum& value) +{ + assert(is_normalized(outgoing)); + + if (!m_visible) + { + value.set(0.0f); + return; + } + + const Vector3d local_outgoing = normalize(get_transform().point_to_local(outgoing)); + const double cos_theta = dot(local_outgoing, Vector3d(0.0, 0.0, 1.0)); + const double sin_theta = std::sqrt(1.0 - cos_theta * cos_theta); + + const double sin_theta_max = SunRadius * m_values.m_size_multiplier / m_values.m_distance; + const double cos_theta_max = std::sqrt(1.0 - sin_theta_max * sin_theta_max); + + + if (cos_theta < cos_theta_max) + { + value.set(0.0f); + return; + } + + const double distance_to_center = SunRadius * m_values.m_size_multiplier * + ((sin_theta / cos_theta) / (sin_theta_max / cos_theta_max)); + + RegularSpectrum31f radiance; + compute_sun_radiance( + -outgoing, + m_values.m_turbidity, + m_values.m_radiance_multiplier, + radiance, + static_cast(distance_to_center)); + + value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); +} + +void SunLight::compute_sun_radiance( + const Vector3d& outgoing, + const float turbidity, + const float radiance_multiplier, + RegularSpectrum31f& radiance, + const float distance_to_center) const +{ + // Compute the relative optical mass. + const float cos_theta = -static_cast(outgoing.y); + const float theta = std::acos(cos_theta); + const float theta_delta = 93.885f - rad_to_deg(theta); + if (theta_delta < 0.0f) + { + radiance.set(0.0f); + return; + } + const float m = 1.0f / (cos_theta + 0.15f * std::pow(theta_delta, -1.253f)); + + // Compute transmittance due to Rayleigh scattering. + RegularSpectrum31f tau_r; + for (size_t i = 0; i < 31; ++i) + tau_r[i] = std::exp(m * m_k1[i]); + + // Compute transmittance due to aerosols. + const float beta = 0.04608f * turbidity - 0.04586f; + RegularSpectrum31f tau_a; + for (size_t i = 0; i < 31; ++i) + tau_a[i] = std::exp(-beta * m * m_k2[i]); + + // Compute transmittance due to ozone absorption. + const float L = 0.35f; // amount of ozone in cm + static const float Ko[31] = + { + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.003f, 0.006f, 0.009f, + 0.014f, 0.021f, 0.030f, 0.040f, + 0.048f, 0.063f, 0.075f, 0.085f, + 0.103f, 0.120f, 0.120f, 0.115f, + 0.125f, 0.120f, 0.105f, 0.090f, + 0.079f, 0.067f, 0.057f, 0.048f, + 0.036f, 0.028f, 0.023f + }; + RegularSpectrum31f tau_o; + for (size_t i = 0; i < 31; ++i) + tau_o[i] = std::exp(-Ko[i] * L * m); #ifdef COMPUTE_REDUNDANT - // Compute transmittance due to mixed gases absorption. - // Disabled since all coefficients are zero in the wavelength range of the simulation. - static const float Kg[31] = - { - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f - }; - RegularSpectrum31f tau_g; - for (size_t i = 0; i < 31; ++i) - tau_g[i] = std::exp(-1.41f * Kg[i] * m / std::pow(1.0f + 118.93f * Kg[i] * m, 0.45f)); + // Compute transmittance due to mixed gases absorption. + // Disabled since all coefficients are zero in the wavelength range of the simulation. + static const float Kg[31] = + { + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f + }; + RegularSpectrum31f tau_g; + for (size_t i = 0; i < 31; ++i) + tau_g[i] = std::exp(-1.41f * Kg[i] * m / std::pow(1.0f + 118.93f * Kg[i] * m, 0.45f)); #endif - // Compute transmittance due to water vapor absorption. - const float W = 2.0f; // precipitable water vapor in cm - static const float Kwa[31] = - { - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.000f, 0.000f, 0.000f, - 0.000f, 0.016f, 0.024f - }; - RegularSpectrum31f tau_wa; - for (size_t i = 0; i < 31; ++i) - tau_wa[i] = std::exp(-0.2385f * Kwa[i] * W * m / std::pow(1.0f + 20.07f * Kwa[i] * W * m, 0.45f)); - - // Sun radiance in W.m^-2.sr^-1.um^-1. - // The units in the paper are W.cm^-2.sr^-1.um^-1. We must multiply the values - // by 10000 to obtain W.m^-2.sr^-1.um^-1. We must then divide them by 1000 to - // obtain W.m^-2.sr^-1.nm^-1. - static const float SunRadianceValues[31] = - { - 21127.5f, 25888.2f, 25829.1f, 24232.3f, - 26760.5f, 29658.3f, 30545.4f, 30057.5f, - 30663.7f, 28830.4f, 28712.1f, 27825.0f, - 27100.6f, 27233.6f, 26361.3f, 25503.8f, - 25060.2f, 25311.6f, 25355.9f, 25134.2f, - 24631.5f, 24173.2f, 23685.3f, 23212.1f, - 22827.7f, 22339.8f, 21970.2f, 21526.7f, - 21097.9f, 20728.3f, 20240.4f - }; - - // Compute the attenuated radiance of the Sun. - for (size_t i = 0; i < 31; ++i) - { - radiance[i] = - SunRadianceValues[i] * - tau_r[i] * - tau_a[i] * - tau_o[i] * + // Compute transmittance due to water vapor absorption. + const float W = 2.0f; // precipitable water vapor in cm + static const float Kwa[31] = + { + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.000f, 0.000f, 0.000f, + 0.000f, 0.016f, 0.024f + }; + RegularSpectrum31f tau_wa; + for (size_t i = 0; i < 31; ++i) + tau_wa[i] = std::exp(-0.2385f * Kwa[i] * W * m / std::pow(1.0f + 20.07f * Kwa[i] * W * m, 0.45f)); + + // Sun radiance in W.m^-2.sr^-1.um^-1. + // The units in the paper are W.cm^-2.sr^-1.um^-1. We must multiply the values + // by 10000 to obtain W.m^-2.sr^-1.um^-1. We must then divide them by 1000 to + // obtain W.m^-2.sr^-1.nm^-1. + static const float SunRadianceValues[31] = + { + 21127.5f, 25888.2f, 25829.1f, 24232.3f, + 26760.5f, 29658.3f, 30545.4f, 30057.5f, + 30663.7f, 28830.4f, 28712.1f, 27825.0f, + 27100.6f, 27233.6f, 26361.3f, 25503.8f, + 25060.2f, 25311.6f, 25355.9f, 25134.2f, + 24631.5f, 24173.2f, 23685.3f, 23212.1f, + 22827.7f, 22339.8f, 21970.2f, 21526.7f, + 21097.9f, 20728.3f, 20240.4f + }; + + + // Limb darkening. + // + // Reference: + // + // Lintu, Andrei & Haber, Jörg & Magnor, Marcus. + // (2005). Realistic Solar Disc Rendering. + // http://wscg.zcu.cz/wscg2005/Papers_2005/Full/F17-full.pdf + // + + constexpr float LimbDarkeningCoeficent = 0.6f; // Limb darkening coefficient for the sun for visible sunlight. + float limb_darkenig = 1.0f; + if (distance_to_center > 0.0f) + { + limb_darkenig = (1.0f - LimbDarkeningCoeficent * + (1.0f - std::sqrt(1.0f - std::pow(distance_to_center, 2.0f) / + std::pow(SunRadius * m_values.m_size_multiplier, 2.0f)))); + } + + // Compute the attenuated radiance of the Sun. + for (size_t i = 0; i < 31; ++i) + { + radiance[i] = + SunRadianceValues[i] * + tau_r[i] * + tau_a[i] * + tau_o[i] * #ifdef COMPUTE_REDUNDANT - tau_g[i] * // always 1.0 + tau_g[i] * // always 1.0 #endif - tau_wa[i] * - radiance_multiplier; - } - } - - void sample_disk( - const Transformd& light_transform, - const Vector2d& s, - const Vector3d& disk_center, - const double disk_radius, - Vector3d& position, - Vector3d& outgoing, - Spectrum& value, - float& probability) const - { - outgoing = -normalize(light_transform.get_parent_z()); - - const Basis3d basis(outgoing); - const Vector2d p = sample_disk_uniform(s); - - position = - disk_center - - m_safe_scene_diameter * basis.get_normal() - + disk_radius * p[0] * basis.get_tangent_u() - + disk_radius * p[1] * basis.get_tangent_v(); - - probability = 1.0f / (Pi() * square(static_cast(disk_radius))); - assert(probability > 0.0f); - - RegularSpectrum31f radiance; - compute_sun_radiance( - outgoing, - m_values.m_turbidity, - m_values.m_radiance_multiplier, - radiance); - - value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); - value *= m_sun_solid_angle; - } - - void sample_sun_surface( - const Transformd& light_transform, - const Vector3d& target_point, - const Vector2d& s, - Vector3d& position, - Vector3d& outgoing, - Spectrum& value, - float& probability) const - { - assert(m_safe_scene_diameter > 0.0); - - // sun_diameter = 1.3914 - // angular_diameter = 2 * arctan(sun_diameter / (2 * distance)) - // tan(angular_diameter / 2) * distance = sun_radius - // tan(angular_diameter / 2) * scene_diameter = virtual_sun_radius - // -> virtual_sun_radius = sun_radius * scene_diameter / distance - double sun_radius = SunRadius * m_safe_scene_diameter / m_values.m_distance; - sun_radius *= m_values.m_size_multiplier; - - outgoing = -normalize(light_transform.get_parent_z()); - - const Basis3d basis(outgoing); - const Vector2d p = sample_disk_uniform(s); - - position = - target_point - - m_safe_scene_diameter * basis.get_normal() - + sun_radius * p[0] * basis.get_tangent_u() - + sun_radius * p[1] * basis.get_tangent_v(); - - outgoing = normalize(target_point - position); - - RegularSpectrum31f radiance; - compute_sun_radiance( - outgoing, - m_values.m_turbidity, - m_values.m_radiance_multiplier, - radiance); - - value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); - value *= m_sun_solid_angle; - - // - // The Sun is represented by a disk of finite radius. The Sun's illumination - // at a given point of the scene is computed by integrating the Sun's - // contribution over that disk. The probability density of a given sample on - // that disk is 1 / Sun's disk surface area. - // - // Since compute_sun_radiance() assumes that the Sun is reduced to a point - // infinitely far away, it's really returning an irradiance, and we need to - // convert it back to a radiance by dividing its return value by the surface - // area of the Sun's disk, which is equivalent to multiplying the probability - // density by the Sun disk's surface area, which leaves us with probability = 1. - // - - probability = 1.0f; - } - }; + tau_wa[i] * + limb_darkenig * + radiance_multiplier; + } +} + +void SunLight::apply_env_edf_overrides(EnvironmentEDF* env_edf) +{ + env_edf->get_inputs().find("sun_light").bind(this); + + // Use the Sun direction from the EDF if it has one. + const Source* sun_theta_src = env_edf->get_inputs().source("sun_theta"); + const Source* sun_phi_src = env_edf->get_inputs().source("sun_phi"); + const Source* sun_shift_src = env_edf->get_inputs().source("horizon_shift"); + if (sun_theta_src != nullptr && + sun_theta_src->is_uniform() && + sun_phi_src != nullptr && + sun_phi_src->is_uniform() && + sun_shift_src != nullptr && + sun_shift_src->is_uniform()) + { + float sun_theta, sun_phi, sun_shift; + sun_theta_src->evaluate_uniform(sun_theta); + sun_phi_src->evaluate_uniform(sun_phi); + sun_shift_src->evaluate_uniform(sun_shift); + + Transformd scratch; + const Transformd& env_edf_transform = env_edf->transform_sequence().evaluate(0.0f, scratch); + + set_transform( + Transformd::from_local_to_parent( + Matrix4d::make_translation(Vector3d(0.0, sun_shift, 0.0)) * + Matrix4d::make_rotation( + Quaterniond::make_rotation( + Vector3d(0.0, 0.0, -1.0), // default emission direction of this light + -Vector3d::make_unit_vector(deg_to_rad(sun_theta), deg_to_rad(sun_phi))))) * + env_edf_transform); + } + + // Use the Sun turbidity from the EDF if it has one. + const Source* turbidity_src = env_edf->get_inputs().source("turbidity"); + const Source* turbidity_multiplier_src = env_edf->get_inputs().source("turbidity_multiplier"); + if (turbidity_src != nullptr && + turbidity_src->is_uniform() && + turbidity_multiplier_src != nullptr && + turbidity_multiplier_src->is_uniform()) + { + float turbidity_multiplier; + turbidity_multiplier_src->evaluate_uniform(turbidity_multiplier); + turbidity_src->evaluate_uniform(m_values.m_turbidity); + m_values.m_turbidity *= turbidity_multiplier; + } +} + +void SunLight::precompute_constants() +{ + for (size_t i = 0; i < 31; ++i) + m_k1[i] = -0.008735f * std::pow(g_light_wavelengths_um[i], -4.08f); + + const float Alpha = 1.3f; // ratio of small to large particle sizes (0 to 4, typically 1.3) + + for (size_t i = 0; i < 31; ++i) + m_k2[i] = std::pow(g_light_wavelengths_um[i], -Alpha); +} + +void SunLight::sample_disk( + const Transformd& light_transform, + const Vector2d& s, + const Vector3d& disk_center, + const double disk_radius, + Vector3d& position, + Vector3d& outgoing, + Spectrum& value, + float& probability) const +{ + outgoing = -normalize(light_transform.get_parent_z()); + + Basis3d basis(outgoing); + const Vector2d p = sample_disk_uniform(s); + + position = + disk_center + - m_safe_scene_diameter * basis.get_normal() + + disk_radius * p[0] * basis.get_tangent_u() + + disk_radius * p[1] * basis.get_tangent_v(); + + probability = 1.0f / (Pi() * square(static_cast(disk_radius))); + assert(probability > 0.0f); + + RegularSpectrum31f radiance; + compute_sun_radiance( + outgoing, + m_values.m_turbidity, + m_values.m_radiance_multiplier, + radiance); + + // Shift sun. + basis = Basis3d(-normalize(light_transform.get_parent_z() + light_transform.get_parent_origin())); + + // Repositioning. + position = + disk_center + - m_safe_scene_diameter * basis.get_normal() + + disk_radius * p[0] * basis.get_tangent_u() + + disk_radius * p[1] * basis.get_tangent_v(); + + value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); + value *= m_sun_solid_angle; +} + +void SunLight::sample_sun_surface( + const Transformd& light_transform, + const Vector3d& target_point, + const Vector2d& s, + Vector3d& position, + Vector3d& outgoing, + Spectrum& value, + float& probability) const +{ + assert(m_safe_scene_diameter > 0.0); + + // sun_diameter = 1.3914 + // angular_diameter = 2 * arctan(sun_diameter / (2 * distance)) + // tan(angular_diameter / 2) * distance = sun_radius + // tan(angular_diameter / 2) * scene_diameter = virtual_sun_radius + // -> virtual_sun_radius = sun_radius * scene_diameter / distance + double sun_radius = SunRadius * m_safe_scene_diameter / m_values.m_distance; + sun_radius *= m_values.m_size_multiplier; + + outgoing = -normalize(light_transform.get_parent_z()); + + Basis3d basis(outgoing); + const Vector2d p = sample_disk_uniform(s); + + position = + target_point + - m_safe_scene_diameter * basis.get_normal() + + sun_radius * p[0] * basis.get_tangent_u() + + sun_radius * p[1] * basis.get_tangent_v(); + + outgoing = normalize(target_point - position); + float distance_to_center = SunRadius * m_values.m_size_multiplier * float(std::sqrt(p[0] * p[0] + p[1] * p[1])); + + + RegularSpectrum31f radiance; + compute_sun_radiance( + outgoing, + m_values.m_turbidity, + m_values.m_radiance_multiplier, + radiance, + distance_to_center); + + value.set(radiance, g_std_lighting_conditions, Spectrum::Illuminance); + + // Shift sun. + basis = Basis3d(-normalize(light_transform.get_parent_z() + light_transform.get_parent_origin())); + + // Repositioning. + position = + target_point + - m_safe_scene_diameter * basis.get_normal() + + sun_radius * p[0] * basis.get_tangent_u() + + sun_radius * p[1] * basis.get_tangent_v(); + outgoing = normalize(target_point - position); + + value *= m_sun_solid_angle; + + // + // The Sun is represented by a disk of finite radius. The Sun's illumination + // at a given point of the scene is computed by integrating the Sun's + // contribution over that disk. The probability density of a given sample on + // that disk is 1 / Sun's disk surface area. + // + // Since compute_sun_radiance() assumes that the Sun is reduced to a point + // infinitely far away, it's really returning an irradiance, and we need to + // convert it back to a radiance by dividing its return value by the surface + // area of the Sun's disk, which is equivalent to multiplying the probability + // density by the Sun disk's surface area, which leaves us with probability = 1. + // + + probability = 1.0f; } @@ -577,6 +650,15 @@ DictionaryArray SunLightFactory::get_input_metadata() const .insert("use", "optional") .insert("help", "If an environment EDF is bound, use the sun angles and turbidity values from the environment")); + metadata.push_back( + Dictionary() + .insert("name", "visible") + .insert("label", "Visible") + .insert("type", "boolean") + .insert("use", "optional") + .insert("default", "true") + .insert("help", "Make the sun visible to the camera")); + metadata.push_back( Dictionary() .insert("name", "turbidity") diff --git a/src/appleseed/renderer/modeling/light/sunlight.h b/src/appleseed/renderer/modeling/light/sunlight.h index 33df62747c..72e04a9d05 100644 --- a/src/appleseed/renderer/modeling/light/sunlight.h +++ b/src/appleseed/renderer/modeling/light/sunlight.h @@ -41,13 +41,122 @@ #include "main/dllsymbol.h" // Forward declarations. -namespace foundation { class Dictionary; } -namespace foundation { class DictionaryArray; } -namespace renderer { class ParamArray; } +namespace foundation { class Dictionary; } +namespace foundation { class DictionaryArray; } +namespace renderer { class ParamArray; } +namespace renderer { class EnvironmentEDF; } namespace renderer { + +// +// Physically-based Sun light. +// + +class SunLight + :public Light +{ + public: + SunLight( + const char* name, + const ParamArray& params); + + void release() override; + const char* get_model() const override; + + bool on_frame_begin( + const Project& project, + const BaseGroup* parent, + OnFrameBeginRecorder& recorder, + foundation::IAbortSwitch* abort_switch) override; + + void sample( + const ShadingContext& shading_context, + const foundation::Transformd& light_transform, + const foundation::Vector2d& s, + foundation::Vector3d& position, + foundation::Vector3d& outgoing, + Spectrum& value, + float& probability) const override; + void sample( + const ShadingContext& shading_context, + const foundation::Transformd& light_transform, + const foundation::Vector3d& target_point, + const foundation::Vector2d& s, + foundation::Vector3d& position, + foundation::Vector3d& outgoing, + Spectrum& value, + float& probability) const override; + void sample( + const ShadingContext& shading_context, + const foundation::Transformd& light_transform, + const foundation::Vector2d& s, + const LightTargetArray& targets, + foundation::Vector3d& position, + foundation::Vector3d& outgoing, + Spectrum& value, + float& probability) const override; + + float compute_distance_attenuation( + const foundation::Vector3d& target, + const foundation::Vector3d& position) const override; + + void evaluate( + const foundation::Vector3d& outgoing, + Spectrum& value); + + private: + APPLESEED_DECLARE_INPUT_VALUES(InputValues) + { + float m_turbidity; // atmosphere turbidity + float m_radiance_multiplier; // emitted radiance multiplier + float m_size_multiplier; // Sun size multiplier + float m_distance; // distance between Sun and scene, in millions of km + }; + + bool m_visible; // visible of the sun + foundation::Vector3d m_scene_center; // world space + double m_scene_radius; // world space + double m_safe_scene_diameter; // world space + float m_sun_solid_angle; // Sun's solid angle, in steradians + + InputValues m_values; + + foundation::RegularSpectrum31f m_k1; + foundation::RegularSpectrum31f m_k2; + + void apply_env_edf_overrides(EnvironmentEDF* env_edf); + + void precompute_constants(); + + void compute_sun_radiance( + const foundation::Vector3d& outgoing, + const float turbidity, + const float radiance_multiplier, + foundation::RegularSpectrum31f& radiance, + const float distance_to_center = 0.0f) const; + + void sample_disk( + const foundation::Transformd& light_transform, + const foundation::Vector2d& s, + const foundation::Vector3d& disk_center, + const double disk_radius, + foundation::Vector3d& position, + foundation::Vector3d& outgoing, + Spectrum& value, + float& probability) const; + void sample_sun_surface( + const foundation::Transformd& light_transform, + const foundation::Vector3d& target_point, + const foundation::Vector2d& s, + foundation::Vector3d& position, + foundation::Vector3d& outgoing, + Spectrum& value, + float& probability) const; +}; + + // // Sun light factory. //