def test_sample_direction(variant_scalar_spectral, spectrum_key, it_pos, wavelength_sample, cutoff_angle, lookat): # Check the correctness of the sample_direction() method import math from mitsuba.core import sample_shifted, Transform4f from mitsuba.render import SurfaceInteraction3f cutoff_angle_rad = math.radians(cutoff_angle) beam_width_rad = cutoff_angle_rad * 0.75 inv_transition_width = 1 / (cutoff_angle_rad - beam_width_rad) emitter, spectrum = create_emitter_and_spectrum(lookat, cutoff_angle, spectrum_key) eval_t = 0.3 trafo = Transform4f(emitter.world_transform().eval(eval_t)) # Create a surface iteration it = SurfaceInteraction3f.zero() it.p = it_pos it.time = eval_t # Sample a wavelength from spectrum wav, spec = spectrum.sample(it, sample_shifted(wavelength_sample)) it.wavelengths = wav # Direction from the position to the point emitter d = -it.p + lookat["pos"] dist = ek.norm(d) d /= dist # Calculate angle between lookat direction and ray direction angle = ek.acos((trafo.inverse().transform_vector(-d))[2]) angle = ek.select( ek.abs(angle - beam_width_rad) < 1e-3, beam_width_rad, angle) angle = ek.select( ek.abs(angle - cutoff_angle_rad) < 1e-3, cutoff_angle_rad, angle) # Sample a direction from the emitter ds, res = emitter.sample_direction(it, [0, 0]) # Evalutate the spectrum spec = spectrum.eval(it) spec = ek.select( angle <= beam_width_rad, spec, spec * ((cutoff_angle_rad - angle) * inv_transition_width)) spec = ek.select(angle <= cutoff_angle_rad, spec, 0) assert ds.time == it.time assert ds.pdf == 1.0 assert ds.delta assert ek.allclose(ds.d, d) assert ek.allclose(res, spec / (dist**2))
def test_sample_ray(variant_packet_spectral, spectrum_key, wavelength_sample, pos_sample, cutoff_angle, lookat): # Check the correctness of the sample_ray() method import math from mitsuba.core import warp, sample_shifted, Transform4f from mitsuba.render import SurfaceInteraction3f cutoff_angle_rad = math.radians(cutoff_angle) cos_cutoff_angle_rad = math.cos(cutoff_angle_rad) beam_width_rad = cutoff_angle_rad * 0.75 inv_transition_width = 1 / (cutoff_angle_rad - beam_width_rad) emitter, spectrum = create_emitter_and_spectrum(lookat, cutoff_angle, spectrum_key) eval_t = 0.3 trafo = Transform4f(emitter.world_transform().eval(eval_t)) # Sample a local direction and calculate local angle dir_sample = pos_sample # not being used anyway local_dir = warp.square_to_uniform_cone(pos_sample, cos_cutoff_angle_rad) angle = ek.acos(local_dir[2]) angle = ek.select( ek.abs(angle - beam_width_rad) < 1e-3, beam_width_rad, angle) angle = ek.select( ek.abs(angle - cutoff_angle_rad) < 1e-3, cutoff_angle_rad, angle) # Sample a ray (position, direction, wavelengths) from the emitter ray, res = emitter.sample_ray(eval_t, wavelength_sample, pos_sample, dir_sample) # Sample wavelengths on the spectrum it = SurfaceInteraction3f.zero() wav, spec = spectrum.sample(it, sample_shifted(wavelength_sample)) it.wavelengths = wav spec = spectrum.eval(it) spec = ek.select( angle <= beam_width_rad, spec, spec * ((cutoff_angle_rad - angle) * inv_transition_width)) spec = ek.select(angle <= cutoff_angle_rad, spec, 0) assert ek.allclose( res, spec / warp.square_to_uniform_cone_pdf( trafo.inverse().transform_vector(ray.d), cos_cutoff_angle_rad)) assert ek.allclose(ray.time, eval_t) assert ek.all(local_dir.z >= cos_cutoff_angle_rad) assert ek.allclose(ray.wavelengths, wav) assert ek.allclose(ray.d, trafo.transform_vector(local_dir)) assert ek.allclose(ray.o, lookat["pos"])
def test05_scalar(t): if not ek.is_array_v(t) or ek.array_size_v(t) == 0: return get_class(t.__module__) if ek.is_mask_v(t): assert ek.all_nested(t(True)) assert ek.any_nested(t(True)) assert ek.none_nested(t(False)) assert ek.all_nested(t(False) ^ t(True)) assert ek.all_nested(ek.eq(t(False), t(False))) assert ek.none_nested(ek.eq(t(True), t(False))) if ek.is_arithmetic_v(t): assert t(1) + t(1) == t(2) assert t(3) - t(1) == t(2) assert t(2) * t(2) == t(4) assert ek.min(t(2), t(3)) == t(2) assert ek.max(t(2), t(3)) == t(3) if ek.is_signed_v(t): assert t(2) * t(-2) == t(-4) assert ek.abs(t(-2)) == t(2) if ek.is_integral_v(t): assert t(6) // t(2) == t(3) assert t(7) % t(2) == t(1) assert t(7) >> 1 == t(3) assert t(7) << 1 == t(14) assert t(1) | t(2) == t(3) assert t(1) ^ t(3) == t(2) assert t(1) & t(3) == t(1) else: assert t(6) / t(2) == t(3) assert ek.sqrt(t(4)) == t(2) assert ek.fmadd(t(1), t(2), t(3)) == t(5) assert ek.fmsub(t(1), t(2), t(3)) == t(-1) assert ek.fnmadd(t(1), t(2), t(3)) == t(1) assert ek.fnmsub(t(1), t(2), t(3)) == t(-5) assert (t(1) & True) == t(1) assert (t(1) & False) == t(0) assert (t(1) | False) == t(1) assert ek.all_nested(t(3) > t(2)) assert ek.all_nested(ek.eq(t(2), t(2))) assert ek.all_nested(ek.neq(t(3), t(2))) assert ek.all_nested(t(1) >= t(1)) assert ek.all_nested(t(2) < t(3)) assert ek.all_nested(t(1) <= t(1)) assert ek.select(ek.eq(t(2), t(2)), t(4), t(5)) == t(4) assert ek.select(ek.eq(t(3), t(2)), t(4), t(5)) == t(5) t2 = t(2) assert ek.hsum(t2) == t.Value(2 * len(t2)) assert ek.dot(t2, t2) == t.Value(4 * len(t2)) assert ek.dot_async(t2, t2) == t(4 * len(t2)) value = t(1) value[ek.eq(value, t(1))] = t(2) value[ek.eq(value, t(3))] = t(5) assert value == t(2)
def test15_abs(m): x = m.Float(-2, 2) ek.enable_grad(x) y = ek.abs(x) ek.backward(y) assert ek.allclose(ek.detach(y), [2, 2]) assert ek.allclose(ek.grad(x), [-1, 1])
def test02_eval_grad(variant_scalar_rgb): # Tests evaluating the texture gradient under different rotation from mitsuba.render import SurfaceInteraction3f from mitsuba.core.xml import load_string from mitsuba.core import Vector2f import numpy as np import enoki as ek delta = 1e-4 si = SurfaceInteraction3f() for u01 in np.random.rand(10, 1): angle = 360.0*u01[0] bitmap = load_string(""" <texture type="bitmap" version="2.0.0"> <string name="filename" value="resources/data/common/textures/noise_8x8.png"/> <transform name="to_uv"> <rotate angle="%f"/> </transform> </texture>""" % angle).expand()[0] for uv in np.random.rand(10, 2): si.uv = Vector2f(uv) f = bitmap.eval_1(si) si.uv = uv + Vector2f(delta, 0) fu = bitmap.eval_1(si) si.uv = uv + Vector2f(0, delta) fv = bitmap.eval_1(si) gradient_finite_difference = Vector2f((fu - f)/delta, (fv - f)/delta) gradient_analytic = bitmap.eval_1_grad(si) assert ek.allclose(0, ek.abs(gradient_finite_difference/gradient_analytic - 1.0), atol = 1e04)
def quat_to_euler(q): name = _ek.detail.array_name('Array', q.Type, [3], q.IsScalar) module = _modules.get(q.__module__) Array3f = getattr(module, name) sinp = 2 * _ek.fmsub(q.w, q.y, q.z * q.x) gimbal_lock = _ek.abs(sinp) > (1.0 - 5e-8) # roll (x-axis rotation) q_y_2 = _ek.sqr(q.y) sinr_cosp = 2 * _ek.fmadd(q.w, q.x, q.y * q.z) cosr_cosp = _ek.fnmadd(2, _ek.fmadd(q.x, q.x, q_y_2), 1) roll = _ek.select(gimbal_lock, 2 * _ek.atan2(q.x, q.w), _ek.atan2(sinr_cosp, cosr_cosp)) # pitch (y-axis rotation) pitch = _ek.select(gimbal_lock, _ek.copysign(0.5 * _ek.Pi, sinp), _ek.asin(sinp)) # yaw (z-axis rotation) siny_cosp = 2 * _ek.fmadd(q.w, q.z, q.x * q.y) cosy_cosp = _ek.fnmadd(2, _ek.fmadd(q.z, q.z, q_y_2), 1) yaw = _ek.select(gimbal_lock, 0, _ek.atan2(siny_cosp, cosy_cosp)) return Array3f(roll, pitch, yaw)
def test02_radical_inverse_vectorized(variant_scalar_rgb): from mitsuba.core import RadicalInverse v = RadicalInverse() for index, prime in enumerate(gen_primes()): if index >= 1024: break result = v.eval(index, ek.arange(10, dtype=ek.uint64)) for i in range(len(result)): assert ek.abs(r_inv(prime, i) - result[i]) < 1e-7
def check_contents(stream): if type(stream) is not ZStream: stream.seek(0) for v in contents: if type(v) is str: assert v == stream.read_string() elif type(v) is int: assert v == stream.read_int64() elif type(v) is float: assert ek.abs(stream.read_single() - v) / v < 1e-5 elif type(v) is bool: assert v == stream.read_bool()
def abs_(a0): if not a0.IsArithmetic: raise Exception("abs(): requires arithmetic operands!") if not a0.IsSpecial or a0.IsMatrix: ar, sr = _check1(a0) for i in range(sr): ar[i] = _ek.abs(a0[i]) return ar elif a0.IsSpecial: return _ek.norm(a0) else: raise Exception('abs(): unsupported array type!')
def test02_type_is_preserved(variant_scalar_rgb): from mitsuba.core import Properties as Prop p = Prop() fill_properties(p) assert p['prop_1'] == 1 assert p['prop_2'] == '1' assert p['prop_3'] == False assert ek.abs(p['prop_4']-3.14) < 1e-6 # Updating an existing property but using a different type p['prop_2'] = 2 assert p['prop_2'] == 2
def test02_radical_inverse_vectorized(variant_scalar_rgb): from mitsuba.core import RadicalInverse try: from mitsuba.packet_rgb.core.qmc import RadicalInverseP except ImportError: pytest.skip("packet_rgb mode not enabled") v = RadicalInverse() v_p = RadicalInverseP() for index in range(1024): result = v_p.eval_scrambled(index, ek.arange(10, dtype=ek.uint64)) for i in range(len(result)): assert ek.abs(v.eval_scrambled(index, i) - result[i]) < 1e-7
def test01_radical_inverse(variant_scalar_rgb): from mitsuba.core import RadicalInverse v = RadicalInverse() assert (v.eval(0, 0) == 0) assert (v.eval(0, 1) == 0.5) assert (v.eval(0, 2) == 0.25) assert (v.eval(0, 3) == 0.75) for index, prime in enumerate(gen_primes()): if index >= 1024: break for i in range(10): assert ek.abs(r_inv(prime, i) - v.eval(index, i)) < 1e-7
def test08_misc(): for i in range(-5, 5): for j in range(-5, 5): a = ek.sqrt(C(i, j)) b = cmath.sqrt(complex(i, j)) assert ek.allclose(a, C(b)) assert ek.allclose(ek.conj(a), C(b.conjugate())) assert ek.allclose(ek.abs(a), C(abs(b))) if i != 0 and j != 0: a = ek.rsqrt(C(i, j)) b = C(1 / cmath.sqrt(complex(i, j))) assert ek.allclose(a, b)
def test04_operators(): I3 = ek.scalar.Array3i F3 = ek.scalar.Array3f assert (I3(1, 2, 3) + I3(0, 1, 0) == I3(1, 3, 3)) assert (I3(1, 2, 3) - I3(0, 1, 0) == I3(1, 1, 3)) assert (I3(1, 2, 3) * I3(0, 1, 0) == I3(0, 2, 0)) assert (I3(1, 2, 3) // I3(2, 2, 2) == I3(0, 1, 1)) assert (I3(1, 2, 3) % I3(2, 2, 2) == I3(1, 0, 1)) assert (I3(1, 2, 3) << 1 == I3(2, 4, 6)) assert (I3(1, 2, 3) >> 1 == I3(0, 1, 1)) assert (I3(1, 2, 3) & 1 == I3(1, 0, 1)) assert (I3(1, 2, 3) | 1 == I3(1, 3, 3)) assert (I3(1, 2, 3) ^ 1 == I3(0, 3, 2)) assert (-I3(1, 2, 3) == I3(-1, -2, -3)) assert (~I3(1, 2, 3) == I3(-2, -3, -4)) assert (ek.abs(I3(1, -2, 3)) == I3(1, 2, 3)) assert (abs(I3(1, -2, 3)) == I3(1, 2, 3)) assert (ek.abs(I3(1, -2, 3)) == I3(1, 2, 3)) assert (ek.fmadd(F3(1, 2, 3), F3(2, 3, 1), F3(1, 1, 1)) == F3(3, 7, 4)) assert (ek.fmsub(F3(1, 2, 3), F3(2, 3, 1), F3(1, 1, 1)) == F3(1, 5, 2)) assert (ek.fnmadd(F3(1, 2, 3), F3(2, 3, 1), F3(1, 1, 1)) == F3(-1, -5, -2)) assert (ek.fnmsub(F3(1, 2, 3), F3(2, 3, 1), F3(1, 1, 1)) == F3(-3, -7, -4))
def test03_ray_intersect_transform(variant_scalar_rgb): if mitsuba.core.MTS_ENABLE_EMBREE: pytest.skip("EMBREE enabled") from mitsuba.core import Ray3f for r in [1, 3]: s = example_scene(radius=r, extra="""<transform name="to_world"> <rotate y="1.0" angle="30"/> <translate x="0.0" y="1.0" z="0.0"/> </transform>""") # grid size n = 21 inv_n = 1.0 / n for x in range(n): for y in range(n): x_coord = r * (2 * (x * inv_n) - 1) y_coord = r * (2 * (y * inv_n) - 1) ray = Ray3f(o=[x_coord, y_coord + 1, -8], d=[0.0, 0.0, 1.0], time=0.0, wavelengths=[]) si_found = s.ray_test(ray) assert si_found == (x_coord ** 2 + y_coord ** 2 <= r * r) \ or ek.abs(x_coord ** 2 + y_coord ** 2 - r * r) < 1e-8 if si_found: ray = Ray3f(o=[x_coord, y_coord + 1, -8], d=[0.0, 0.0, 1.0], time=0.0, wavelengths=[]) si = s.ray_intersect(ray) ray_u = Ray3f(ray) ray_v = Ray3f(ray) eps = 1e-4 ray_u.o += si.dp_du * eps ray_v.o += si.dp_dv * eps si_u = s.ray_intersect(ray_u) si_v = s.ray_intersect(ray_v) if si_u.is_valid(): du = (si_u.uv - si.uv) / eps assert ek.allclose(du, [1, 0], atol=2e-2) if si_v.is_valid(): dv = (si_v.uv - si.uv) / eps assert ek.allclose(dv, [0, 1], atol=2e-2)
def test03_distance(variant_scalar_rgb): from mitsuba.core import BoundingBox3f as BBox assert BBox([1, 2, 3], [2, 3, 5]).distance(BBox([4, 2, 3], [5, 3, 5])) == 2 assert ek.abs( BBox([1, 2, 3], [2, 3, 5]).distance(BBox([3, 4, 6], [7, 7, 7])) - ek.sqrt(3)) < 1e-6 assert BBox([1, 2, 3], [2, 3, 5]).distance(BBox([1.1, 2.2, 3.3], [1.8, 2.8, 3.8])) == 0 assert BBox([1, 2, 3], [2, 3, 5]).distance([1.5, 2.5, 3.5]) == 0 assert BBox([1, 2, 3], [2, 3, 5]).distance([3, 2.5, 3.5]) == 1
def test03_ray_intersect_transform(variant_scalar_rgb): from mitsuba.core import xml, Ray3f, Transform4f for r in [1, 3]: s = xml.load_dict({ "type": "sphere", "radius": r, "to_world": Transform4f.translate([0, 1, 0]) * Transform4f.rotate([0, 1, 0], 30.0) }) # grid size n = 21 inv_n = 1.0 / n for x in range(n): for y in range(n): x_coord = r * (2 * (x * inv_n) - 1) y_coord = r * (2 * (y * inv_n) - 1) ray = Ray3f(o=[x_coord, y_coord + 1, -8], d=[0.0, 0.0, 1.0], time=0.0, wavelengths=[]) si_found = s.ray_test(ray) assert si_found == (x_coord ** 2 + y_coord ** 2 <= r * r) \ or ek.abs(x_coord ** 2 + y_coord ** 2 - r * r) < 1e-8 if si_found: si = s.ray_intersect(ray) ray_u = Ray3f(ray) ray_v = Ray3f(ray) eps = 1e-4 ray_u.o += si.dp_du * eps ray_v.o += si.dp_dv * eps si_u = s.ray_intersect(ray_u) si_v = s.ray_intersect(ray_v) if si_u.is_valid(): du = (si_u.uv - si.uv) / eps assert ek.allclose(du, [1, 0], atol=2e-2) if si_v.is_valid(): dv = (si_v.uv - si.uv) / eps assert ek.allclose(dv, [0, 1], atol=2e-2)
def sincos(x): Float = type(x) Int = ek.int_array_t(Float) xa = ek.abs(x) j = Int(xa * 1.2732395447351626862) j = (j + Int(1)) & ~Int(1) y = Float(j) Shift = Float.Type.Size * 8 - 3 sign_sin = ek.reinterpret_array(Float, j << Shift) ^ x sign_cos = ek.reinterpret_array(Float, (~(j - Int(2)) << Shift)) y = xa - y * 0.78515625 \ - y * 2.4187564849853515625e-4 \ - y * 3.77489497744594108e-8 z = y * y z |= ek.eq(xa, ek.Infinity) s = poly2(z, -1.6666654611e-1, 8.3321608736e-3, -1.9515295891e-4) * z c = poly2(z, 4.166664568298827e-2, -1.388731625493765e-3, 2.443315711809948e-5) * z s = ek.fmadd(s, y, y) c = ek.fmadd(c, z, ek.fmadd(z, -0.5, 1)) polymask = ek.eq(j & Int(2), ek.zero(Int)) return ( ek.mulsign(ek.select(polymask, s, c), sign_sin), ek.mulsign(ek.select(polymask, c, s), sign_cos) )
def quat_to_euler(q): q_y_2 = _ek.sqr(q.y) sinr_cosp = 2 * _ek.fmadd(q.w, q.x, q.y * q.z) cosr_cosp = _ek.fnmadd(2, _ek.fmadd(q.x, q.x, q_y_2), 1) roll = _ek.atan2(sinr_cosp, cosr_cosp) # pitch (y-axis rotation) sinp = 2 * _ek.fmsub(q.w, q.y, q.z * q.x) if (_ek.abs(sinp) >= 1.0): pitch = _ek.copysign(0.5 * _ek.Pi, sinp) else: pitch = _ek.asin(sinp) # yaw (z-axis rotation) siny_cosp = 2 * _ek.fmadd(q.w, q.z, q.x * q.y) cosy_cosp = _ek.fnmadd(2, _ek.fmadd(q.z, q.z, q_y_2), 1) yaw = _ek.atan2(siny_cosp, cosy_cosp) name = _ek.detail.array_name('Array', q.Type, [3], q.IsScalar) module = _modules.get(q.__module__) Array3f = getattr(module, name) return Array3f(roll, pitch, yaw)
def rlgamma(a, x): 'Regularized lower incomplete gamma function based on CEPHES' eps = 1e-15 big = 4.503599627370496e15 biginv = 2.22044604925031308085e-16 if a < 0 or x < 0: raise "out of range" if x == 0: return 0 ax = (a * ek.log(x)) - x - ek.lgamma(a) if ax < -709.78271289338399: return 1.0 if a < x else 0.0 if x <= 1 or x <= a: r2 = a c2 = 1 ans2 = 1 while True: r2 = r2 + 1 c2 = c2 * x / r2 ans2 += c2 if c2 / ans2 <= eps: break return ek.exp(ax) * ans2 / a c = 0 y = 1 - a z = x + y + 1 p3 = 1 q3 = x p2 = x + 1 q2 = z * x ans = p2 / q2 while True: c += 1 y += 1 z += 2 yc = y * c p = (p2 * z) - (p3 * yc) q = (q2 * z) - (q3 * yc) if q != 0: nextans = p / q error = ek.abs((ans - nextans) / nextans) ans = nextans else: error = 1 p3 = p2 p2 = p q3 = q2 q2 = q # normalize fraction when the numerator becomes large if ek.abs(p) > big: p3 *= biginv p2 *= biginv q3 *= biginv q2 *= biginv if error <= eps: break; return 1 - ek.exp(ax) * ans
def test03_develop(variant_scalar_rgb, file_format, tmpdir): from mitsuba.core.xml import load_string from mitsuba.core import Bitmap, Struct, ReconstructionFilter, float_dtype from mitsuba.render import ImageBlock import numpy as np """Create a test image. Develop it to a few file format, each time reading it back and checking that contents are unchanged.""" np.random.seed(12345 + ord(file_format[0])) # Note: depending on the file format, the alpha channel may be automatically removed. film = load_string("""<film version="2.0.0" type="hdrfilm"> <integer name="width" value="41"/> <integer name="height" value="37"/> <string name="file_format" value="{}"/> <string name="pixel_format" value="rgba"/> <string name="component_format" value="float32"/> <rfilter type="box"/> </film>""".format(file_format)) # Regardless of the output file format, values are stored as XYZAW (5 channels). contents = np.random.uniform(size=(film.size()[1], film.size()[0], 5)) # RGBE and will only reconstruct well images that have similar scales on # all channel (because exponent is shared between channels). if file_format is "rgbe": contents = 1 + 0.1 * contents # Use unit weights. contents[:, :, 4] = 1.0 block = ImageBlock(film.size(), 5, film.reconstruction_filter()) block.clear() for x in range(film.size()[1]): for y in range(film.size()[0]): block.put([y + 0.5, x + 0.5], contents[x, y, :]) film.prepare(['X', 'Y', 'Z', 'A', 'W']) film.put(block) with pytest.raises(RuntimeError): # Should raise when the destination file hasn't been specified. film.develop() filename = str(tmpdir.join('test_image.' + file_format)) film.set_destination_file(filename) film.develop() # Read back and check contents other = Bitmap(filename).convert(Bitmap.PixelFormat.XYZAW, Struct.Type.Float32, srgb_gamma=False) img = np.array(other, copy=False) if False: import matplotlib.pyplot as plt plt.figure() plt.subplot(1, 3, 1) plt.imshow(contents[:, :, :3]) plt.subplot(1, 3, 2) plt.imshow(img[:, :, :3]) plt.subplot(1, 3, 3) plt.imshow(ek.sum(ek.abs(img[:, :, :3] - contents[:, :, :3]), axis=2), cmap='coolwarm') plt.colorbar() plt.show() if file_format == "exr": assert ek.allclose(img, contents, atol=1e-5) else: if file_format == "rgbe": assert ek.allclose(img[:, :, :3], contents[:, :, :3], atol=1e-2), \ '\n{}\nvs\n{}\n'.format(img[:4, :4, :3], contents[:4, :4, :3]) else: assert ek.allclose(img[:, :, :3], contents[:, :, :3], atol=1e-5) # Alpha channel was ignored, alpha and weights should default to 1.0. assert ek.allclose(img[:, :, 3:5], 1.0, atol=1e-6)
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_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))