def test01_isotropic(variant_scalar_rgb): # Reference data from "Multiple Light Scattering" by C. van de Hulst, 1980 # Volume 1, Table 12 p.260-261 (Isotropic scattering, finite slabs) albedo = 0.8 thickness = 2.0 mu, w = mitsuba.core.quad.gauss_lobatto(50) layer = mitsuba.layer.Layer(mu, w) layer.set_isotropic(albedo) layer.expand(thickness) print(layer) # Intensities leaving out at top mu_i_top = [1.0, 0.9, 0.7, 0.5, 0.3, 0.1] mu_o_top = 0.5 ref_top = [0.28616, 0.30277, 0.34188, 0.39102, 0.45353, 0.53214] for i in range(len(mu_i_top)): table = ref_top[i] computed = layer.eval(mu_o_top, -mu_i_top[i]) * np.pi assert np.allclose(table, computed, rtol=0.001) # Intensities leaving out at bottom mu_i_bot = [1.0, 0.9, 0.7, 0.3, 0.1] mu_o_bot = -0.5 ref_bot = [0.14864, 0.14731, 0.13963, 0.09425, 0.06601] for i in range(len(mu_i_bot)): table = ref_bot[i] computed = layer.eval(mu_o_bot, -mu_i_bot[i]) * np.pi assert np.allclose(table, computed, rtol=0.003)
def test02_hg(variant_scalar_rgb): # Reproduce same results from test01_isotropic using the HG implementation albedo = 0.8 thickness = 2.0 g = 0.0001 n, ms, md = mitsuba.layer.henyey_greenstein_parameter_heuristic(g) n = 100 mu, w = mitsuba.core.quad.gauss_lobatto(n) layer = mitsuba.layer.Layer(mu, w, ms, md) layer.set_henyey_greenstein(albedo, g) layer.expand(thickness) # Intensities leaving out at top mu_i_top = [1.0, 0.9, 0.7, 0.5, 0.3, 0.1] mu_o_top = 0.5 ref_top = [0.28616, 0.30277, 0.34188, 0.39102, 0.45353, 0.53214] for i in range(len(mu_i_top)): table = ref_top[i] computed = layer.eval(mu_o_top, -mu_i_top[i]) * np.pi assert np.allclose(table, computed, rtol=0.001) # Intensities leaving out at bottom mu_i_bot = [1.0, 0.9, 0.7, 0.3, 0.1] mu_o_bot = -0.5 ref_bot = [0.14864, 0.14731, 0.13963, 0.09425, 0.06601] for i in range(len(mu_i_bot)): table = ref_bot[i] computed = layer.eval(mu_o_bot, -mu_i_bot[i]) * np.pi assert np.allclose(table, computed, rtol=0.003)
def plot_layer(ax1, layer, zenith_i=30.0, azimuth_i=0.0, transmission=False, levels=None, clamp=True, center=True, vmin=None, vmax=None): if zenith_i < 90 and not transmission: component = 'Rt' elif zenith_i < 90 and transmission: component = 'Ttb' elif zenith_i >= 90 and not transmission: component = 'Rb' else: component = 'Tbt' phi_i = np.radians(azimuth_i) theta_i = np.radians(zenith_i) mu_i = np.cos(theta_i) azimuths = np.linspace(0, 360, 200) if component == 'Rt' or component == 'Tbt': zeniths = np.linspace(0, 90, 200) theta_ticks_deg = [10, 30, 50, 70, 90] theta_labels = ['0˚', '', '', '', '90˚'] elif component == 'Rb' or component == 'Ttb': zeniths = np.linspace(180, 90, 200) theta_ticks_deg = [180, 160, 140, 120, 100] theta_labels = ['90˚', '', '', '', '180˚'] theta_o, phi_o = np.meshgrid(np.radians(zeniths), np.radians(azimuths)) mu_o = -np.cos(theta_o) phi_s = phi_o + phi_i phi_d = phi_o - phi_i data = layer.eval(mu_o, mu_i, phi_s, phi_d, clamp) # data *= np.abs(mu_o) storage = mitsuba.layer.BSDFStorage.from_layer("tmp.bsdf", layer, 1e-8) data = storage.eval(mu_i, mu_o, 0.5 * (phi_s - phi_d), 0.5 * (phi_s + phi_d), clamp) data /= np.abs(mu_o) ax1.grid(linestyle='-', linewidth=0.6, alpha=0.3, color='w') text_col = 'k' ax1.set_rgrids(np.radians(theta_ticks_deg), labels=theta_labels, angle=270, color=text_col, fontweight='ultralight', size='10', ha='center', alpha=0.8) phi_ticks_deg = np.array([0, 45, 90, 135, 180, 225, 270, 315], dtype=np.float32) phi_labels = [("%d˚" % i) for i in phi_ticks_deg] phi_labels[0] = "" phi_labels[4] = "" phi_ticks_deg -= np.degrees(phi_i) phi_ticks_deg = np.where(phi_ticks_deg < 0, phi_ticks_deg + 360, phi_ticks_deg) phi_ticks_deg = np.where(phi_ticks_deg > 360, phi_ticks_deg - 360, phi_ticks_deg) ax1.set_thetagrids(phi_ticks_deg, labels=phi_labels, color='k', fontweight='ultralight', size='10', ha='center', alpha=0.8) if center and not (vmin and vmax) and clamp: vmin = 0.001 vmax = np.max(data) norm = None if center and not (vmin and vmax): vmin = np.min(data) vmax = np.max(data) norm = MyNormalize(vmin, vmax) elif center: norm = MyNormalize(vmin, vmax) else: vmin = np.min(data) vmax = np.max(data) norm = None theta_o_plot = theta_o if component == 'Rt' or component == 'Tbt' else np.radians( 270) - theta_o view = ax1.contourf(phi_o - phi_i, theta_o_plot, data, 200, cmap='coolwarm', norm=norm, levels=levels, vmin=vmin, vmax=vmax) out_info = (phi_o - phi_i, theta_o_plot, data) for c in view.collections: c.set_edgecolor("face") c.set_rasterized(True) theta_i_mark = theta_i phi_i_plot = np.pi if component == 'Ttb' or component == 'Tbt': theta_i_mark = np.pi - theta_i theta_i_plot = theta_i_mark if component == 'Rt' or component == 'Tbt' else np.radians( 270) - theta_i_mark xy = (phi_i_plot, np.abs(theta_i_plot)) xytext = (phi_i_plot - 0.3, np.abs(theta_i_plot) + 0.1) ax1.plot(xy[0], xy[1], 'x', color=text_col, ms='10', mew=2) ax1.annotate('$\omega_i$', xy=xy, textcoords='data', color=text_col, fontweight='black', size='14', xytext=xytext) dr = 0.16 if component == 'Rb' or component == 'Ttb': dstart = 1.033 * np.pi orientation_line_radii = [dstart, dstart + dr] else: dstart = 0.533 * np.pi orientation_line_radii = [dstart, dstart + dr] x, y = np.array([[-phi_i, -phi_i], orientation_line_radii]) line = mlines.Line2D(x, y, lw=14, color='k') line.set_clip_on(False) ax1.add_line(line) x, y = np.array([[-phi_i - np.pi, -phi_i - np.pi], orientation_line_radii]) line = mlines.Line2D(x, y, lw=14, color='k') line.set_clip_on(False) ax1.add_line(line) [i.set_linewidth(2) for i in ax1.spines.values()] return view, out_info
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 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