Skip to content

Commit 705c22e

Browse files
committed
Filled out methods in volumelight.cpp and added appropriate tests for both shapes and volumelights.
1 parent bbcb3a4 commit 705c22e

File tree

23 files changed

+499
-79
lines changed

23 files changed

+499
-79
lines changed

include/mitsuba/python/docstr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,6 +4082,9 @@ static const char *__doc_mitsuba_MediumInteraction_sigma_s = R"doc()doc";
40824082

40834083
static const char *__doc_mitsuba_MediumInteraction_sigma_t = R"doc()doc";
40844084

4085+
static const char *__doc_mitsuba_MediumInteraction_emitter =
4086+
R"doc(Return the emitter associated with the intersection (if any))doc";
4087+
40854088
static const char *__doc_mitsuba_MediumInteraction_to_local = R"doc(Convert a world-space vector into local shading coordinates)doc";
40864089

40874090
static const char *__doc_mitsuba_MediumInteraction_to_world = R"doc(Convert a local shading-space vector into world space)doc";

include/mitsuba/render/interaction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,10 @@ struct MediumInteraction : Interaction<Float_, Spectrum_> {
552552
//! @{ \name Methods
553553
// =============================================================
554554

555+
/// Get emitter attached to the medium associated with this interaction
556+
/// \note Defined in scene.h
557+
EmitterPtr emitter(Mask active = true) const;
558+
555559

556560
/// Convert a local shading-space vector into world space
557561
Vector3f to_world(const Vector3f &v) const {

include/mitsuba/render/medium.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ MI_DECLARE_ENUM_OPERATORS(MediumEventSamplingMode)
1818
template <typename Float, typename Spectrum>
1919
class MI_EXPORT_LIB Medium : public Object {
2020
public:
21-
MI_IMPORT_TYPES(PhaseFunction, Sampler, Scene, Texture, Emitter, Volume);
21+
MI_IMPORT_TYPES(PhaseFunction, Sampler, Scene, Texture, Emitter, Volume, EmitterPtr);
2222

2323
/// Intersects a ray with the medium's bounding box
2424
virtual std::tuple<Mask, Float, Float>
@@ -123,7 +123,12 @@ class MI_EXPORT_LIB Medium : public Object {
123123
}
124124

125125
/// Return the emitter of this medium
126-
MI_INLINE const Emitter *emitter() const {
126+
const EmitterPtr emitter(Mask /*unused*/ = true) const {
127+
return m_emitter.get();
128+
}
129+
130+
/// Return the emitter of this medium
131+
EmitterPtr emitter(Mask /*unused*/ = true) {
127132
return m_emitter.get();
128133
}
129134

@@ -185,7 +190,6 @@ DRJIT_VCALL_TEMPLATE_BEGIN(mitsuba::Medium)
185190
DRJIT_VCALL_GETTER(emitter, const typename Class::Emitter*)
186191
DRJIT_VCALL_GETTER(use_emitter_sampling, bool)
187192
DRJIT_VCALL_GETTER(is_homogeneous, bool)
188-
DRJIT_VCALL_GETTER(is_emitter, bool)
189193
DRJIT_VCALL_GETTER(has_spectral_extinction, bool)
190194
DRJIT_VCALL_METHOD(get_majorant)
191195
DRJIT_VCALL_METHOD(get_radiance)
@@ -197,6 +201,7 @@ DRJIT_VCALL_TEMPLATE_BEGIN(mitsuba::Medium)
197201
DRJIT_VCALL_METHOD(medium_probabilities_analog)
198202
DRJIT_VCALL_METHOD(medium_probabilities_max)
199203
DRJIT_VCALL_METHOD(medium_probabilities_mean)
204+
auto is_emitter() const { return neq(emitter(), nullptr); }
200205
DRJIT_VCALL_TEMPLATE_END(mitsuba::Medium)
201206

202207
//! @}

include/mitsuba/render/mesh.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,15 @@ class MI_EXPORT_LIB Mesh : public Shape<Float, Spectrum> {
334334
*/
335335
void build_parameterization();
336336

337+
/**
338+
* \brief Initialize the \c m_volume_parameterization field for running
339+
* ray/object intersections.
340+
*
341+
* Internally, the function creates a nested scene to leverage optimized
342+
* ray tracing functionality in \ref pdf_position_3d()
343+
*/
344+
void build_volume_parameterization();
345+
337346
// Ensures that the sampling table are ready.
338347
DRJIT_INLINE void ensure_pmf_built() const {
339348
if (unlikely(m_area_pmf.empty()))
@@ -487,7 +496,7 @@ class MI_EXPORT_LIB Mesh : public Shape<Float, Spectrum> {
487496
Float m_inv_volume;
488497

489498
/// Optional: used in eval_parameterization()
490-
ref<Scene<Float, Spectrum>> m_parameterization;
499+
ref<Scene<Float, Spectrum>> m_parameterization, m_volume_parameterization;
491500

492501
/// Pointer to the scene that owns this mesh
493502
Scene<Float, Spectrum>* m_scene = nullptr;

include/mitsuba/render/records.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct PositionSample {
2626
using Spectrum = Spectrum_;
2727
MI_IMPORT_RENDER_BASIC_TYPES()
2828
using SurfaceInteraction3f = typename RenderAliases::SurfaceInteraction3f;
29+
using MediumInteraction3f = typename RenderAliases::MediumInteraction3f;
2930

3031
//! @}
3132
// =============================================================
@@ -77,6 +78,17 @@ struct PositionSample {
7778
: p(si.p), n(si.sh_frame.n), uv(si.uv), time(si.time), pdf(0.f),
7879
delta(false) { }
7980

81+
/**
82+
* \brief Create a position sampling record from a medium interaction
83+
*
84+
* This is useful to determine the hypothetical sampling density in a
85+
* medium after hitting it using standard ray tracing. This happens for
86+
* instance in volumetric path tracing with multiple importance sampling.
87+
*/
88+
PositionSample(const MediumInteraction3f &mei)
89+
: p(mei.p), n(mei.sh_frame.n), uv(0.f), time(mei.time), pdf(0.f),
90+
delta(false) { }
91+
8092
/// Basic field constructor
8193
PositionSample(const Point3f &p, const Normal3f &n, const Point2f &uv,
8294
Float time, Float pdf, Mask delta)
@@ -119,7 +131,9 @@ struct DirectionSample : public PositionSample<Float_, Spectrum_> {
119131

120132
using Interaction3f = typename RenderAliases::Interaction3f;
121133
using SurfaceInteraction3f = typename RenderAliases::SurfaceInteraction3f;
134+
using MediumInteraction3f = typename RenderAliases::MediumInteraction3f;
122135
using EmitterPtr = typename RenderAliases::EmitterPtr;
136+
using MediumPtr = typename RenderAliases::MediumPtr;
123137

124138
//! @}
125139
// =============================================================
@@ -179,6 +193,29 @@ struct DirectionSample : public PositionSample<Float_, Spectrum_> {
179193
emitter = si.emitter(scene);
180194
}
181195

196+
/**
197+
* \brief Create a direct sampling record, which can be used to \a query
198+
* the density of a medium position with respect to a given reference
199+
* position.
200+
*
201+
* Direction `s` is set so that it points from the reference interaction to
202+
* the interacted medium, as required when using e.g. the \ref Endpoint
203+
* interface to compute PDF values.
204+
*
205+
* \param it
206+
* Medium interaction
207+
*
208+
* \param ref
209+
* Reference position
210+
*/
211+
DirectionSample(const MediumInteraction3f &mei,
212+
const Interaction3f &ref) : Base(mei) {
213+
Vector3f rel = mei.p - ref.p;
214+
dist = dr::norm(rel);
215+
d = select(mei.is_valid(), rel / dist, -mei.wi);
216+
emitter = mei.emitter();
217+
}
218+
182219
/// Element-by-element constructor
183220
DirectionSample(const Point3f &p, const Normal3f &n, const Point2f &uv,
184221
const Float &time, const Float &pdf, const Mask &delta,

include/mitsuba/render/scene.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,5 +599,18 @@ SurfaceInteraction<Float, Spectrum>::emitter(const Scene *scene, Mask active) co
599599
}
600600
}
601601

602+
// See interaction.h
603+
template <typename Float, typename Spectrum>
604+
typename MediumInteraction<Float, Spectrum>::EmitterPtr
605+
MediumInteraction<Float, Spectrum>::emitter(Mask active) const {
606+
if constexpr (!dr::is_jit_v<Float>) {
607+
DRJIT_MARK_USED(active);
608+
return is_valid() ? medium->emitter() : nullptr;
609+
} else {
610+
EmitterPtr emitter = medium->emitter(active & is_valid());
611+
return emitter;
612+
}
613+
}
614+
602615
MI_EXTERN_CLASS(Scene)
603616
NAMESPACE_END(mitsuba)

include/mitsuba/render/shape.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ NAMESPACE_BEGIN(mitsuba)
2525
template <typename Float, typename Spectrum>
2626
class MI_EXPORT_LIB Shape : public Object {
2727
public:
28-
MI_IMPORT_TYPES(BSDF, Medium, Emitter, Sensor, MeshAttribute, Texture);
28+
MI_IMPORT_TYPES(BSDF, Medium, Volume, Emitter, Sensor, MeshAttribute, Texture);
2929

3030
// Use 32 bit indices to keep track of indices to conserve memory
3131
using ScalarIndex = uint32_t;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import pytest
2+
import drjit as dr
3+
import mitsuba as mi
4+
5+
from mitsuba.scalar_rgb.test.util import fresolver_append_path
6+
7+
8+
spectrum_dicts = {
9+
'd65': {
10+
"type": "d65",
11+
},
12+
'regular': {
13+
"type": "regular",
14+
"wavelength_min": 500,
15+
"wavelength_max": 600,
16+
"values": "1, 2"
17+
}
18+
}
19+
20+
21+
@fresolver_append_path
22+
def create_emitter_and_spectrum(s_key='d65'):
23+
emitter = mi.load_dict({
24+
"type": "obj",
25+
"filename": "resources/data/tests/obj/cbox_smallbox.obj",
26+
"emitter" : { "type": "volumelight", "radiance" : spectrum_dicts[s_key] }
27+
})
28+
spectrum = mi.load_dict(spectrum_dicts[s_key])
29+
expanded = spectrum.expand()
30+
if len(expanded) == 1:
31+
spectrum = expanded[0]
32+
33+
return emitter, spectrum
34+
35+
36+
def test01_constructor(variant_scalar_rgb):
37+
# Check that the shape is properly bound to the emitter
38+
shape, spectrum = create_emitter_and_spectrum()
39+
assert shape.emitter().bbox() == shape.bbox()
40+
41+
# Check that we are not allowed to specify a to_world transform directly in the emitter.
42+
with pytest.raises(RuntimeError):
43+
e = mi.load_dict({
44+
"type" : "volumelight",
45+
"to_world" : mi.ScalarTransform4f.translate([5, 0, 0])
46+
})
47+
48+
49+
@pytest.mark.parametrize("spectrum_key", spectrum_dicts.keys())
50+
def test02_eval(variants_vec_spectral, spectrum_key):
51+
# Check that eval() return the same values as the 'radiance' spectrum
52+
53+
shape, spectrum = create_emitter_and_spectrum(spectrum_key)
54+
emitter = shape.emitter()
55+
56+
it = dr.zeros(mi.SurfaceInteraction3f, 3)
57+
assert dr.allclose(emitter.eval(it), spectrum.eval(it))
58+
59+
# Check that eval returns 0.0 when the sample point is outside the shape
60+
it.p = mi.ScalarPoint3f([0.0, 0.0, 0.0])
61+
assert dr.allclose(emitter.eval(it), 0.0)
62+
63+
64+
@pytest.mark.parametrize("spectrum_key", spectrum_dicts.keys())
65+
def test03_sample_ray(variants_vec_spectral, spectrum_key):
66+
# Check the correctness of the sample_ray() method
67+
68+
shape, spectrum = create_emitter_and_spectrum(spectrum_key)
69+
emitter = shape.emitter()
70+
71+
time = 0.5
72+
wavelength_sample = [0.5, 0.33, 0.1]
73+
pos_sample = [[0.2, 0.1, 0.2], [0.6, 0.9, 0.2], [0.5]*3]
74+
dir_sample = [[0.4, 0.5, 0.3], [0.1, 0.4, 0.9]]
75+
76+
# Sample a ray (position, direction, wavelengths) on the emitter
77+
ray, res = emitter.sample_ray(time, wavelength_sample, pos_sample, dir_sample)
78+
79+
# Sample wavelengths on the spectrum
80+
it = dr.zeros(mi.SurfaceInteraction3f, 3)
81+
82+
# Sample a position in the shape
83+
ps = shape.sample_position_3d(time, pos_sample)
84+
pdf = mi.warp.square_to_uniform_sphere_pdf(mi.warp.square_to_uniform_sphere(dir_sample))
85+
it.p = ps.p
86+
it.n = ps.n
87+
it.time = time
88+
it.wavelengths = mi.Float(wavelength_sample) * (mi.MI_CIE_MAX - mi.MI_CIE_MIN) + mi.MI_CIE_MIN
89+
90+
spec = dr.select(ps.pdf > 0.0, emitter.eval(it) * (mi.MI_CIE_MAX - mi.MI_CIE_MIN) / (ps.pdf * pdf), 0.0)
91+
assert dr.allclose(res, spec)
92+
assert dr.allclose(ray.time, time)
93+
assert dr.allclose(ray.o, ps.p, atol=2e-2)
94+
assert dr.allclose(ray.d, mi.Frame3f(ps.n).to_world(mi.warp.square_to_uniform_sphere(dir_sample)))
95+
96+
97+
@pytest.mark.parametrize("spectrum_key", spectrum_dicts.keys())
98+
def test04_sample_direction(variants_vec_spectral, spectrum_key):
99+
# Check the correctness of the sample_direction(), pdf_direction(), and eval_direction() methods
100+
101+
shape, spectrum = create_emitter_and_spectrum(spectrum_key)
102+
emitter = shape.emitter()
103+
104+
# Direction sampling is conditioned on a sampled position
105+
it = dr.zeros(mi.SurfaceInteraction3f, 3)
106+
it.p = [[0.2, 0.1, 0.2], [0.6, -0.9, 0.2],
107+
[0.4, 0.9, -0.2]] # Some positions
108+
it.time = 1.0
109+
110+
# Sample direction on the emitter
111+
samples = [[0.4, 0.5, 0.3], [0.1, 0.4, 0.9], [0.5]*3]
112+
ds, res = emitter.sample_direction(it, samples)
113+
114+
# Sample direction on the shape
115+
sphere_ds = dr.zeros(mi.DirectionSample3f)
116+
sphere_ds.p = it.p
117+
ps = shape.sample_position_3d(it.time, samples)
118+
sphere_ds.d = ps.p - it.p
119+
dist2 = dr.squared_norm(sphere_ds.d)
120+
sphere_ds.d = sphere_ds.d / dr.sqrt(dist2)
121+
sphere_ds.pdf = ps.pdf * dist2
122+
123+
assert dr.allclose(ds.pdf, sphere_ds.pdf)
124+
assert dr.allclose(ds.pdf, emitter.pdf_direction(it, ds))
125+
assert dr.allclose(ds.d, sphere_ds.d, atol=1e-3)
126+
assert dr.allclose(ds.time, it.time)
127+
128+
# Evaluate the spectrum (divide by the pdf)
129+
spec = dr.select(ds.pdf > 0.0, emitter.eval(it) / ds.pdf, 0.0)
130+
assert dr.allclose(res, spec)
131+
132+
assert dr.allclose(emitter.eval_direction(it, ds), spec)

0 commit comments

Comments
 (0)