From 58366543fb329733db29a6881afd784c475b187a Mon Sep 17 00:00:00 2001 From: Vincent Leroy Date: Tue, 26 May 2020 12:46:41 +0200 Subject: [PATCH] Distant plugin fix and docs and test updates --- src/sensors/distant.cpp | 28 +++++++-- src/sensors/tests/test_distant.py | 98 ++++++++++++++++--------------- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/src/sensors/distant.cpp b/src/sensors/distant.cpp index 8438d131f..3f29c0bcd 100644 --- a/src/sensors/distant.cpp +++ b/src/sensors/distant.cpp @@ -32,9 +32,21 @@ Distant directional sensor (:monosp:`distant`) uniformly over the entire scene. This sensor plugin implements a distant directional sensor which records -radiation leaving the scene in a given direction. If the ``target`` parameter -is not set, rays cast by the sensor will be distributed uniformly on the cross -section of the scene's bounding sphere. +radiation leaving the scene in a given direction. By default, it records the +(spectral) radiant flux per unit solid angle leaving the scene in the specified +direction (in unit power per unit solid angle). Rays cast by the sensor are +distributed uniformly on the cross section of the scene's bounding sphere. + +.. warning:: + + If this sensor is used with an environment map emitter, it will also record + radiant flux coming from the part of emitter appearing through the scene's + bounding sphere cross section. Care should be taken notably when using the + `constant` or `envmap` emitters. + +If the ``target`` parameter is set, the sensor looks at a single point and +records a (spectral) radiant flux per unit surface area per unit solid angle +(in unit power per unit surface area per unit solid angle). */ @@ -116,7 +128,10 @@ MTS_VARIANT class DistantSensor final : public Sensor { } ray.update(); - return std::make_pair(ray, wav_weight); + return std::make_pair( + ray, m_has_target + ? wav_weight + : wav_weight * (math::Pi * sqr(m_bsphere.radius))); } std::pair sample_ray_differential( @@ -153,7 +168,10 @@ MTS_VARIANT class DistantSensor final : public Sensor { ray.has_differentials = false; ray.update(); - return std::make_pair(ray, wav_weight); + return std::make_pair( + ray, m_has_target + ? wav_weight + : wav_weight * (math::Pi * sqr(m_bsphere.radius))); } /// This sensor does not occupy any particular region of space, return an diff --git a/src/sensors/tests/test_distant.py b/src/sensors/tests/test_distant.py index b166c26bb..e823754a7 100644 --- a/src/sensors/tests/test_distant.py +++ b/src/sensors/tests/test_distant.py @@ -6,29 +6,22 @@ def dict_sensor(direction=None, target=None, fwidth=1): - if direction is None: - dict_direction = {} - else: - dict_direction = {"direction": direction} + result = {"type": "distant"} - if target is None: - dict_target = {} - else: - dict_target = {"target": target} + if direction: + result["direction"] = direction - dict_film = { + if target: + result["target"] = target + + result["film"] = { "type": "hdrfilm", "width": fwidth, "height": 1, "rfilter": {"type": "box"} } - return { - "type": "distant", - **dict_direction, - **dict_target, - "film": dict_film, - } + return result def make_sensor(direction=None, target=None, fwidth=1): @@ -177,45 +170,56 @@ def test_intersection(variant_scalar_rgb, direction): ek.dot(direction, [0, 0, -1]), atol=0.1) -@pytest.mark.parametrize("radiance", [10**x for x in range(-3, 4)]) -def test_render(variant_scalar_rgb, radiance): +def test_render(variant_scalar_rgb): # Test render results with a simple scene from mitsuba.core.xml import load_dict - from mitsuba.core import Bitmap, Struct + from mitsuba.core import Bitmap, Struct, ScalarTransform4f + + for w_e, w_o in zip(([0, 0, -1], [0, 1, -1]), ([0, 0, -1], [0, 1, -1])): + l_e = 1.0 # Emitted radiance + w_e = list(ek.normalize(w_e)) # Emitter direction + w_o = list(ek.normalize(w_o)) # Sensor direction + cos_theta_e = abs(ek.dot(w_e, [0, 0, 1])) + cos_theta_o = abs(ek.dot(w_o, [0, 0, 1])) + + scale = 0.5 + rho = 1.0 # Surface reflectance + surface_area = 4. * scale ** 2 - def dict_scene(radiance=1.0, spp=1): - return { - "type": "scene", - "shape": { + expected = l_e * cos_theta_e * surface_area * rho / np.pi * cos_theta_o + + dict_scene = { + "type": "scene", + "shape": { "type": "rectangle", - "bsdf": {"type": "conductor"}, - }, - "integrator": {"type": "path"}, - "sensor": { - "type": "distant", - "film": { + "to_world": ScalarTransform4f.scale(scale), + "bsdf": {"type": "diffuse", "reflectance": rho}, + }, + "emitter": { + "type": "directional", + "irradiance": l_e, + "direction": w_e + }, + "sensor": { + "type": "distant", + "direction": w_o, + "film": { "type": "hdrfilm", - "width": 1, "height": 1, - "pixel_format": "rgb", + "width": 1, + "pixel_format": "luminance", "rfilter": {"type": "box"}, - }, - "sampler": { - "type": "independent", - "sample_count": spp - }, }, - "emitter": { - "type": "constant", - "radiance": { - "type": "spectrum", - "value": radiance - } - } - } - - scene = load_dict(dict_scene(spp=1, radiance=radiance)) + "sampler": { + "type": "independent", + "sample_count": 512 + }, + }, + "integrator": {"type": "path"} + } + + scene = load_dict(dict_scene) sensor = scene.sensors()[0] scene.integrator().render(scene, sensor) - img = sensor.film().bitmap() - assert np.allclose(np.array(img), radiance) + img = np.array(sensor.film().bitmap()).squeeze() + assert np.allclose(np.array(img), expected)