Exemplo n.º 1
0
    def pdf(self, ctx, si, wo, active):
        if not ctx.is_enabled(BSDFFlags.DiffuseReflection):
            return Vector3f(0)

        cos_theta_i = Frame3f.cos_theta(si.wi)
        cos_theta_o = Frame3f.cos_theta(wo)

        pdf = warp.square_to_cosine_hemisphere_pdf(wo)

        return ek.select((cos_theta_i > 0.0) & (cos_theta_o > 0.0), pdf, 0.0)
Exemplo n.º 2
0
def test03_frame_equality(variant_scalar_rgb):
    from mitsuba.core import Frame3f

    f1 = Frame3f([1, 0, 0], [0, 1, 0], [0, 0, 1])
    f2 = Frame3f([0, 0, 1])
    f3 = Frame3f([0, 0, 1], [0, 1, 0], [1, 0, 0])

    assert f1 == f2
    assert f2 == f1
    assert not f1 == f3
    assert not f2 == f3
Exemplo n.º 3
0
    def eval(self, ctx, si, wo, active):
        if not ctx.is_enabled(BSDFFlags.DiffuseReflection):
            return Vector3f(0)

        cos_theta_i = Frame3f.cos_theta(si.wi)
        cos_theta_o = Frame3f.cos_theta(wo)

        value = self.m_reflectance.eval(si, active) * math.InvPi * cos_theta_o

        return ek.select((cos_theta_i > 0.0) & (cos_theta_o > 0.0), value,
                         Vector3f(0))
Exemplo n.º 4
0
    def eval(self, ctx, si, wo, active):
        """
        Emitter sampling
        """
        if not ctx.is_enabled(BSDFFlags.DiffuseReflection):
            return Vector3f(0)

        cos_theta_i = Frame3f.cos_theta(si.wi) 
        cos_theta_o = Frame3f.cos_theta(wo)

        value = self.get_btf(si.wi, wo, si.uv) * math.InvPi

        return ek.select((cos_theta_i > 0.0) & (cos_theta_o > 0.0), value, Vector3f(0))
def test03_mueller_to_world_to_local(variant_scalar_mono_polarized):
    """
    At a few places, coordinate changes between local BSDF reference frame and
    world coordinates need to take place. This change also needs to be applied
    to Mueller matrices used in computations involving polarization state.

    In practice, this is always a simple rotation of reference Stokes vectors
    (for incident & outgoing directions) of the Mueller matrix.

    To test this behavior we take any Mueller matrix (e.g. linear polarizer)
    for some arbitrary incident/outgoing directions in world coordinates and
    compute the round trip going to local frame and back again.
    """
    from mitsuba.core import Frame3f, UnpolarizedSpectrum
    from mitsuba.render import SurfaceInteraction3f
    from mitsuba.render.mueller import linear_polarizer
    import numpy as np

    si = SurfaceInteraction3f()
    si.sh_frame = Frame3f(ek.normalize([1.0, 1.0, 1.0]))

    M = linear_polarizer(UnpolarizedSpectrum(1.0))

    # Random incident and outgoing directions
    wi_world = ek.normalize([0.2, 0.0, 1.0])
    wo_world = ek.normalize([0.0, -0.8, 1.0])

    wi_local = si.to_local(wi_world)
    wo_local = si.to_local(wo_world)

    M_local = si.to_local_mueller(M, wi_world, wo_world)
    M_world = si.to_world_mueller(M_local, wi_local, wo_local)

    assert ek.allclose(M, M_world, atol=1e-5)
Exemplo n.º 6
0
    def eval(self, si, active):
        cosTheta = ek.dot(si.wi, si.n)

        tmp2 = ek.select(Frame3f.cos_theta(si.wi) > 0, \
                self.m_radiance.eval(si, active)*self.fallof(cosTheta), \
                float(0))
        return tmp2
Exemplo n.º 7
0
def test03_sample_ray(variant_packet_spectral, spectrum_key):
    # Check the correctness of the sample_ray() method

    from mitsuba.core import warp, Frame3f, sample_shifted
    from mitsuba.render import SurfaceInteraction3f

    shape, spectrum = create_emitter_and_spectrum(spectrum_key)
    emitter = shape.emitter()

    time = 0.5
    wavelength_sample = [0.5, 0.33, 0.1]
    pos_sample = [[0.2, 0.1, 0.2], [0.6, 0.9, 0.2]]
    dir_sample = [[0.4, 0.5, 0.3], [0.1, 0.4, 0.9]]

    # Sample a ray (position, direction, wavelengths) on the emitter
    ray, res = emitter.sample_ray(time, wavelength_sample, pos_sample,
                                  dir_sample)

    # Sample wavelengths on the spectrum
    it = SurfaceInteraction3f.zero(3)
    wav, spec = spectrum.sample_spectrum(it, sample_shifted(wavelength_sample))

    # Sample a position on the shape
    ps = shape.sample_position(time, pos_sample)

    assert ek.allclose(res, spec * shape.surface_area() * ek.pi)
    assert ek.allclose(ray.time, time)
    assert ek.allclose(ray.wavelengths, wav)
    assert ek.allclose(ray.o, ps.p)
    assert ek.allclose(
        ray.d,
        Frame3f(ps.n).to_world(warp.square_to_cosine_hemisphere(dir_sample)))
def test02_eval_all(variant_scalar_rgb):
    from mitsuba.core import Frame3f
    from mitsuba.render import BSDFFlags, BSDFContext, SurfaceInteraction3f
    from mitsuba.core.xml import load_string
    from mitsuba.core.math import InvPi

    weight = 0.2

    bsdf = load_string("""<bsdf version="2.0.0" type="blendbsdf">
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="0.0"/>
        </bsdf>
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="1.0"/>
        </bsdf>
        <spectrum name="weight" value="{}"/>
    </bsdf>""".format(weight))

    si = SurfaceInteraction3f()
    si.t = 0.1
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.sh_frame = Frame3f(si.n)
    si.wi = [0, 0, 1]

    wo = [0, 0, 1]
    ctx = BSDFContext()

    # Evaluate the blend of both components
    expected = (1 - weight) * 0.0 * InvPi + weight * 1.0 * InvPi
    value    = bsdf.eval(ctx, si, wo)
    assert ek.allclose(value, expected)
def test02_sample_quarter_wave_local(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Transform4f, Spectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Test polarized implementation. Special case of delta = 90˚, also known
    # as a quarter-wave plate. (In local BSDF coordinates.)
    #
    # Following "Polarized Light - Fundamentals and Applications" by Edward Collett
    # Chapter 5.3, equation (30) & (31):
    #
    # Case 1) Linearly polarized +45˚ light (Stokes vector [1, 0, 1, 0]) yields
    #         right circularly polarized light (Stokes vector [1, 0, 0, 1]).
    # Case 2) Linearly polarized -45˚ light (Stokes vector [1, 0, -1, 0]) yields
    #         left circularly polarized light (Stokes vector [1, 0, 0, -1]).
    #
    # Case 3) Right circularly polarized light (Stokes vector [1, 0, 0, 1]) yields
    #         linearly polarized -45˚ light (Stokes vector [1, 0, -1, 0]).
    # Case 4) Left circularly polarized light (Stokes vector [1, 0, 0, -1]) yields
    #         linearly polarized +45˚ light (Stokes vector [1, 0, 1, 0]).

    linear_pos = spectrum_from_stokes([1, 0, +1, 0])
    linear_neg = spectrum_from_stokes([1, 0, -1, 0])
    circular_right = spectrum_from_stokes([1, 0, 0, +1])
    circular_left = spectrum_from_stokes([1, 0, 0, -1])

    bsdf = load_string("""<bsdf version='2.0.0' type='retarder'>
                          <spectrum name="theta" value="0"/>
                          <spectrum name="delta" value="90.0"/>
                      </bsdf>""")

    # Incident direction
    wi = [0, 0, 1]

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.wi = wi
    n = [0, 0, 1]
    si.n = n
    si.sh_frame = Frame3f(si.n)

    bs, M = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])

    # Case 1)
    assert ek.allclose(M @ linear_pos, circular_right, atol=1e-3)
    # Case 2)
    assert ek.allclose(M @ linear_neg, circular_left, atol=1e-3)
    # Case 3)
    assert ek.allclose(M @ circular_right, linear_neg, atol=1e-3)
    # Case 4)
    assert ek.allclose(M @ circular_left, linear_pos, atol=1e-3)
Exemplo n.º 10
0
 def make_context(n):
     mi = MediumInteraction3f.zero(n)
     mi.wi = wi
     ek.set_slices(mi.wi, n)
     mi.sh_frame = Frame3f(-mi.wi)
     mi.wavelengths = []
     ctx = PhaseFunctionContext(None)
     return mi, ctx
Exemplo n.º 11
0
    def sample(self, ctx, si, sample1, sample2, active):
        """
        BSDF sampling
        """
        cos_theta_i = Frame3f.cos_theta(si.wi)

        active &= cos_theta_i > 0

        bs = BSDFSample3f()
        bs.wo  = warp.square_to_cosine_hemisphere(sample2)
        bs.pdf = warp.square_to_cosine_hemisphere_pdf(bs.wo)
        bs.eta = 1.0
        bs.sampled_type = +BSDFFlags.DiffuseReflection
        bs.sampled_component = 0

        value = self.get_btf(si.wi, bs.wo, si.uv) / Frame3f.cos_theta(bs.wo)

        return ( bs, ek.select(active & (bs.pdf > 0.0), value, Vector3f(0)) )
def interaction():
    from mitsuba.core import Frame3f
    from mitsuba.render import SurfaceInteraction3f
    si = SurfaceInteraction3f()
    si.t = 0.1
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.sh_frame = Frame3f(si.n)
    return si
Exemplo n.º 13
0
def test01_construction(variant_scalar_rgb):
    from mitsuba.core import Frame3f

    # Uninitialized frame
    _ = Frame3f()

    # Frame3f from the 3 vectors: no normalization should be performed
    f1 = Frame3f([0.005, 50, -6], [0.01, -13.37, 1], [0.5, 0, -6.2])
    assert ek.allclose(f1.s, [0.005, 50, -6])
    assert ek.allclose(f1.t, [0.01, -13.37, 1])
    assert ek.allclose(f1.n, [0.5, 0, -6.2])

    # Frame3f from the Normal component only
    f2 = Frame3f([0, 0, 1])
    assert ek.allclose(f2.s, [1, 0, 0])
    assert ek.allclose(f2.t, [0, 1, 0])
    assert ek.allclose(f2.n, [0, 0, 1])

    # Copy constructor
    f3 = Frame3f(f2)
    assert f2 == f3
def test03_sample_half_wave_local(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Transform4f, Spectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Test polarized implementation. Special case of delta = 180˚, also known
    # as a half-wave plate. (In local BSDF coordinates.)
    #
    # Following "Polarized Light - Fundamentals and Applications" by Edward Collett
    # Chapter 5.3:
    #
    # Case 1 & 2) Switch between diagonal linear polarization states (-45˚ & + 45˚)
    # Case 3 & 4) Switch circular polarization direction

    linear_pos = spectrum_from_stokes([1, 0, +1, 0])
    linear_neg = spectrum_from_stokes([1, 0, -1, 0])
    circular_right = spectrum_from_stokes([1, 0, 0, +1])
    circular_left = spectrum_from_stokes([1, 0, 0, -1])

    bsdf = load_string("""<bsdf version='2.0.0' type='retarder'>
                          <spectrum name="theta" value="0"/>
                          <spectrum name="delta" value="180.0"/>
                      </bsdf>""")

    # Incident direction
    wi = [0, 0, 1]

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.wi = wi
    n = [0, 0, 1]
    si.n = n
    si.sh_frame = Frame3f(si.n)

    bs, M = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])

    # Case 1)
    assert ek.allclose(M @ linear_pos, linear_neg, atol=1e-3)
    # Case 2)
    assert ek.allclose(M @ linear_neg, linear_pos, atol=1e-3)
    # Case 3)
    assert ek.allclose(M @ circular_right, circular_left, atol=1e-3)
    # Case 4)
    assert ek.allclose(M @ circular_left, circular_right, atol=1e-3)
def test01_intersection_construction(variant_scalar_rgb):
    from mitsuba.core import Frame3f
    from mitsuba.render import SurfaceInteraction3f

    si = SurfaceInteraction3f()
    si.shape = None
    si.t = 1
    si.time = 2
    si.wavelengths = []
    si.p = [1, 2, 3]
    si.n = [4, 5, 6]
    si.uv = [7, 8]
    si.sh_frame = Frame3f([9, 10, 11], [12, 13, 14], [15, 16, 17])
    si.dp_du = [18, 19, 20]
    si.dp_dv = [21, 22, 23]
    si.duv_dx = [24, 25]
    si.duv_dy = [26, 27]
    si.wi = [31, 32, 33]
    si.prim_index = 34
    si.instance = None
    assert si.sh_frame == Frame3f([9, 10, 11], [12, 13, 14], [15, 16, 17])
    assert repr(si) == """SurfaceInteraction[
def test05_sample_components(variant_scalar_rgb):
    from mitsuba.core import Frame3f
    from mitsuba.render import BSDFFlags, BSDFContext, SurfaceInteraction3f
    from mitsuba.core.xml import load_string
    from mitsuba.core.math import InvPi

    weight = 0.2

    bsdf = load_string("""<bsdf version="2.0.0" type="blendbsdf">
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="0.0"/>
        </bsdf>
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="1.0"/>
        </bsdf>
        <spectrum name="weight" value="{}"/>
    </bsdf>""".format(weight))

    si = SurfaceInteraction3f()
    si.t = 0.1
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.sh_frame = Frame3f(si.n)
    si.wi = [0, 0, 1]

    ctx = BSDFContext()

    # Sample specific components separately using two different values of 'sample1'
    # and make sure the desired component is chosen always.

    ctx.component = 0

    expected_a = (1-weight)*0.0    # InvPi will cancel out with sampling pdf, but still need to apply weight
    bs_a, weight_a = bsdf.sample(ctx, si, 0.1, [0.5, 0.5])
    assert ek.allclose(weight_a, expected_a)

    expected_b = (1-weight)*0.0    # InvPi will cancel out with sampling pdf, but still need to apply weight
    bs_b, weight_b = bsdf.sample(ctx, si, 0.3, [0.5, 0.5])
    assert ek.allclose(weight_b, expected_b)

    ctx.component = 1

    expected_a = weight*1.0    # InvPi will cancel out with sampling pdf, but still need to apply weight
    bs_a, weight_a = bsdf.sample(ctx, si, 0.1, [0.5, 0.5])
    assert ek.allclose(weight_a, expected_a)

    expected_b = weight*1.0    # InvPi will cancel out with sampling pdf, but still need to apply weight
    bs_b, weight_b = bsdf.sample(ctx, si, 0.3, [0.5, 0.5])
    assert ek.allclose(weight_b, expected_b)
Exemplo n.º 17
0
    def sample(self, ctx, si, sample1, sample2, active):
        cos_theta_i = Frame3f.cos_theta(si.wi)

        active &= cos_theta_i > 0

        bs = BSDFSample3f()
        bs.wo = mr.retro_transmit(si.wi)
        bs.pdf = 1
        bs.sampled_type = +BSDFFlags.DeltaTransmission
        bs.sampled_component = 0
        bs.eta = 1

        value = self.m_retro_transmittance.eval(si, active)

        return (bs, ek.select(active, value, 0))
Exemplo n.º 18
0
    def sample_ray(
            self,
            time,
            sample1,  # wavelength
            sample2,  # pos
            sample3,  # dir
            active):

        ps = self.m_shape.sample_position(time, sample2, active)
        local = warp.square_to_cosine_hemisphere(sample3)

        si = SurfaceInteraction3f(ps, 0)
        wavelengths, spec_weight = self.m_radiance.sample(
            si, ek.arange(sample1), active)

        ray = Ray3f(ps.p, Frame3f(ps.n).to_world(local), time, wavelengths)
        return (ray, spec_weight * self.m_area_times_pi)
Exemplo n.º 19
0
def test02_eval_pdf(variant_scalar_rgb):
    from mitsuba.core import Frame3f
    from mitsuba.render import BSDFContext, BSDFFlags, SurfaceInteraction3f
    from mitsuba.core.xml import load_string

    bsdf = load_string("<bsdf version='2.0.0' type='diffuse'></bsdf>")

    si    = SurfaceInteraction3f()
    si.p  = [0, 0, 0]
    si.n  = [0, 0, 1]
    si.wi = [0, 0, 1]
    si.sh_frame = Frame3f(si.n)

    ctx = BSDFContext()

    for i in range(20):
        theta = i / 19.0 * (ek.pi / 2)
        wo = [ek.sin(theta), 0, ek.cos(theta)]

        v_pdf  = bsdf.pdf(ctx, si, wo=wo)
        v_eval = bsdf.eval(ctx, si, wo=wo)[0]
        assert ek.allclose(v_pdf, wo[2] / ek.pi)
        assert ek.allclose(v_eval, 0.5 * wo[2] / ek.pi)
def test04_sample_all(variant_scalar_rgb):
    from mitsuba.core import Frame3f
    from mitsuba.render import BSDFFlags, BSDFContext, SurfaceInteraction3f
    from mitsuba.core.xml import load_string
    from mitsuba.core.math import InvPi

    weight = 0.2

    bsdf = load_string("""<bsdf version="2.0.0" type="blendbsdf">
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="0.0"/>
        </bsdf>
        <bsdf type="diffuse">
            <spectrum name="reflectance" value="1.0"/>
        </bsdf>
        <spectrum name="weight" value="{}"/>
    </bsdf>""".format(weight))

    si = SurfaceInteraction3f()
    si.t = 0.1
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.sh_frame = Frame3f(si.n)
    si.wi = [0, 0, 1]

    ctx = BSDFContext()

    # Sample using two different values of 'sample1' and make sure correct
    # components are chosen.

    expected_a = 1.0    # InvPi & weight will cancel out with sampling pdf
    bs_a, weight_a = bsdf.sample(ctx, si, 0.1, [0.5, 0.5])
    assert ek.allclose(weight_a, expected_a)

    expected_b = 0.0    # InvPi & weight will cancel out with sampling pdf
    bs_b, weight_b = bsdf.sample(ctx, si, 0.3, [0.5, 0.5])
    assert ek.allclose(weight_b, expected_b)
Exemplo n.º 21
0
def test01_roughplastic(variant_scalar_rgb):
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDF, BSDFContext, SurfaceInteraction3f
    from mitsuba.core import Frame3f

    thetas = np.linspace(0, np.pi / 2, 20)
    phi = np.pi

    values_ref = []

    # Create plastic reference BSDF
    bsdf = load_string("""<bsdf version="2.0.0" type="roughplastic">
                              <spectrum name="diffuse_reflectance" value="0.5"/>
                              <float name="alpha" value="0.3"/>
                              <string name="distribution" value="beckmann"/>
                              <float name="int_ior" value="1.5"/>
                              <float name="ext_ior" value="1.0"/>
                              <boolean name="nonlinear" value="true"/>
                          </bsdf>""")

    theta_i = np.radians(30.0)
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.wi = [np.sin(theta_i), 0, np.cos(theta_i)]
    si.sh_frame = Frame3f(si.n)
    ctx = BSDFContext()

    for theta in thetas:
        wo = [
            np.sin(theta) * np.cos(phi),
            np.sin(theta) * np.sin(phi),
            np.cos(theta)
        ]
        values_ref.append(bsdf.eval(ctx, si, wo=wo)[0])

    # Create same BSDF as layer representation by applying adding equations
    n, ms, md = mitsuba.layer.microfacet_parameter_heuristic(0.3, 0.3, 1.5)
    mu, w = mitsuba.core.quad.gauss_lobatto(n)

    coating = mitsuba.layer.Layer(mu, w, ms, md)
    coating.set_microfacet(1.5, 0.3, 0.3)
    base = mitsuba.layer.Layer(mu, w, ms, md)
    base.set_diffuse(0.5)

    layer = mitsuba.layer.Layer.add(coating, base)

    for i, theta in enumerate(thetas):
        l_eval = layer.eval(-np.cos(theta), np.cos(theta_i)) * np.abs(
            np.cos(theta))
        # Values should be close (except if they are insignificantly small).
        # We have less precision at grazing angles because of Fourier representation.
        assert values_ref[i] < 1e-5 or np.allclose(
            values_ref[i], l_eval, rtol=0.05 / (np.abs(np.cos(theta))))

    # Convert into BSDF storage representation
    base_path = os.path.dirname(os.path.realpath(__file__)) + "/data/"
    if not os.path.exists(base_path):
        os.makedirs(base_path)
    path = base_path + "roughplastic.bsdf"
    storage = mitsuba.layer.BSDFStorage.from_layer(path, layer, 1e-5)

    for i, theta in enumerate(thetas):
        s_eval = storage.eval(np.cos(theta_i), -np.cos(theta))[0]
        # Values should be close (except if they are insignificantly small).
        # We have less precision at grazing angles because of Fourier representation.
        assert values_ref[i] < 1e-5 or np.allclose(
            values_ref[i], s_eval, rtol=0.05 / (np.abs(np.cos(theta))))
    storage.close()

    # And load via the "fourier" BSDF plugin
    fourier = load_string("""<bsdf version="2.0.0" type="fourier">
                                 <string name="filename" value="{}"/>
                             </bsdf>""".format(path))

    for i, theta in enumerate(thetas):
        wo = [
            np.sin(theta) * np.cos(phi),
            np.sin(theta) * np.sin(phi),
            np.cos(theta)
        ]
        f_eval = fourier.eval(ctx, si, wo=wo)[0]
        assert values_ref[i] < 1e-5 or np.allclose(
            values_ref[i], f_eval, rtol=0.05 / (np.abs(np.cos(theta))))
    del fourier
Exemplo n.º 22
0
def test02_unit_frame(variant_scalar_rgb):
    from mitsuba.core import Frame3f, Vector2f, Vector3f

    for theta in [30 * mitsuba.core.math.Pi / 180, 95 * mitsuba.core.math.Pi / 180]:
        phi = 73 * mitsuba.core.math.Pi / 180
        sin_theta, cos_theta = ek.sin(theta), ek.cos(theta)
        sin_phi, cos_phi = ek.sin(phi), ek.cos(phi)

        v = Vector3f(
            cos_phi * sin_theta,
            sin_phi * sin_theta,
            cos_theta
        )
        f = Frame3f(Vector3f(1.0, 2.0, 3.0) / ek.sqrt(14))

        v2 = f.to_local(v)
        v3 = f.to_world(v2)

        assert ek.allclose(v3, v)

        assert ek.allclose(Frame3f.cos_theta(v), cos_theta)
        assert ek.allclose(Frame3f.sin_theta(v), sin_theta)
        assert ek.allclose(Frame3f.cos_phi(v), cos_phi)
        assert ek.allclose(Frame3f.sin_phi(v), sin_phi)
        assert ek.allclose(Frame3f.cos_theta_2(v), cos_theta * cos_theta)
        assert ek.allclose(Frame3f.sin_theta_2(v), sin_theta * sin_theta)
        assert ek.allclose(Frame3f.cos_phi_2(v), cos_phi * cos_phi)
        assert ek.allclose(Frame3f.sin_phi_2(v), sin_phi * sin_phi)
        assert ek.allclose(Vector2f(Frame3f.sincos_phi(v)), [sin_phi, cos_phi])
        assert ek.allclose(Vector2f(Frame3f.sincos_phi_2(v)), [sin_phi * sin_phi, cos_phi * cos_phi])
Exemplo n.º 23
0
def test02_sample_local(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Transform4f, Spectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Test polarized implementation, version in local BSDF coordinate system
    # (surface normal aligned with "z").
    #
    # The polarizer is rotated to different angles and hit with fully
    # unpolarized light (Stokes vector [1, 0, 0, 0]).
    # We then test if the outgoing Stokes vector corresponds to the expected
    # rotation of linearly polarized light (Case 1).
    #
    # Additionally, a perfect linear polarizer should be invariant to "tilting",
    # i.e. rotations around "x" or "z" in this local frame (Case 2, 3).

    # Incident direction
    wi = [0, 0, 1]
    stokes_in = spectrum_from_stokes([1, 0, 0, 0])

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.wi = wi
    n = [0, 0, 1]
    si.n = n
    si.sh_frame = Frame3f(si.n)

    # Polarizer rotation angles
    angles = [0, 90, +45, -45]
    # Expected outgoing Stokes vector
    expected_states = [spectrum_from_stokes([0.5,  0.5,  0,   0]),
                       spectrum_from_stokes([0.5, -0.5,  0,   0]),
                       spectrum_from_stokes([0.5,  0,   +0.5, 0]),
                       spectrum_from_stokes([0.5,  0,   -0.5, 0])]

    for k in range(len(angles)):
        angle = angles[k]
        expected = expected_states[k]

        bsdf = load_string("""<bsdf version='2.0.0' type='polarizer'>
                                  <spectrum name="theta" value="{}"/>
                              </bsdf>""".format(angle))

        # Case 1: Perpendicular incidence.
        bs, M = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])

        stokes_out = M @ stokes_in
        assert ek.allclose(expected, stokes_out, atol=1e-3)

        def rotate_vector(v, axis, angle):
            return Transform4f.rotate(axis, angle).transform_vector(v)

        # Case 2: Tilt polarizer around "x". Should not change anything.
        # (Note: to stay with local coordinates, we rotate the incident direction instead.)
        si.wi = rotate_vector(wi, [1, 0, 0], angle=30.0)
        bs, M = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
        stokes_out = M @ stokes_in
        assert ek.allclose(expected, stokes_out, atol=1e-3)

        # Case 3: Tilt polarizer around "y". Should not change anything.
        # (Note: to stay with local coordinates, we rotate the incident direction instead.)
        si.wi = rotate_vector(wi, [0, 1, 0], angle=30.0)
        bs, M = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
        stokes_out = M @ stokes_in
        assert ek.allclose(expected, stokes_out, atol=1e-3)
Exemplo n.º 24
0
def test01_diffuse(variant_scalar_rgb):
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDF, BSDFContext, SurfaceInteraction3f
    from mitsuba.core import Frame3f

    thetas = np.linspace(0, np.pi / 2, 20)
    phi = np.pi

    values_ref = []

    # Create diffuse reference BSDF
    bsdf = load_string("""<bsdf version="2.0.0" type="diffuse">
                              <spectrum name="reflectance" value="0.5"/>
                          </bsdf>""")

    theta_i = np.radians(30.0)
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.n = [0, 0, 1]
    si.wi = [np.sin(theta_i), 0, np.cos(theta_i)]
    si.sh_frame = Frame3f(si.n)
    ctx = BSDFContext()

    for theta in thetas:
        wo = [
            np.sin(theta) * np.cos(phi),
            np.sin(theta) * np.sin(phi),
            np.cos(theta)
        ]
        values_ref.append(bsdf.eval(ctx, si, wo=wo)[0])

    # Create same BSDF as layer representation
    n = 100
    ms = 1
    md = 1
    mu, w = mitsuba.core.quad.gauss_lobatto(n)
    layer = mitsuba.layer.Layer(mu, w, ms, md)
    layer.set_diffuse(0.5)

    for i, theta in enumerate(thetas):
        l_eval = layer.eval(-np.cos(theta), np.cos(theta_i)) * np.abs(
            np.cos(theta))
        # Values should be close (except if they are insignificantly small).
        # We have less precision at grazing angles because of Fourier representation.
        assert np.allclose(values_ref[i], l_eval, rtol=0.01)

    # Convert into BSDF storage representation
    base_path = os.path.dirname(os.path.realpath(__file__)) + "/data/"
    if not os.path.exists(base_path):
        os.makedirs(base_path)
    path = base_path + "diffuse.bsdf"
    storage = mitsuba.layer.BSDFStorage.from_layer(path, layer, 1e-5)

    for i, theta in enumerate(thetas):
        s_eval = storage.eval(np.cos(theta_i), -np.cos(theta))[0]
        # Values should be close (except if they are insignificantly small).
        # We have less precision at grazing angles because of Fourier representation.
        assert np.allclose(values_ref[i], s_eval, rtol=0.01)
    storage.close()

    # And load via the "fourier" BSDF plugin
    fourier = load_string("""<bsdf version="2.0.0" type="fourier">
                                 <string name="filename" value="{}"/>
                             </bsdf>""".format(path))

    for i, theta in enumerate(thetas):
        wo = [
            np.sin(theta) * np.cos(phi),
            np.sin(theta) * np.sin(phi),
            np.cos(theta)
        ]
        f_eval = fourier.eval(ctx, si, wo=wo)[0]
        assert np.allclose(values_ref[i], f_eval, rtol=0.02)
    del fourier
Exemplo n.º 25
0
def outgoing_direction(n_phi,
                       n_theta,
                       Dvis_sampler,
                       theta_i,
                       phi_i,
                       isotropic,
                       theta_max=np.pi / 2,
                       all=False):
    from mitsuba.core import Vector2f, Frame3f
    from mitsuba.core import MarginalContinuous2D2

    print("Max theta angle is %f deg." % np.degrees(theta_max))

    phi_o = np.zeros((phi_i.size, theta_i.size, n_phi, n_theta))
    theta_o = np.zeros((phi_i.size, theta_i.size, n_phi, n_theta))
    invalid = np.ones((phi_i.size, theta_i.size, n_phi, n_theta), dtype='bool')
    active = np.ones((phi_i.size, theta_i.size, n_phi, n_theta), dtype='bool')

    # Create uniform samples
    u_0 = np.linspace(0, 1, n_theta)
    u_1 = np.linspace(0, 1, n_phi)
    samples = Vector2f(np.tile(u_0, n_phi), np.repeat(u_1, n_theta))

    # Construct projected surface area interpolant data structure
    params = [phi_i.tolist(), theta_i.tolist()]
    m_vndf = MarginalContinuous2D2(Dvis_sampler, params, normalize=True)

    for i in range(phi_i.size):
        for j in range(theta_i.size):
            # Warp uniform samples by VNDF distribution (G1 mapping)
            u_m, ndf_pdf = m_vndf.sample(samples, [phi_i[i], theta_i[j]])
            # Convert samples to radians (G2 mapping)
            theta_m = u2theta(u_m.x)  # [0, 1] -> [0, pi]
            phi_m = u2phi(u_m.y)  # [0, 1] -> [0, 2pi]
            if isotropic:
                phi_m += phi_i[i]
            # Phase vector
            m = spherical2cartesian(theta_m, phi_m)
            # Incident direction
            wi = spherical2cartesian(theta_i[j], phi_i[i])
            # Outgoing direction (reflection over phase vector)
            wo = ek.fmsub(m, 2.0 * ek.dot(m, wi), wi)
            tmp1, tmp2 = cartesian2spherical(wo)
            # Remove invalid directions
            act = u_m.y > 0  # covered twice [-pi = pi]
            inv = Frame3f.cos_theta(wo) < 0  # below surface plane
            act &= np.invert(inv)  # above surface plane
            act &= tmp1 <= (theta_max + EPSILON)  # further angular restriction
            if isotropic:
                act &= tmp2 >= 0
            if not all:
                tmp1[~act] = 0
                tmp2[~act] = 0
            else:
                tmp1[inv] = 0
                tmp2[inv] = 0

            # Fit to datashape
            act = np.reshape(act, (n_phi, n_theta))
            inv = np.reshape(inv, (n_phi, n_theta))
            tmp1 = np.reshape(tmp1, (n_phi, n_theta))
            tmp2 = np.reshape(tmp2, (n_phi, n_theta))

            # Append
            active[i, j] = act
            invalid[i, j] = inv
            theta_o[i, j] = tmp1
            phi_o[i, j] = tmp2
    return [theta_o, phi_o, active, invalid]
def test02_sample_pol_world(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Spectrum, UnpolarizedSpectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f, fresnel_conductor
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Create a Silver conductor BSDF and reflect different polarization states
    # at a 45˚ angle.
    # This test takes place in world coordinates and thus involves additional
    # coordinate system rotations.
    #
    # The setup is as follows:
    # - The mirror is positioned at [0, 0, 0], angled s.t. the surface normal
    #   is along [1, 1, 0].
    # - Incident ray w1 = [-1, 0, 0] strikes the mirror at a 45˚ angle and
    #   reflects into direction w2 = [0, 1, 0]
    # - The corresponding Stokes bases are b1 = [0, 1, 0] and b2 = [1, 0, 0].

    # Setup
    eta = 0.136125 + 4.010625j  # IoR values of Ag at 635.816284nm
    bsdf = load_string("""<bsdf version='2.0.0' type='conductor'>
                              <spectrum name="eta" value="{}"/>
                              <spectrum name="k" value="{}"/>
                          </bsdf>""".format(eta.real, eta.imag))
    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.n = ek.normalize([1.0, 1.0, 0.0])
    si.sh_frame = Frame3f(si.n)

    # Incident / outgoing directions and their Stokes bases
    w1 = ek.scalar.Vector3f([-1, 0, 0])
    b1 = [0, 1, 0]
    w2 = ek.scalar.Vector3f([0, 1, 0])
    b2 = [1, 0, 0]

    # Perform actual reflection
    si.wi = si.to_local(-w1)
    bs, M_tmp = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
    M_world = si.to_world_mueller(M_tmp, -si.wi, bs.wo)

    # Test that outgoing direction is as predicted
    assert ek.allclose(si.to_world(bs.wo), w2, atol=1e-5)

    # Align reference frames s.t. polarization is expressed w.r.t. b1 & b2
    M_world = rotate_mueller_basis(M_world, w1, stokes_basis(w1), b1, w2,
                                   stokes_basis(w2), b2)

    # Apply to unpolarized light and verify that it is equivalent to normal Fresnel
    a0 = M_world @ spectrum_from_stokes([1, 0, 0, 0])
    F = fresnel_conductor(si.wi[2], ek.scalar.Complex2f(eta.real, eta.imag))
    a0 = a0[0, 0]
    assert ek.allclose(a0[0], F, atol=1e-3)

    # Apply to horizontally polarized light (linear at 0˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still horizontally polarized
    a1 = M_world @ spectrum_from_stokes([1, +1, 0, 0])
    assert ek.allclose(a1[0, 0], ek.abs(a1[1, 0]), atol=1e-3)  # 1)
    a1 /= a1[0, 0]
    assert ek.allclose(a1, spectrum_from_stokes([1, 1, 0, 0]), atol=1e-3)  # 2)

    # Apply to vertically polarized light (linear at 90˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still vertically polarized
    a2 = M_world @ spectrum_from_stokes([1, -1, 0, 0])
    assert ek.allclose(a2[0, 0], ek.abs(a2[1, 0]), atol=1e-3)  # 1)
    a2 /= a2[0, 0]
    assert ek.allclose(a2, spectrum_from_stokes([1, -1, 0, 0]),
                       atol=1e-3)  # 2)

    # Apply to (positive) diagonally polarized light (linear at +45˚)
    # Test that..
    # 1) The polarization is flipped to -45˚
    # 2) There is now also some (left) circular polarization
    a3 = M_world @ spectrum_from_stokes([1, 0, +1, 0])
    assert ek.all(a3[2, 0] < UnpolarizedSpectrum(0.0))
    assert ek.all(a3[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to (negative) diagonally polarized light (linear at -45˚)
    # Test that..
    # 1) The polarization is flipped to +45˚
    # 2) There is now also some (right) circular polarization
    a4 = M_world @ spectrum_from_stokes([1, 0, -1, 0])
    assert ek.all(a4[2, 0] > UnpolarizedSpectrum(0.0))
    assert ek.all(a4[3, 0] > UnpolarizedSpectrum(0.0))

    # Apply to right circularly polarized light
    # Test that the polarization is flipped to left circular
    a5 = M_world @ spectrum_from_stokes([1, 0, 0, +1])
    assert ek.all(a5[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to left circularly polarized light
    # Test that the polarization is flipped to right circular
    a6 = M_world @ spectrum_from_stokes([1, 0, 0, -1])
    assert ek.all(a6[3, 0] > UnpolarizedSpectrum(0.0))
Exemplo n.º 27
0
def test02_roughconductor(variant_scalar_rgb):
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDF, BSDFContext, SurfaceInteraction3f
    from mitsuba.core import Frame3f

    for alpha in [(0.3, 0.3), (0.3 + 1e-5, 0.3 - 1e-5), (0.2, 0.4)]:
        alpha_u = alpha[0]
        alpha_v = alpha[1]

        thetas = np.linspace(0, np.pi / 2, 20)
        phi = np.pi

        values_ref = []

        # Create conductor reference BSDF
        bsdf = load_string("""<bsdf version="2.0.0" type="roughconductor">
                                  <float name="alpha_u" value="{}"/>
                                  <float name="alpha_v" value="{}"/>
                                  <string name="distribution" value="beckmann"/>
                                  <spectrum name="eta" value="0.0"/>
                                  <spectrum name="k" value="1.0"/>
                              </bsdf>""".format(alpha_u, alpha_v))

        theta_i = np.radians(30.0)
        si = SurfaceInteraction3f()
        si.p = [0, 0, 0]
        si.n = [0, 0, 1]
        si.wi = [np.sin(theta_i), 0, np.cos(theta_i)]
        si.sh_frame = Frame3f(si.n)
        ctx = BSDFContext()

        for theta in thetas:
            wo = [
                np.sin(theta) * np.cos(phi),
                np.sin(theta) * np.sin(phi),
                np.cos(theta)
            ]
            values_ref.append(bsdf.eval(ctx, si, wo=wo)[0])

        # Create same BSDF as layer representation
        n, ms, md = mitsuba.layer.microfacet_parameter_heuristic(
            alpha_u, alpha_v, 0 + 1j)
        mu, w = mitsuba.core.quad.gauss_lobatto(n)
        layer = mitsuba.layer.Layer(mu, w, ms, md)
        layer.set_microfacet(0 + 1j, alpha_u, alpha_v)

        for i, theta in enumerate(thetas):
            l_eval = layer.eval(-np.cos(theta), np.cos(theta_i)) * np.abs(
                np.cos(theta))
            # Values should be close (except if they are insignificantly small).
            # We have less precision at grazing angles because of Fourier representation.
            print(values_ref[i], l_eval)
            assert values_ref[i] < 1e-5 or np.allclose(
                values_ref[i], l_eval, rtol=0.05 / (np.abs(np.cos(theta))))

        # Convert into BSDF storage representation
        base_path = os.path.dirname(os.path.realpath(__file__)) + "/data/"
        if not os.path.exists(base_path):
            os.makedirs(base_path)
        path = base_path + "roughconductor.bsdf"
        storage = mitsuba.layer.BSDFStorage.from_layer(path, layer, 1e-8)

        for i, theta in enumerate(thetas):
            s_eval = storage.eval(np.cos(theta_i), -np.cos(theta))[0]
            # Values should be close (except if they are insignificantly small).
            # We have less precision at grazing angles because of Fourier representation.
            assert values_ref[i] < 1e-5 or np.allclose(
                values_ref[i], s_eval, rtol=0.05 / (np.abs(np.cos(theta))))
        storage.close()

        # And load via the "fourier" BSDF plugin
        fourier = load_string("""<bsdf version="2.0.0" type="fourier">
                                     <string name="filename" value="{}"/>
                                 </bsdf>""".format(path))

        for i, theta in enumerate(thetas):
            wo = [
                np.sin(theta) * np.cos(phi),
                np.sin(theta) * np.sin(phi),
                np.cos(theta)
            ]
            f_eval = fourier.eval(ctx, si, wo=wo)[0]
            assert values_ref[i] < 1e-5 or np.allclose(
                values_ref[i], f_eval, rtol=0.05 / (np.abs(np.cos(theta))))
        del fourier
Exemplo n.º 28
0
def render_sample(scene, sampler, rays, bdata, heightmap_pybind, bssrdf=None):
    """
    Sample RTE
    TODO: Support multi channel sampling

    Args:
        scene: Target scene object
        sampler: Sampler object for random number
        rays: Given rays for sampling
        bdata: BSSRDF Data object
        heightmap_pybind: Object for getting height map around incident position.
                          Refer src/librender/python/heightmap.cpp

    Returns:
        result: Sampling RTE result
        valid_rays: Mask data whether rays are valid or not
        scatter: Scatter components of Sampling RTE result
        non_scatter: Non scatter components of Sampling RTE result
        invalid_sample: Sampling RTE result with invalid sampled data by VAEBSSRDF
    """

    eta = Float(1.0)
    emission_weight = Float(1.0)
    throughput = Spectrum(1.0)
    result = Spectrum(0.0)
    scatter = Spectrum(0.0)
    non_scatter = Spectrum(0.0)
    invalid_sample = Spectrum(0.0)
    active = True
    is_bssrdf = False

    ##### First interaction #####
    si = scene.ray_intersect(rays, active)
    active = si.is_valid() & active
    valid_rays = si.is_valid()

    emitter = si.emitter(scene, active)

    depth = 0

    # Set channel
    # At and after evaluating BSSRDF, a ray consider only this one channel
    n_channels = 3
    channel = UInt32(
        ek.min(sampler.next_1d(active) * n_channels, n_channels - 1))

    d_out_local = Vector3f().zero()
    d_out_pdf = Float(0)

    sss = Mask(False)

    while (True):
        depth += 1
        if config.aovs and depth == 2:
            sss = is_bssrdf

        ##### Interaction with emitters #####
        emission_val = emission_weight * throughput * Emitter.eval_vec(
            emitter, si, active)

        result += ek.select(active, emission_val, Spectrum(0.0))
        invalid_sample += ek.select(active, emission_val, Spectrum(0.0))
        scatter += ek.select(active & sss, emission_val, Spectrum(0.0))
        non_scatter += ek.select(active & ~sss, emission_val, Spectrum(0.0))

        active = active & si.is_valid()

        # Process russian roulette
        if depth > config.rr_depth:
            q = ek.min(ek.hmax(throughput) * ek.sqr(eta), 0.95)
            active = active & (sampler.next_1d(active) < q)
            throughput *= ek.rcp(q)

        # Stop if the number of bouces exceeds the given limit bounce, or
        # all rays are invalid. latter check is done only when the limit
        # bounce is infinite
        if depth >= config.max_depth:
            break

        ##### Emitter sampling #####
        bsdf = si.bsdf(rays)
        ctx = BSDFContext()

        active_e = active & has_flag(BSDF.flags_vec(bsdf), BSDFFlags.Smooth)
        ds, emitter_val = scene.sample_emitter_direction(
            si, sampler.next_2d(active_e), True, active_e)
        active_e &= ek.neq(ds.pdf, 0.0)

        # Query the BSDF for that emitter-sampled direction
        wo = si.to_local(ds.d)
        bsdf_val = BSDF.eval_vec(bsdf, ctx, si, wo, active_e)
        # Determine density of sampling that same direction using BSDF sampling
        bsdf_pdf = BSDF.pdf_vec(bsdf, ctx, si, wo, active_e)

        mis = ek.select(ds.delta, Float(1), mis_weight(ds.pdf, bsdf_pdf))

        emission_val = mis * throughput * bsdf_val * emitter_val

        result += ek.select(active, emission_val, Spectrum(0.0))
        invalid_sample += ek.select(active, emission_val, Spectrum(0.0))
        scatter += ek.select(active & sss, emission_val, Spectrum(0.0))
        non_scatter += ek.select(active & ~sss, emission_val, Spectrum(0.0))

        ##### BSDF sampling #####
        bs, bsdf_val = BSDF.sample_vec(bsdf, ctx, si, sampler.next_1d(active),
                                       sampler.next_2d(active), active)

        ##### BSSRDF replacing #####
        if (config.enable_bssrdf):
            # Replace bsdf samples by ones of BSSRDF
            bs.wo = ek.select(is_bssrdf, d_out_local, bs.wo)
            bs.pdf = ek.select(is_bssrdf, d_out_pdf, bs.pdf)
            bs.sampled_component = ek.select(is_bssrdf, UInt32(1),
                                             bs.sampled_component)
            bs.sampled_type = ek.select(is_bssrdf,
                                        UInt32(+BSDFFlags.DeltaTransmission),
                                        bs.sampled_type)
        ############################

        throughput *= ek.select(is_bssrdf, Float(1.0), bsdf_val)
        active &= ek.any(ek.neq(throughput, 0))

        eta *= bs.eta

        # Intersect the BSDF ray against the scene geometry
        rays = RayDifferential3f(si.spawn_ray(si.to_world(bs.wo)))
        si_bsdf = scene.ray_intersect(rays, active)

        ##### Checking BSSRDF #####
        if (config.enable_bssrdf):
            # Whether the BSDF is BSS   RDF or not?
            is_bssrdf = (active
                         & has_flag(BSDF.flags_vec(bsdf), BSDFFlags.BSSRDF)
                         & (Frame3f.cos_theta(bs.wo) < Float(0.0))
                         & (Frame3f.cos_theta(si.wi) > Float(0.0)))

            # Decide whether we should use 0-scattering or multiple scattering
            is_zero_scatter = utils_render.check_zero_scatter(
                sampler, si_bsdf, bs, channel, is_bssrdf)
            is_bssrdf = is_bssrdf & ~is_zero_scatter

            throughput *= ek.select(is_bssrdf, ek.sqr(bs.eta), Float(1.0))
        ###########################

        ###### Process for BSSRDF ######
        if (config.enable_bssrdf and not ek.none(is_bssrdf)):
            # Get projected samples from BSSRDF
            projected_si, project_suc, abs_prob = bssrdf.sample_bssrdf(
                scene, bsdf, bs, si, bdata, heightmap_pybind, channel,
                is_bssrdf)

            if config.visualize_invalid_sample and (depth <= 1):
                active = active & (~is_bssrdf | project_suc)
                invalid_sample += ek.select((is_bssrdf & (~project_suc)),
                                            Spectrum([100, 0, 0]),
                                            Spectrum(0.0))

            # Sample outgoing direction from projected position
            d_out_local, d_out_pdf = utils_render.resample_wo(
                sampler, is_bssrdf)
            # Apply absorption probability
            throughput *= ek.select(is_bssrdf,
                                    Spectrum(1) - abs_prob, Spectrum(1))
            # Replace interactions by sampled ones from BSSRDF
            si_bsdf = SurfaceInteraction3f().masked_si(si_bsdf, projected_si,
                                                       is_bssrdf)
        ################################

        # Determine probability of having sampled that same
        # direction using emitter sampling
        emitter = si_bsdf.emitter(scene, active)
        ds = DirectionSample3f(si_bsdf, si)
        ds.object = emitter

        delta = has_flag(bs.sampled_type, BSDFFlags.Delta)
        emitter_pdf = ek.select(delta, Float(0.0),
                                scene.pdf_emitter_direction(si, ds))
        emission_weight = mis_weight(bs.pdf, emitter_pdf)

        si = si_bsdf

    return result, valid_rays, scatter, non_scatter, invalid_sample
def test02_sample_pol_local(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Transform4f, Spectrum, UnpolarizedSpectrum, Vector3f
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f, fresnel_conductor
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Create a Silver conductor BSDF and reflect different polarization states
    # at a 45˚ angle.
    # All tests take place directly in local BSDF coordinates. Here we only
    # want to make sure that the output of this looks like what you would
    # expect from a Mueller matrix describing specular reflection on a mirror.

    eta = 0.136125 + 4.010625j  # IoR values of Ag at 635.816284nm
    bsdf = load_string("""<bsdf version='2.0.0' type='conductor'>
                              <spectrum name="eta" value="{}"/>
                              <spectrum name="k" value="{}"/>
                          </bsdf>""".format(eta.real, eta.imag))

    theta_i = 45 * ek.pi / 180
    wi = Vector3f([-ek.sin(theta_i), 0, ek.cos(theta_i)])

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.wi = wi
    n = [0, 0, 1]
    si.sh_frame = Frame3f(n)

    bs, M_local = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
    wo = bs.wo

    # Rotate into standard coordinates for specular reflection
    bi_s = ek.normalize(ek.cross(n, -wi))
    bi_p = ek.normalize(ek.cross(-wi, bi_s))
    bo_s = ek.normalize(ek.cross(n, wo))
    bo_p = ek.normalize(ek.cross(wo, bi_s))

    M_local = rotate_mueller_basis(M_local, -wi, stokes_basis(-wi), bi_p, wo,
                                   stokes_basis(wo), bo_p)

    # Apply to unpolarized light and verify that it is equivalent to normal Fresnel
    a0 = M_local @ spectrum_from_stokes([1, 0, 0, 0])
    F = fresnel_conductor(ek.cos(theta_i),
                          ek.scalar.Complex2f(eta.real, eta.imag))
    a0 = a0[0, 0]
    assert ek.allclose(a0[0], F, atol=1e-3)

    # Apply to horizontally polarized light (linear at 0˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still horizontally polarized
    a1 = M_local @ spectrum_from_stokes([1, +1, 0, 0])
    assert ek.allclose(a1[0, 0], ek.abs(a1[1, 0]), atol=1e-3)  # 1)
    a1 /= a1[0, 0]
    assert ek.allclose(a1, spectrum_from_stokes([1, 1, 0, 0]), atol=1e-3)  # 2)

    # Apply to vertically polarized light (linear at 90˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still vertically polarized
    a2 = M_local @ spectrum_from_stokes([1, -1, 0, 0])
    assert ek.allclose(a2[0, 0], ek.abs(a2[1, 0]), atol=1e-3)  # 1)
    a2 /= a2[0, 0]
    assert ek.allclose(a2, spectrum_from_stokes([1, -1, 0, 0]),
                       atol=1e-3)  # 2)

    # Apply to (positive) diagonally polarized light (linear at +45˚)
    # Test that..
    # 1) The polarization is flipped to -45˚
    # 2) There is now also some (left) circular polarization
    a3 = M_local @ spectrum_from_stokes([1, 0, +1, 0])
    assert ek.all(a3[2, 0] < UnpolarizedSpectrum(0.0))
    assert ek.all(a3[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to (negative) diagonally polarized light (linear at -45˚)
    # Test that..
    # 1) The polarization is flipped to +45˚
    # 2) There is now also some (right) circular polarization
    a4 = M_local @ spectrum_from_stokes([1, 0, -1, 0])
    assert ek.all(a4[2, 0] > UnpolarizedSpectrum(0.0))
    assert ek.all(a4[3, 0] > UnpolarizedSpectrum(0.0))

    # Apply to right circularly polarized light
    # Test that the polarization is flipped to left circular
    a5 = M_local @ spectrum_from_stokes([1, 0, 0, +1])
    assert ek.all(a5[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to left circularly polarized light
    # Test that the polarization is flipped to right circular
    a6 = M_local @ spectrum_from_stokes([1, 0, 0, -1])
    assert ek.all(a6[3, 0] > UnpolarizedSpectrum(0.0))