diff --git a/include/mitsuba/python/docstr.h b/include/mitsuba/python/docstr.h index aebae83b0..283852d69 100644 --- a/include/mitsuba/python/docstr.h +++ b/include/mitsuba/python/docstr.h @@ -754,6 +754,13 @@ inexpensively. When this is not possible, the value is approximated by evaluating the BSDF for a normal outgoing direction and returning this value multiplied by pi. This is the default behaviour of this method. +Parameter ``si``: + A surface interaction data structure describing the underlying + surface position.)doc"; + +static const char *__doc_mitsuba_BSDF_eval_roughness = +R"doc(Evaluate the roughness + Parameter ``si``: A surface interaction data structure describing the underlying surface position.)doc"; diff --git a/include/mitsuba/render/bsdf.h b/include/mitsuba/render/bsdf.h index 084286085..ee590c19c 100644 --- a/include/mitsuba/render/bsdf.h +++ b/include/mitsuba/render/bsdf.h @@ -519,6 +519,23 @@ class MI_EXPORT_LIB BSDF : public Object { virtual Spectrum eval_diffuse_reflectance(const SurfaceInteraction3f &si, Mask active = true) const; + /** + * \brief Evaluate roughness + * + * This method approximates the roughness for a given + * direction. For some materials, an exact value can be computed + * inexpensively. + * When this is not possible, the value is approximated by + * evaluating the BSDF for a normal outgoing direction and returning this + * value multiplied by pi. This is the default behaviour of this method. + * + * \param si + * A surface interaction data structure describing the underlying + * surface position. + */ + virtual Float eval_roughness(const SurfaceInteraction3f &si, + Mask active = true) const; + /// Return a human-readable representation of the BSDF std::string to_string() const override = 0; @@ -600,6 +617,7 @@ DRJIT_VCALL_TEMPLATE_BEGIN(mitsuba::BSDF) DRJIT_VCALL_METHOD(eval_pdf) DRJIT_VCALL_METHOD(eval_pdf_sample) DRJIT_VCALL_METHOD(eval_diffuse_reflectance) + DRJIT_VCALL_METHOD(eval_roughness) DRJIT_VCALL_GETTER(flags, uint32_t) auto needs_differentials() const { return has_flag(flags(), mitsuba::BSDFFlags::NeedsDifferentials); diff --git a/src/bsdfs/bumpmap.cpp b/src/bsdfs/bumpmap.cpp index e956d3f58..6f3fa25d0 100644 --- a/src/bsdfs/bumpmap.cpp +++ b/src/bsdfs/bumpmap.cpp @@ -226,6 +226,11 @@ class BumpMap final : public BSDF { return m_nested_bsdf->eval_diffuse_reflectance(si, active); } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_nested_bsdf->eval_roughness(si, active); + } + std::string to_string() const override { std::ostringstream oss; oss << "BumpMap[" << std::endl diff --git a/src/bsdfs/conductor.cpp b/src/bsdfs/conductor.cpp index 6f8cf3966..edc454267 100644 --- a/src/bsdfs/conductor.cpp +++ b/src/bsdfs/conductor.cpp @@ -310,6 +310,12 @@ class SmoothConductor final : public BSDF { return 0.f; } + Float eval_roughness(const SurfaceInteraction3f & /*si*/, + Mask /*active*/) const override { + return Float(0.0f); + } + + std::string to_string() const override { std::ostringstream oss; oss << "SmoothConductor[" << std::endl diff --git a/src/bsdfs/mask.cpp b/src/bsdfs/mask.cpp index 6c6090134..99b03b1ed 100644 --- a/src/bsdfs/mask.cpp +++ b/src/bsdfs/mask.cpp @@ -225,6 +225,11 @@ class MaskBSDF final : public BSDF { return m_nested_bsdf->eval_diffuse_reflectance(si, active); } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_nested_bsdf->eval_roughness(si, active); + } + std::string to_string() const override { std::ostringstream oss; oss << "Mask[" << std::endl diff --git a/src/bsdfs/plastic.cpp b/src/bsdfs/plastic.cpp index e853af7b7..355dc1219 100644 --- a/src/bsdfs/plastic.cpp +++ b/src/bsdfs/plastic.cpp @@ -365,6 +365,11 @@ class SmoothPlastic final : public BSDF { return m_diffuse_reflectance->eval(si, active); } + Float eval_roughness(const SurfaceInteraction3f &/*si*/, + Mask /*active*/) const override { + return Float(0.0f); + } + std::string to_string() const override { std::ostringstream oss; oss << "SmoothPlastic[" << std::endl diff --git a/src/bsdfs/principled.cpp b/src/bsdfs/principled.cpp index b8d8bcceb..c0ec2cccc 100644 --- a/src/bsdfs/principled.cpp +++ b/src/bsdfs/principled.cpp @@ -842,6 +842,11 @@ class Principled final : public BSDF { return m_base_color->eval(si, active); } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_roughness->eval_1(si, active); + } + std::string to_string() const override { std::ostringstream oss; oss << "Principled BSDF :" << std::endl diff --git a/src/bsdfs/principledthin.cpp b/src/bsdfs/principledthin.cpp index 3f43e8063..018875aed 100644 --- a/src/bsdfs/principledthin.cpp +++ b/src/bsdfs/principledthin.cpp @@ -710,6 +710,11 @@ class PrincipledThin final : public BSDF { return m_base_color->eval(si, active); } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_roughness->eval_1(si, active); + } + std::string to_string() const override { std::ostringstream oss; oss << "The Thin Principled BSDF :" << std::endl diff --git a/src/bsdfs/roughconductor.cpp b/src/bsdfs/roughconductor.cpp index 77df7e5f5..fa8cd1f4f 100755 --- a/src/bsdfs/roughconductor.cpp +++ b/src/bsdfs/roughconductor.cpp @@ -495,6 +495,12 @@ class RoughConductor final : public BSDF { return { F * value & active, dr::select(active, pdf, 0.f) }; } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_alpha_u->eval_1(si, active); + } + + std::string to_string() const override { std::ostringstream oss; oss << "RoughConductor[" << std::endl diff --git a/src/bsdfs/roughdielectric.cpp b/src/bsdfs/roughdielectric.cpp index cb2d82502..c679563a8 100644 --- a/src/bsdfs/roughdielectric.cpp +++ b/src/bsdfs/roughdielectric.cpp @@ -603,6 +603,11 @@ class RoughDielectric final : public BSDF { dr::select(active, pdf * dr::abs(dwh_dwo), 0.f) }; } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + return m_alpha_u->eval_1(si, active); + } + std::string to_string() const override { std::ostringstream oss; oss << "RoughDielectric[" << std::endl diff --git a/src/bsdfs/roughplastic.cpp b/src/bsdfs/roughplastic.cpp index bd6ea6235..a0f6ac2a3 100644 --- a/src/bsdfs/roughplastic.cpp +++ b/src/bsdfs/roughplastic.cpp @@ -506,6 +506,11 @@ class RoughPlastic final : public BSDF { return m_diffuse_reflectance->eval(si, active); } + Float eval_roughness(const SurfaceInteraction3f & /*si*/, + Mask /*active*/) const override { + return m_alpha; + } + std::string to_string() const override { std::ostringstream oss; oss << "RoughPlastic[" << std::endl diff --git a/src/bsdfs/tests/test_twosided.py b/src/bsdfs/tests/test_twosided.py index 475bbf36e..fac782d39 100644 --- a/src/bsdfs/tests/test_twosided.py +++ b/src/bsdfs/tests/test_twosided.py @@ -134,3 +134,42 @@ def test03_eval_diffuse_reflectance(variants_vec_rgb): assert dr.allclose(dr.select(up, value - value_front, 0), 0.0) assert dr.allclose(dr.select(up, 0, value - value_back), 0.0) + + + +def test04_eval_roughness(variants_vec_rgb): + bsdf_front = mi.load_dict({ + 'type': 'roughconductor', + 'alpha': 0.1 + }) + bsdf_back = mi.load_dict({ + 'type': 'principled', + 'roughness': 0.5 + }) + bsdf = mi.load_dict({ + 'type': 'twosided', + 'a': bsdf_front, + 'b': bsdf_back, + }) + + si = mi.SurfaceInteraction3f() + si.t = 0.1 + si.p = [0, 0, 0] + si.n = [0, 0, 1] + si.sh_frame = mi.Frame3f(si.n) + + n = 5 + epsilon = 0.0001 + for u in dr.linspace(mi.Float, epsilon, 1 - epsilon, n): + for v in dr.linspace(mi.Float, epsilon, 1 - epsilon, n): + si.wi = mi.warp.square_to_uniform_sphere([u / float(n-1), + v / float(n-1)]) + up = mi.Frame3f.cos_theta(si.wi) > 0.0 + + value = bsdf.eval_roughness(si) + value_front = bsdf_front.eval_roughness(si) + si.wi.z *= -1 + value_back = bsdf_back.eval_roughness(si) + + assert dr.allclose(dr.select(up, value - value_front, 0), 0.0) + assert dr.allclose(dr.select(up, 0, value - value_back), 0.0) diff --git a/src/bsdfs/twosided.cpp b/src/bsdfs/twosided.cpp index 106466e1f..f27ec803d 100644 --- a/src/bsdfs/twosided.cpp +++ b/src/bsdfs/twosided.cpp @@ -281,6 +281,30 @@ class TwoSidedBRDF final : public BSDF { return result; } } + Float eval_roughness(const SurfaceInteraction3f &si_, + Mask active) const override { + SurfaceInteraction3f si(si_); + + if (m_brdf[0] == m_brdf[1]) { + si.wi.z() = dr::abs(si.wi.z()); + return m_brdf[0]->eval_roughness(si, active); + } else { + Float result = 0.f; + Mask front_side = Frame3f::cos_theta(si.wi) > 0.f && active, + back_side = Frame3f::cos_theta(si.wi) < 0.f && active; + + if (dr::any_or(front_side)) + result = m_brdf[0]->eval_roughness(si, front_side); + + if (dr::any_or(back_side)) { + si.wi.z() *= -1.f; + dr::masked(result, back_side) = + m_brdf[1]->eval_roughness(si, back_side); + } + + return result; + } + } std::string to_string() const override { std::ostringstream oss; diff --git a/src/integrators/aov.cpp b/src/integrators/aov.cpp index 39f5dea7b..ecf7ad67a 100644 --- a/src/integrators/aov.cpp +++ b/src/integrators/aov.cpp @@ -63,6 +63,7 @@ output file. Currently, the following AOVs types are available: - :monosp:`albedo`: Albedo (diffuse reflectance) of the material. + - :monosp:`roughness`: Roughness of the material (returns 1.0 if not applicable). - :monosp:`depth`: Distance from the pinhole. - :monosp:`position`: World space position value. - :monosp:`uv`: UV coordinates. @@ -80,7 +81,7 @@ wide pixel reconstruction filter as it will result in fractional values. The :monosp:`albedo` AOV will evaluate the diffuse reflectance (\ref BSDF::eval_diffuse_reflectance) of the material. Note that depending on -the material, this value might only be an approximation. +the material, this value might only be an approximation. Likewise for the :monosp:`roughness`. */ template @@ -91,6 +92,7 @@ class AOVIntegrator final : public SamplingIntegrator { enum class Type { Albedo, + Roughness, Depth, Position, UV, @@ -120,6 +122,10 @@ class AOVIntegrator final : public SamplingIntegrator { m_aov_names.push_back(item[0] + ".R"); m_aov_names.push_back(item[0] + ".G"); m_aov_names.push_back(item[0] + ".B"); + } + else if (item[1] == "roughness") { + m_aov_types.push_back(Type::Roughness); + m_aov_names.push_back(item[0] + ".R"); } else if (item[1] == "depth") { m_aov_types.push_back(Type::Depth); m_aov_names.push_back(item[0] + ".T"); @@ -244,6 +250,13 @@ class AOVIntegrator final : public SamplingIntegrator { *aovs++ = rgb.b(); } break; + case Type::Roughness: { + BSDFPtr bsdf = si.bsdf(ray); + Float rough = bsdf->eval_roughness(si, active); + + *aovs++ = Float(rough); + } + break; case Type::Depth: *aovs++ = dr::select(si.is_valid(), si.t, 0.f); break; diff --git a/src/render/bsdf.cpp b/src/render/bsdf.cpp index a544cdfbe..992b92e80 100644 --- a/src/render/bsdf.cpp +++ b/src/render/bsdf.cpp @@ -40,6 +40,11 @@ MI_VARIANT Spectrum BSDF::eval_diffuse_reflectance( return eval(ctx, si, wo, active) * dr::Pi; } +MI_VARIANT Float BSDF::eval_roughness( + const SurfaceInteraction3f & /* si */, Mask /* active */) const { + return 1.f; +} + template std::string type_mask_to_string(Index type_mask) { std::ostringstream oss; diff --git a/src/render/python/bsdf_v.cpp b/src/render/python/bsdf_v.cpp index 7f0edc033..5974d1208 100644 --- a/src/render/python/bsdf_v.cpp +++ b/src/render/python/bsdf_v.cpp @@ -64,6 +64,10 @@ MI_VARIANT class PyBSDF : public BSDF { Mask active) const override { PYBIND11_OVERRIDE_PURE(Spectrum, BSDF, eval_diffuse_reflectance, si, active); } + Float eval_roughness(const SurfaceInteraction3f &si, + Mask active) const override { + PYBIND11_OVERRIDE_PURE(Float, BSDF, eval_roughness, si, active); + } std::string to_string() const override { PYBIND11_OVERRIDE_PURE(std::string, BSDF, to_string,); @@ -112,6 +116,10 @@ template void bind_bsdf_generic(Cls &cls) { [](Ptr bsdf, const SurfaceInteraction3f &si, Mask active) { return bsdf->eval_diffuse_reflectance(si, active); }, "si"_a, "active"_a = true, D(BSDF, eval_diffuse_reflectance)) + .def("eval_roughness", + [](Ptr bsdf, const SurfaceInteraction3f &si, Mask active) { + return bsdf->eval_roughness(si, active); + }, "si"_a, "active"_a = true, D(BSDF, eval_roughness)) .def("flags", [](Ptr bsdf) { return bsdf->flags(); }, D(BSDF, flags)) .def("needs_differentials", [](Ptr bsdf) { return bsdf->needs_differentials(); },