Skip to content

Commit a1b48b3

Browse files
committed
Add ray offset to prevent self-occlusion for meshes
1 parent 298180f commit a1b48b3

File tree

10 files changed

+104
-19
lines changed

10 files changed

+104
-19
lines changed

docs/references.bib

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,16 @@ @article{Schuessler2017Microfacet
675675
articleno = {205},
676676
numpages = {12},
677677
}
678+
679+
@inbook{Hanika2021,
680+
title = {Hacking the Shadow Terminator},
681+
author = {Hanika, Johannes},
682+
year = {2021},
683+
booktitle = {Ray Tracing Gems II: Next Generation Real-Time Rendering with DXR, Vulkan, and OptiX},
684+
publisher = {Apress},
685+
address = {Berkeley, CA},
686+
pages = {65--76},
687+
doi = {10.1007/978-1-4842-7185-8_4},
688+
isbn = {978-1-4842-7185-8},
689+
editor = {Marrs, Adam and Shirley, Peter and Wald, Ingo}
690+
}

include/mitsuba/python/docstr.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4476,6 +4476,8 @@ static const char *__doc_mitsuba_Interaction_operator_assign_2 = R"doc(//! @})do
44764476

44774477
static const char *__doc_mitsuba_Interaction_p = R"doc(Position of the interaction in world coordinates)doc";
44784478

4479+
static const char *__doc_mitsuba_Interaction_ray_offset = R"doc((Optional) offset to be used to spawn rays from this interaction)doc";
4480+
44794481
static const char *__doc_mitsuba_Interaction_spawn_ray = R"doc(Spawn a semi-infinite ray towards the given direction)doc";
44804482

44814483
static const char *__doc_mitsuba_Interaction_spawn_ray_to = R"doc(Spawn a finite ray towards the given position)doc";

include/mitsuba/render/interaction.h

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ struct Interaction {
100100
/// Position of the interaction in world coordinates
101101
Point3f p;
102102

103+
/// (Optional) offset to be used to spawn rays from this interaction
104+
Vector3f ray_offset;
105+
103106
/// Geometric normal (only valid for \c SurfaceInteraction)
104107
Normal3f n;
105108

@@ -113,7 +116,7 @@ struct Interaction {
113116
/// Constructor
114117
Interaction(Float t, Float time, const Wavelength &wavelengths,
115118
const Point3f &p, const Normal3f &n = 0.f)
116-
: t(t), time(time), wavelengths(wavelengths), p(p), n(n) { }
119+
: t(t), time(time), wavelengths(wavelengths), p(p), ray_offset(0.f), n(n) { }
117120

118121
/// Virtual destructor
119122
virtual ~Interaction() = default;
@@ -124,11 +127,12 @@ struct Interaction {
124127
* field should be set to an infinite value to mark invalid intersection records.
125128
*/
126129
virtual void zero_(size_t size = 1) {
127-
t = dr::full<Float>(dr::Infinity<Float>, size);
128-
time = dr::zeros<Float>(size);
129-
wavelengths = dr::zeros<Wavelength>(size);
130-
p = dr::zeros<Point3f>(size);
131-
n = dr::zeros<Normal3f>(size);
130+
t = dr::full<Float>(dr::Infinity<Float>, size);
131+
time = dr::zeros<Float>(size);
132+
wavelengths = dr::zeros<Wavelength>(size);
133+
p = dr::zeros<Point3f>(size);
134+
ray_offset = dr::zeros<Vector3f>(size);
135+
n = dr::zeros<Normal3f>(size);
132136
}
133137

134138
/// Is the current interaction valid?
@@ -154,7 +158,7 @@ struct Interaction {
154158
//! @}
155159
// =============================================================
156160

157-
DRJIT_STRUCT(Interaction, t, time, wavelengths, p, n);
161+
DRJIT_STRUCT(Interaction, t, time, wavelengths, p, ray_offset, n);
158162

159163
private:
160164
/**
@@ -165,7 +169,7 @@ struct Interaction {
165169
Point3f offset_p(const Vector3f &d) const {
166170
Float mag = (1.f + dr::max(dr::abs(p))) * math::RayEpsilon<Float>;
167171
mag = dr::detach(dr::mulsign(mag, dr::dot(n, d)));
168-
return dr::fmadd(mag, dr::detach(n), p);
172+
return dr::fmadd(mag, dr::detach(n), p + dr::detach(ray_offset));
169173
}
170174
};
171175

@@ -186,7 +190,7 @@ struct SurfaceInteraction : Interaction<Float_, Spectrum_> {
186190
using Spectrum = Spectrum_;
187191

188192
// Make parent fields/functions visible
189-
MI_IMPORT_BASE(Interaction, t, time, wavelengths, p, n, is_valid)
193+
MI_IMPORT_BASE(Interaction, t, time, wavelengths, p, ray_offset, n, is_valid)
190194

191195
MI_IMPORT_RENDER_BASIC_TYPES()
192196
MI_IMPORT_OBJECT_TYPES()
@@ -524,8 +528,8 @@ struct SurfaceInteraction : Interaction<Float_, Spectrum_> {
524528
//! @}
525529
// =============================================================
526530

527-
DRJIT_STRUCT(SurfaceInteraction, t, time, wavelengths, p, n, shape, uv,
528-
sh_frame, dp_du, dp_dv, dn_du, dn_dv, duv_dx,
531+
DRJIT_STRUCT(SurfaceInteraction, t, time, wavelengths, p, ray_offset, n,
532+
shape, uv, sh_frame, dp_du, dp_dv, dn_du, dn_dv, duv_dx,
529533
duv_dy, wi, prim_index, instance)
530534
};
531535

@@ -545,7 +549,7 @@ struct MediumInteraction : Interaction<Float_, Spectrum_> {
545549
using Index = typename CoreAliases::UInt32;
546550

547551
// Make parent fields/functions visible
548-
MI_IMPORT_BASE(Interaction, t, time, wavelengths, p, n, is_valid)
552+
MI_IMPORT_BASE(Interaction, t, time, wavelengths, p, ray_offset, n, is_valid)
549553
//! @}
550554
// =============================================================
551555

@@ -609,7 +613,7 @@ struct MediumInteraction : Interaction<Float_, Spectrum_> {
609613
//! @}
610614
// =============================================================
611615

612-
DRJIT_STRUCT(MediumInteraction, t, time, wavelengths, p, n, medium,
616+
DRJIT_STRUCT(MediumInteraction, t, time, wavelengths, p, ray_offset, n, medium,
613617
sh_frame, wi, sigma_s, sigma_n, sigma_t,
614618
combined_extinction, mint)
615619
};
@@ -755,7 +759,9 @@ std::ostream &operator<<(std::ostream &os, const Interaction<Float, Spectrum> &i
755759
<< " t = " << it.t << "," << std::endl
756760
<< " time = " << it.time << "," << std::endl
757761
<< " wavelengths = " << it.wavelengths << "," << std::endl
758-
<< " p = " << string::indent(it.p, 6) << std::endl
762+
<< " p = " << string::indent(it.p, 6) << "," << std::endl
763+
<< " ray_offset = " << string::indent(it.ray_offset, 15) << "," << std::endl
764+
<< " n = " << string::indent(it.n, 6) << std::endl
759765
<< "]";
760766
}
761767
return os;

include/mitsuba/render/mesh.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ class MI_EXPORT_LIB Mesh : public Shape<Float, Spectrum> {
604604
/// Flag that can be set by the user to disable loading/computation of vertex normals
605605
bool m_face_normals = false;
606606
bool m_flip_normals = false;
607+
ScalarFloat m_ray_offset_scale = 1.0f;
607608

608609
/* Surface area distribution -- generated on demand when \ref
609610
prepare_area_pmf() is first called. */

src/render/mesh.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ MI_VARIANT Mesh<Float, Spectrum>::Mesh(const Properties &props) : Base(props) {
2828
appearance. Default: ``false`` */
2929
m_face_normals = props.get<bool>("face_normals", false);
3030
m_flip_normals = props.get<bool>("flip_normals", false);
31+
m_ray_offset_scale = props.get<ScalarFloat>("ray_offset_scale", 1.0f);
3132

3233
m_discontinuity_types = (uint32_t) DiscontinuityFlags::PerimeterType;
3334

@@ -1553,6 +1554,22 @@ Mesh<Float, Spectrum>::compute_surface_interaction(const Ray3f &ray,
15531554
si.sh_frame.n = si.n;
15541555
}
15551556

1557+
if (has_vertex_normals() &&
1558+
likely(has_flag(ray_flags, RayFlags::ShadingFrame) ||
1559+
has_flag(ray_flags, RayFlags::dNSdUV)) &&
1560+
(m_ray_offset_scale > 0.f)) {
1561+
// Implements "Hacking the shadow terminator" by Johannes Hanika (2021).
1562+
// The code matches the original implementation, but was slightly
1563+
// simplified since b0 * p0 + b1 * p1 + b2 * p2 = si.p, and b0 + b1 + b2 = 1.
1564+
Vector3f tmp0 = p0 - si.p, tmp1 = p1 - si.p, tmp2 = p2 - si.p;
1565+
Float dot0 = dr::maximum(dr::dot(tmp0, n0), 0.f),
1566+
dot1 = dr::maximum(dr::dot(tmp1, n1), 0.f),
1567+
dot2 = dr::maximum(dr::dot(tmp2, n2), 0.f);
1568+
si.ray_offset =
1569+
m_ray_offset_scale *
1570+
dr::detach(dr::fmadd(b0, dot0 * n0, dr::fmadd(b1, dot1 * n1, b2 * dot2 * n2)));
1571+
}
1572+
15561573
if (m_flip_normals) {
15571574
si.n = -si.n;
15581575
si.sh_frame.n = -si.sh_frame.n;

src/render/python/interaction_v.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ MI_PY_EXPORT(Interaction) {
1616
.def_field(Interaction3f, time, D(Interaction, time))
1717
.def_field(Interaction3f, wavelengths, D(Interaction, wavelengths))
1818
.def_field(Interaction3f, p, D(Interaction, p))
19+
.def_field(Interaction3f, ray_offset, D(Interaction, ray_offset))
1920
.def_field(Interaction3f, n, D(Interaction, n))
2021
// Methods
2122
.def(nb::init<>(), D(Interaction, Interaction))
@@ -30,7 +31,7 @@ MI_PY_EXPORT(Interaction) {
3031
.def("zero_", &Interaction3f::zero_, D(Interaction, zero))
3132
.def_repr(Interaction3f);
3233

33-
MI_PY_DRJIT_STRUCT(it, Interaction3f, t, time, wavelengths, p, n)
34+
MI_PY_DRJIT_STRUCT(it, Interaction3f, t, time, wavelengths, p, ray_offset, n)
3435
}
3536

3637
MI_PY_EXPORT(SurfaceInteraction) {
@@ -91,7 +92,7 @@ MI_PY_EXPORT(SurfaceInteraction) {
9192
D(SurfaceInteraction, has_n_partials))
9293
.def_repr(SurfaceInteraction3f);
9394

94-
MI_PY_DRJIT_STRUCT(si, SurfaceInteraction3f, t, time, wavelengths, p, n,
95+
MI_PY_DRJIT_STRUCT(si, SurfaceInteraction3f, t, time, wavelengths, p, ray_offset, n,
9596
shape, uv, sh_frame, dp_du, dp_dv, dn_du, dn_dv, duv_dx,
9697
duv_dy, wi, prim_index, instance)
9798
}
@@ -118,7 +119,7 @@ MI_PY_EXPORT(MediumInteraction) {
118119
.def("to_local", &MediumInteraction3f::to_local, "v"_a, D(MediumInteraction, to_local))
119120
.def_repr(MediumInteraction3f);
120121

121-
MI_PY_DRJIT_STRUCT(mi, MediumInteraction3f, t, time, wavelengths, p, n,
122+
MI_PY_DRJIT_STRUCT(mi, MediumInteraction3f, t, time, wavelengths, p, ray_offset, n,
122123
medium, sh_frame, wi, sigma_s, sigma_n, sigma_t,
123124
combined_extinction, mint)
124125
}

src/render/tests/test_interaction.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test02_intersection_construction(variant_scalar_rgb):
4242
time=2,
4343
wavelengths=[],
4444
p=[1, 2, 3],
45+
ray_offset=[0, 0, 0],
4546
n=[4, 5, 6],
4647
shape=0x0,
4748
uv=[7, 8],

src/render/tests/test_mesh.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,3 +1410,29 @@ def test36_mesh_vcalls_with_directed_edges(variants_vec_rgb):
14101410

14111411
result = mesh_ptr.opposite_dedge(mi.UInt32([2, 3, 2]))
14121412
assert dr.all(result == mi.UInt32([3, 2, 3]))
1413+
1414+
1415+
@pytest.mark.parametrize("ray_offset_scale", [0.0, 1.0])
1416+
def test37_mesh_ray_offset(variants_vec_rgb, ray_offset_scale):
1417+
props = mi.Properties()
1418+
props["ray_offset_scale"] = ray_offset_scale
1419+
mesh = mi.Mesh(props)
1420+
params = mi.traverse(mesh)
1421+
params['vertex_positions'] = [-1, -1, 0, 1, -1, 0, 0, 1, 0]
1422+
params['faces'] = [0, 1, 2]
1423+
params.update()
1424+
params['vertex_normals'] = dr.ravel(dr.normalize(
1425+
mi.Vector3f([-1, 1, 0], [-1, -1, 1], [1, 1, 1])))
1426+
params.update()
1427+
scene = mi.load_dict({
1428+
"type": "scene",
1429+
"mesh": mesh,
1430+
})
1431+
si = scene.ray_intersect(mi.Ray3f([0, 0, 1], [0, 0, -1]))
1432+
ray = si.spawn_ray(dr.normalize(mi.Vector3f(1, -2, 3)))
1433+
if ray_offset_scale == 0.0:
1434+
assert dr.allclose(si.ray_offset, 0.0)
1435+
assert dr.allclose(ray.o.z, 0.0, atol=1e-4)
1436+
else:
1437+
assert dr.allclose(si.ray_offset.x, 0.0) # Due to symmetry, x offset is 0.
1438+
assert ray.o.z > 0.5 # Offset along z axis should be significant.

src/shapes/obj.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Wavefront OBJ mesh loader (:monosp:`obj`)
2020
-----------------------------------------
2121
2222
.. pluginparameters::
23-
:extra-rows: 5
23+
:extra-rows: 6
2424
2525
* - filename
2626
- |string|
@@ -41,6 +41,15 @@ Wavefront OBJ mesh loader (:monosp:`obj`)
4141
- Is the mesh inverted, i.e. should the normal vectors be flipped? (Default:|false|, i.e.
4242
the normals point outside)
4343
44+
* - ray_offset_scale
45+
- |float|
46+
- Scale factor used to offset ray origins when tracing secondary rays
47+
(e.g., shadow rays) to avoid self-intersections. Rays are offset
48+
according to the mismatch between geometric and shading normals, following
49+
:cite:`Hanika2021`. This offset can be reduced by using a factor < 1,
50+
and disabled entirely by setting it to 0 (e.g., to prevent light leaks).
51+
(Default: 1.0)
52+
4453
* - to_world
4554
- |transform|
4655
- Specifies an optional linear object-to-world transformation.

src/shapes/ply.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ PLY (Stanford Triangle Format) mesh loader (:monosp:`ply`)
2323
----------------------------------------------------------
2424
2525
.. pluginparameters::
26-
:extra-rows: 4
26+
:extra-rows: 5
2727
2828
* - filename
2929
- |string|
@@ -44,6 +44,15 @@ PLY (Stanford Triangle Format) mesh loader (:monosp:`ply`)
4444
- Is the mesh inverted, i.e. should the normal vectors be flipped? (Default:|false|, i.e.
4545
the normals point outside)
4646
47+
* - ray_offset_scale
48+
- |float|
49+
- Scale factor used to offset ray origins when tracing secondary rays
50+
(e.g., shadow rays) to avoid self-intersections. Rays are offset
51+
according to the mismatch between geometric and shading normals, following
52+
:cite:`Hanika2021`. This offset can be reduced by using a factor < 1,
53+
and disabled entirely by setting it to 0 (e.g., to prevent light leaks).
54+
(Default: 1.0)
55+
4756
* - to_world
4857
- |transform|
4958
- Specifies an optional linear object-to-world transformation.

0 commit comments

Comments
 (0)