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)
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()
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)
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))
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)
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',
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]) ]
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))
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)
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))
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
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