void DirectLightingIntegrator::compute_outgoing_radiance_bsdf_sampling(
    SamplingContext&            sampling_context,
    const MISHeuristic          mis_heuristic,
    const Dual3d&               outgoing,
    Spectrum&                   radiance,
    SpectrumStack&              aovs) const
{
    radiance.set(0.0f);
    aovs.set(0.0f);

    if (m_light_sampler.get_emitting_triangle_count() == 0)
        return;

    for (size_t i = 0; i < m_bsdf_sample_count; ++i)
    {
        take_single_bsdf_sample(
            sampling_context,
            mis_heuristic,
            outgoing,
            radiance,
            aovs);
    }

    if (m_bsdf_sample_count > 1)
    {
        const float rcp_bsdf_sample_count = 1.0f / m_bsdf_sample_count;
        radiance *= rcp_bsdf_sample_count;
        aovs *= rcp_bsdf_sample_count;
    }
}
void DirectLightingIntegrator::compute_outgoing_radiance_light_sampling(
    SamplingContext&            sampling_context,
    const MISHeuristic          mis_heuristic,
    const Dual3d&               outgoing,
    Spectrum&                   radiance,
    SpectrumStack&              aovs) const
{
    radiance.set(0.0f);
    aovs.set(0.0f);

    sampling_context.split_in_place(3, m_light_sample_count);

    for (size_t i = 0; i < m_light_sample_count; ++i)
    {
        take_single_light_sample(
            sampling_context,
            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;
    }
}
void DirectLightingIntegrator::take_single_bsdf_or_light_sample(
    SamplingContext&            sampling_context,
    Spectrum&                   radiance,
    SpectrumStack&              aovs)
{
    radiance.set(0.0f);
    aovs.set(0.0f);

    sampling_context.split_in_place(1, 1);

    if (sampling_context.next_double2() < 0.5)
    {
        sampling_context.split_in_place(3, m_light_sample_count);

        take_single_light_sample(
            sampling_context,
            DirectLightingIntegrator::mis_balance,
            radiance,
            aovs);
    }
    else
    {
        take_single_bsdf_sample(
            sampling_context,
            DirectLightingIntegrator::mis_balance,
            radiance,
            aovs);
    }
}
void DirectLightingIntegrator::compute_outgoing_radiance_single_sample(
    SamplingContext&            sampling_context,
    const Dual3d&               outgoing,
    Spectrum&                   radiance,
    SpectrumStack&              aovs) const
{
    radiance.set(0.0f);
    aovs.set(0.0f);

    if (m_light_sampler.get_emitting_triangle_count() > 0)
    {
        sampling_context.split_in_place(1, 1);

        if (sampling_context.next_double2() < 0.5)
        {
            sampling_context.split_in_place(3, 1);
            take_single_light_sample(
                sampling_context,
                MISBalance,
                outgoing,
                radiance,
                aovs);
        }
        else
        {
            take_single_bsdf_sample(
                sampling_context,
                MISBalance,
                outgoing,
                radiance,
                aovs);
        }

        radiance *= 2.0f;
        aovs *= 2.0f;
    }
    else
    {
        take_single_light_sample(
            sampling_context,
            MISNone,
            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);
        }
    }
}