void DirectLightingIntegrator::take_single_light_sample(
    SamplingContext&            sampling_context,
    const MISHeuristic          mis_heuristic,
    const Dual3d&               outgoing,
    Spectrum&                   radiance,
    SpectrumStack&              aovs) const
{
    // todo: if we had a way to know that a BSDF is purely specular, we could
    // immediately return black here since there will be no contribution from
    // such a BSDF.

    if (!m_light_sampler.has_lights_or_emitting_triangles())
        return;

    const Vector3d s = sampling_context.next_vector2<3>();

    LightSample sample;
    m_light_sampler.sample(m_time, s, sample);

    if (sample.m_triangle)
    {
        add_emitting_triangle_sample_contribution(
            sample,
            mis_heuristic,
            outgoing,
            radiance,
            aovs);
    }
    else
    {
        add_non_physical_light_sample_contribution(
            sample,
            outgoing,
            radiance,
            aovs);
    }
}
void DirectLightingIntegrator::compute_outgoing_radiance_light_sampling_low_variance(
    SamplingContext&            sampling_context,
    const MISHeuristic          mis_heuristic,
    const Dual3d&               outgoing,
    Spectrum&                   radiance,
    SpectrumStack&              aovs) const
{
    radiance.set(0.0f);
    aovs.set(0.0f);

    // todo: if we had a way to know that a BSDF is purely specular, we could
    // immediately return black here since there will be no contribution from
    // such a BSDF.

    // Sample emitting triangles.
    if (m_light_sampler.get_emitting_triangle_count() > 0)
    {
        sampling_context.split_in_place(3, m_light_sample_count);

        for (size_t i = 0; i < m_light_sample_count; ++i)
        {
            const Vector3d s = sampling_context.next_vector2<3>();

            LightSample sample;
            m_light_sampler.sample_emitting_triangles(m_time, s, sample);

            add_emitting_triangle_sample_contribution(
                sample,
                mis_heuristic,
                outgoing,
                radiance,
                aovs);
        }

        if (m_light_sample_count > 1)
        {
            const float rcp_light_sample_count = 1.0f / m_light_sample_count;
            radiance *= rcp_light_sample_count;
            aovs *= rcp_light_sample_count;
        }
    }

    // Sample non-physical light sources.
    const size_t light_count = m_light_sampler.get_non_physical_light_count();
    if (light_count > 0)
    {
        sampling_context.split_in_place(2, light_count);

        for (size_t i = 0; i < light_count; ++i)
        {
            const Vector2d s = sampling_context.next_vector2<2>();

            LightSample sample;
            m_light_sampler.sample_non_physical_light(m_time, s, i, sample);

            add_non_physical_light_sample_contribution(
                sample,
                outgoing,
                radiance,
                aovs);
        }
    }
}
void DirectLightingIntegrator::compute_outgoing_radiance_light_sampling_low_variance(
    SamplingContext&                sampling_context,
    const MISHeuristic              mis_heuristic,
    const Dual3d&                   outgoing,
    DirectShadingComponents&        radiance,
    LightPathStream*                light_path_stream) const
{
    radiance.set(0.0f);

    // No light source in the scene.
    if (!m_light_sampler.has_lights())
        return;

    // Check if PDF of the sampler is Dirac delta and therefore cannot contribute to the light sampling.
    if (!m_material_sampler.contributes_to_light_sampling())
        return;

    if (m_light_sample_count > 0)
    {
        // Add contributions from all non-physical light sources that aren't part of the lightset.
        for (size_t i = 0, e = m_light_sampler.get_non_physical_light_count(); i < e; ++i)
        {
            // Sample the light.
            LightSample sample;
            m_light_sampler.sample_non_physical_light(m_time, i, sample);

            // Add the contribution of the chosen light.
            add_non_physical_light_sample_contribution(
                sampling_context,
                sample,
                outgoing,
                radiance,
                light_path_stream);
        }
    }

    // Add contributions from the light set.
    if (m_light_sampler.has_lightset())
    {
        DirectShadingComponents lightset_radiance;

        sampling_context.split_in_place(3, m_light_sample_count);

        for (size_t i = 0, e = m_light_sample_count; i < e; ++i)
        {
            // Sample the light set.
            LightSample sample;
            m_light_sampler.sample_lightset(
                m_time,
                sampling_context.next2<Vector3f>(),
                m_material_sampler.get_shading_point(),
                sample);

            // Add the contribution of the chosen light.
            if (sample.m_shape)
            {
                add_emitting_shape_sample_contribution(
                    sampling_context,
                    sample,
                    mis_heuristic,
                    outgoing,
                    lightset_radiance,
                    light_path_stream);
            }
            else
            {
                add_non_physical_light_sample_contribution(
                    sampling_context,
                    sample,
                    outgoing,
                    lightset_radiance,
                    light_path_stream);
            }
        }

        if (m_light_sample_count > 1)
            lightset_radiance /= static_cast<float>(m_light_sample_count);

        radiance += lightset_radiance;
    }
}