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)
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 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)
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)
예제 #5
0
def integrator_sample(scene, sampler, rays, medium, active=True):
    si = scene.ray_intersect(rays)
    active = si.is_valid() & active

    # Visible emitters
    emitter_vis = si.emitter(scene, active)
    result = ek.select(active, Emitter.eval_vec(emitter_vis, si, active), Vector3f(0.0))

    ctx = BSDFContext()
    bsdf = si.bsdf(rays)

    # Emitter sampling
    sample_emitter = active & has_flag(BSDF.flags_vec(bsdf), BSDFFlags.Smooth)
    ds, emitter_val = scene.sample_emitter_direction(si, sampler.next_2d(sample_emitter), True, sample_emitter)
    active_e = sample_emitter & ek.neq(ds.pdf, 0.0)
    wo = si.to_local(ds.d)
    bsdf_val = BSDF.eval_vec(bsdf, ctx, si, wo, active_e)
    bsdf_pdf = BSDF.pdf_vec(bsdf, ctx, si, wo, active_e)
    mis = ek.select(ds.delta, Float(1), mis_weight(ds.pdf, bsdf_pdf))
    result += ek.select(active_e, emitter_val * bsdf_val * mis, Vector3f(0))

    # BSDF sampling
    active_b = active
    bs, bsdf_val = BSDF.sample_vec(bsdf, ctx, si, sampler.next_1d(active), sampler.next_2d(active), active_b)
    si_bsdf = scene.ray_intersect(si.spawn_ray(si.to_world(bs.wo)), active_b)
    emitter = si_bsdf.emitter(scene, active_b)
    active_b &= ek.neq(emitter, 0)
    emitter_val = Emitter.eval_vec(emitter, si_bsdf, active_b)
    delta = has_flag(bs.sampled_type, BSDFFlags.Delta)
    ds = DirectionSample3f(si_bsdf, si)
    ds.object = emitter
    emitter_pdf = ek.select(delta, Float(0), scene.pdf_emitter_direction(si, ds, active_b))
    result += ek.select(active_b, bsdf_val * emitter_val * mis_weight(bs.pdf, emitter_pdf), Vector3f(0))
    return result, si.is_valid(), ek.select(si.is_valid(), si.t, Float(0.0))
def test11_chi2_aniso_lobe_trans(variant_packet_rgb):
    from mitsuba.render import BSDFContext

    xml = """
    <float name="alpha_u" value="0.5"/>
    <float name="alpha_v" value="0.2"/>
    """
    wi = ek.normalize([-0.5, -0.5, 0.1])
    ctx = BSDFContext()
    ctx.component = 1
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi, ctx=ctx)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         res=201)

    assert chi2.run()
예제 #7
0
def test01_ctx_construct(variant_scalar_rgb):
    from mitsuba.render import BSDFContext, BSDFFlags, TransportMode
    ctx = BSDFContext()
    assert ctx.type_mask == +BSDFFlags.All
    assert ctx.component == np.uint32(-1)
    assert ctx.mode == TransportMode.Radiance
    ctx.reverse()
    assert ctx.mode == TransportMode.Importance

    # By default, all components and types are queried
    assert ctx.is_enabled(BSDFFlags.DeltaTransmission)
    assert ctx.is_enabled(BSDFFlags.Delta, 1)
    assert ctx.is_enabled(BSDFFlags.DiffuseTransmission, 6)
    assert ctx.is_enabled(BSDFFlags.Glossy, 10)
예제 #8
0
def BSDFAdapter(bsdf_type, extra, wi=[0, 0, 1], ctx=None):
    """
    Adapter to test BSDF sampling using the Chi^2 test.

    Parameter ``bsdf_type`` (string):
        Name of the BSDF plugin to instantiate.

    Parameter ``extra`` (string):
        Additional XML used to specify the BSDF's parameters.

    Parameter ``wi`` (array(3,)):
        Incoming direction, in local coordinates.
    """

    from mitsuba.render import BSDFContext, SurfaceInteraction3f
    from mitsuba.core import Float
    from mitsuba.core.xml import load_string

    if ctx is None:
        ctx = BSDFContext()

    def make_context(n):
        si = SurfaceInteraction3f.zero(n)
        si.wi = wi
        ek.set_slices(si.wi, n)
        si.wavelengths = []
        return (si, ctx)

    def instantiate(args):
        xml = """<bsdf version="2.0.0" type="%s">
            %s
        </bsdf>""" % (bsdf_type, extra)
        return load_string(xml % args)

    def sample_functor(sample, *args):
        n = ek.slices(sample)
        plugin = instantiate(args)
        (si, ctx) = make_context(n)
        bs, weight = plugin.sample(ctx, si, sample[0], [sample[1], sample[2]])

        w = Float.full(1.0, ek.slices(weight))
        w[ek.all(ek.eq(weight, 0))] = 0
        return bs.wo, w

    def pdf_functor(wo, *args):
        n = ek.slices(wo)
        plugin = instantiate(args)
        (si, ctx) = make_context(n)
        return plugin.pdf(ctx, si, wo)

    return sample_functor, pdf_functor
def test02_pdf(variant_scalar_rgb, interaction):
    from mitsuba.core.math import InvPi
    from mitsuba.render import BSDFContext
    from mitsuba.core.xml import load_string


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

    interaction.wi = [0, 0, 1]
    ctx = BSDFContext()
    p_pdf = bsdf.pdf(ctx, interaction, [0, 0, 1])
    assert ek.allclose(p_pdf, InvPi)

    p_pdf = bsdf.pdf(ctx, interaction, [0, 0, -1])
    assert ek.allclose(p_pdf, 0.0)
def test03_sample_eval_pdf(variant_scalar_rgb, interaction):
    from mitsuba.core.math import InvPi
    from mitsuba.core.warp import square_to_uniform_sphere
    from mitsuba.render import BSDFContext
    from mitsuba.core.xml import load_string

    bsdf = load_string("""<bsdf version="2.0.0" type="twosided">
        <bsdf type="diffuse">
            <rgb name="reflectance" value="0.1, 0.1, 0.1"/>
        </bsdf>
        <bsdf type="diffuse">
            <rgb name="reflectance" value="0.9, 0.9, 0.9"/>
        </bsdf>
    </bsdf>""")

    n = 5
    ctx = BSDFContext()
    for u in ek.arange(UInt32, n):
        for v in ek.arange(UInt32, n):
            interaction.wi = square_to_uniform_sphere([u / float(n-1),
                                                       v / float(n-1)])
            up = ek.dot(interaction.wi, [0, 0, 1]) > 0

            for x in ek.arange(UInt32, n):
                for y in ek.arange(UInt32, n):
                    sample = [x / float(n-1), y / float(n-1)]
                    (bs, s_value) = bsdf.sample(ctx, interaction, 0.5, sample)

                    if ek.any(s_value > 0):
                        # Multiply by square_to_cosine_hemisphere_theta
                        s_value *= bs.wo[2] * InvPi
                        if not up:
                            s_value *= -1

                        e_value = bsdf.eval(ctx, interaction, bs.wo)
                        p_pdf   = bsdf.pdf(ctx, interaction, bs.wo)

                        assert ek.allclose(s_value, e_value, atol=1e-2)
                        assert ek.allclose(bs.pdf, p_pdf)
                        assert not ek.any(ek.isnan(e_value) | ek.isnan(s_value))
예제 #11
0
def get_bxdf_s(args, theta_i, phi_i):
    # Load desired BSDF plugin
    bsdf = load_string(args.material)

    # Create a (dummy) surface interaction to use for the evaluation
    si = SurfaceInteraction3f()

    # Specify an incident direction with 45 degrees elevation
    si.wi = sph_dir(theta_i, phi_i)

    # Create grid in spherical coordinates and map it onto the sphere
    d2r = ek.pi / 180
    theta_s, phi_s = ek.meshgrid(
        ek.linspace(Float, d2r * args.ts[0], d2r * args.ts[1], args.ts[2]),
        ek.linspace(Float, d2r * args.ps[0], d2r * args.ps[1], args.ps[2]))
    ws = sph_dir(theta_s, phi_s)

    # Evaluate the whole array (18000 directions) at once
    values = bsdf.eval(BSDFContext(), si, ws)
    values_r = np.array(values)[:, 0]
    values_r = values_r.reshape(args.ts[2], args.ps[2]).T
    return values_r
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)
예제 #14
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
def test05_sample_half_wave_world(variant_scalar_mono_polarized):
    from mitsuba.core import Ray3f, Spectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis_collinear

    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 world 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])

    # Incident direction
    forward = [0, -1, 0]

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    ray = Ray3f([0, 100, 0], forward, 0.0, 0.0)

    # Build scene with given polarizer rotation angle
    scene_str = """<scene version='2.0.0'>
                       <shape type="rectangle">
                           <bsdf type="retarder">
                               <spectrum name="delta" value="180"/>
                           </bsdf>

                           <transform name="to_world">
                               <rotate x="1" y="0" z="0" angle="-90"/>  <!-- Rotate s.t. it is no longer aligned with local coordinates -->
                           </transform>
                       </shape>
                   </scene>"""
    scene = load_string(scene_str)

    # Intersect ray against geometry
    si = scene.ray_intersect(ray)

    bs, M_local = si.bsdf().sample(ctx, si, 0.0, [0.0, 0.0])
    M_world = si.to_world_mueller(M_local, -si.wi, bs.wo)

    # Make sure we are measuring w.r.t. to the optical table
    M = rotate_mueller_basis_collinear(M_world, forward, stokes_basis(forward),
                                       [-1, 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)
                      </bsdf>""")

# Create a (dummy) surface interaction to use for the evaluation
si = SurfaceInteraction3f()

# Specify an incident direction with 45 degrees elevation
si.wi = sph_dir(ek.pi * 45 / 180, 0.0)

# Create grid in spherical coordinates and map it onto the sphere
res = 300
theta_o, phi_o = ek.meshgrid(ek.linspace(Float, 0, ek.pi, res),
                             ek.linspace(Float, 0, 2 * ek.pi, 2 * res))
wo = sph_dir(theta_o, phi_o)

# Evaluate the whole array (18000 directions) at once
values = bsdf.eval(BSDFContext(), si, wo)

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable

# Extract red channel of BRDF values and reshape into 2D grid
values_r = np.array(values)[:, 0]
values_r = values_r.reshape(2 * res, res).T

# Plot values for spherical coordinates
fig, ax = plt.subplots(figsize=(12, 7))

im = ax.imshow(values_r,
               extent=[0, 2 * np.pi, np.pi, 0],
               cmap='jet',
예제 #17
0
    def sample(self, scene, sampler, ray, medium=None, active=True):
        result = Vector3f(0.0)
        si = scene.ray_intersect(ray, active)
        active = si.is_valid() & active

        # emitter = si.emitter(scene)
        # result = ek.select(active, Emitter.eval_vec(emitter, si, active), Vector3f(0.0))

        # z_axis = np.array([0, 0, 1])

        # vertex = si.p.numpy()
        # v_count = vertex.shape[0]
        # vertex = np.expand_dims(vertex, axis=1)
        # vertex = np.repeat(vertex, self.light.vertex_count(), axis=1)

        # light_vertices = self.light.vertex_positions_buffer().numpy().reshape(self.light.vertex_count(), 3)
        # light_vertices = np.expand_dims(light_vertices, axis=0)
        # light_vertices = np.repeat(light_vertices, v_count, axis=0)

        # sph_polygons = light_vertices - vertex
        # sph_polygons = sph_polygons / np.linalg.norm(sph_polygons, axis=2, keepdims=True)

        # z_axis = np.repeat( np.expand_dims(z_axis, axis=0), v_count, axis=0 )
        # result_np = np.zeros(v_count, dtype=np.double)

        # for idx in range( self.light.vertex_count() ):
        #     idx1 = (idx+1) % self.light.vertex_count()
        #     idx2 = (idx) % self.light.vertex_count()

        #     dp = np.sum( sph_polygons[:, idx1, :] * sph_polygons[:, idx2, :], axis=1 )
        #     acos = np.arccos(dp)

        #     cp = np.cross( sph_polygons[:, idx1, :], sph_polygons[:, idx2, :] )
        #     cp = cp / np.linalg.norm(cp, axis=1, keepdims=True)

        #     dp = np.sum( cp * z_axis, axis=1 )

        #     result_np += acos * dp

        # result_np *= 0.5 * 1.0/math.pi
        # result_np = np.repeat( result_np.reshape((v_count, 1)), 3, axis=1 )

        # fin = self.light_radiance * Vector3f(result_np)
        # fin[fin < 0] = 0
        # result += ek.select(active, fin, Vector3f(0.0))

        ctx = BSDFContext()
        bsdf = si.bsdf(ray)

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

        ds, emitter_val = scene.sample_emitter_direction(
            si, sampler.next_2d(active), True, active)
        pdf = ds.pdf
        active_e = active & ek.neq(ds.pdf, 0.0)
        wo = ds.d
        bsdf_val = BSDF.eval_vec(bsdf, ctx, si, wo, active_e)
        emitter_val[emitter_val > 1.0] = 1.0
        bsdf_val *= emitter_val

        # _, wi_theta, wi_phi = sph_convert(si.to_world(si.wi))
        _, wo_theta, wo_phi = sph_convert(wo)

        y_0_0_ = y_0_0(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))

        y_1_n1_ = y_1_n1(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_1_0_ = y_1_0(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_1_p1_ = y_1_p1(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))

        y_2_n2_ = y_2_n2(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_2_n1_ = y_2_n1(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_2_0_ = y_2_0(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_2_p1_ = y_2_p1(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))
        y_2_p2_ = y_2_p2(bsdf_val, wo_theta, wo_phi) / ek.select(
            pdf > 0, pdf, Float(0.01))

        return result, si.is_valid(), [ Float(y_0_0_[0]), Float(y_0_0_[1]), Float(y_0_0_[2]),\
                                        Float(y_1_n1_[0]), Float(y_1_n1_[1]), Float(y_1_n1_[2]),\
                                        Float(y_1_0_[0]), Float(y_1_0_[1]), Float(y_1_0_[2]),\
                                        Float(y_1_p1_[0]), Float(y_1_p1_[1]), Float(y_1_p1_[2]),\
                                        Float(y_2_n2_[0]), Float(y_2_n2_[1]), Float(y_2_n2_[2]),\
                                        Float(y_2_n1_[0]), Float(y_2_n1_[1]), Float(y_2_n1_[2]),\
                                        Float(y_2_0_[0]), Float(y_2_0_[1]), Float(y_2_0_[2]),\
                                        Float(y_2_p1_[0]), Float(y_2_p1_[1]), Float(y_2_p1_[2]),\
                                        Float(y_2_p2_[0]), Float(y_2_p2_[1]), Float(y_2_p2_[2]) ]
예제 #18
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_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))
예제 #20
0
def test03_sample_world(variant_scalar_mono_polarized):
    from mitsuba.core import Ray3f, Spectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis_collinear

    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 world coordinates. This involves
    # additional coordinate changes to the local reference frame and back.
    #
    # 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.

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

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    ray = Ray3f([0, -100, 0], forward, 0.0, 0.0)

    # 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]

        # Build scene with given polarizer rotation angle
        scene_str = """<scene version='2.0.0'>
                           <shape type="rectangle">
                               <bsdf type="polarizer"/>

                               <transform name="to_world">
                                   <rotate x="0" y="0" z="1" angle="{}"/>   <!-- Rotate around optical axis -->
                                   <rotate x="1" y="0" z="0" angle="-90"/>  <!-- Rotate s.t. it is no longer aligned with local coordinates -->
                               </transform>
                           </shape>
                       </scene>""".format(angle)
        scene = load_string(scene_str)

        # Intersect ray against geometry
        si = scene.ray_intersect(ray)

        bs, M_local = si.bsdf().sample(ctx, si, 0.0, [0.0, 0.0])
        M_world = si.to_world_mueller(M_local, -si.wi, bs.wo)

        # Make sure we are measuring w.r.t. to the optical table
        M = rotate_mueller_basis_collinear(M_world,
                                           forward,
                                           stokes_basis(forward), [-1, 0, 0])

        stokes_out = M @ stokes_in
        assert ek.allclose(stokes_out, expected, atol=1e-3)
예제 #21
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)
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))
예제 #23
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
예제 #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