Skip to content

Commit 36b1452

Browse files
authored
bsdl: Implement MX conductor with multiple scattering (#1991)
This moves the BSDF to libbsdl so we use the albedo tables infrastructure. Adds roughness regularization and energy compensation (multiple scattering, Turquin style). I removed the thinfilm options that were not implemented and don't exist in the MaterialX spec. Also I propose thinfilm to be implemented as a decoration via vertical layering so we don't repeat the parameters in every BSDF/closure. Signed-off-by: Alejandro Conty <[email protected]>
1 parent 7294115 commit 36b1452

File tree

13 files changed

+312
-27
lines changed

13 files changed

+312
-27
lines changed

src/libbsdl/bsdl.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function(ADD_BSDL_LIBRARY NAME)
2121

2222
if (DEFINED bsdl_SPECTRAL_COLOR_SPACES)
2323
add_executable(jakobhanika_luts ${CMAKE_CURRENT_SOURCE_DIR}/${bsdl_SUBDIR}/src/jakobhanika_luts.cpp)
24-
target_link_libraries(genluts PRIVATE Threads::Threads)
24+
target_link_libraries(jakobhanika_luts PRIVATE Threads::Threads)
2525
foreach(CS ${bsdl_SPECTRAL_COLOR_SPACES})
2626
set(JACOBHANIKA_${CS} ${CMAKE_CURRENT_BINARY_DIR}/jakobhanika_${CS}.cpp)
2727
list(APPEND BSDL_LUTS_CPP ${JACOBHANIKA_${CS}})
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright Contributors to the Open Shading Language project.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
6+
#pragma once
7+
8+
#include <BSDL/bsdf_decl.h>
9+
#include <BSDL/microfacet_tools_decl.h>
10+
11+
BSDL_ENTER_NAMESPACE
12+
13+
namespace mtx {
14+
15+
struct ConductorFresnel {
16+
BSDL_INLINE_METHOD ConductorFresnel() {}
17+
18+
BSDL_INLINE_METHOD
19+
ConductorFresnel(Power IOR, Power extinction, float lambda_0);
20+
BSDL_INLINE_METHOD Power eval(float cos_theta) const;
21+
BSDL_INLINE_METHOD Power F0() const;
22+
23+
BSDL_INLINE_METHOD Power avg() const;
24+
25+
private:
26+
Power IOR, extinction;
27+
float lambda_0;
28+
};
29+
30+
template<typename BSDF_ROOT> struct ConductorLobe : public Lobe<BSDF_ROOT> {
31+
using Base = Lobe<BSDF_ROOT>;
32+
struct Data {
33+
// microfacet params
34+
Imath::V3f N, U;
35+
float roughness_x;
36+
float roughness_y;
37+
// fresnel params
38+
Imath::C3f IOR;
39+
Imath::C3f extinction;
40+
const char* distribution;
41+
using lobe_type = ConductorLobe<BSDF_ROOT>;
42+
};
43+
template<typename D> static typename LobeRegistry<D>::Entry entry()
44+
{
45+
static_assert(
46+
std::is_base_of<Data, D>::value); // Make no other assumptions
47+
using R = LobeRegistry<D>;
48+
return { name(),
49+
{ R::param(&D::N), R::param(&D::U), R::param(&D::roughness_x),
50+
R::param(&D::roughness_y), R::param(&D::IOR),
51+
R::param(&D::extinction), R::param(&D::distribution),
52+
R::close() } };
53+
}
54+
55+
template<typename T>
56+
BSDL_INLINE_METHOD ConductorLobe(T*, const BsdfGlobals& globals,
57+
const Data& data);
58+
59+
static const char* name() { return "conductor_bsdf"; }
60+
61+
BSDL_INLINE_METHOD Power albedo_impl() const { return fresnel.avg(); }
62+
63+
BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo,
64+
const Imath::V3f& wi) const;
65+
BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo,
66+
const Imath::V3f& rnd) const;
67+
68+
private:
69+
GGXDist dist;
70+
ConductorFresnel fresnel;
71+
float E_ms;
72+
};
73+
74+
} // namespace mtx
75+
76+
BSDL_LEAVE_NAMESPACE
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright Contributors to the Open Shading Language project.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
6+
#pragma once
7+
8+
#include <BSDL/MTX/bsdf_conductor_decl.h>
9+
10+
BSDL_ENTER_NAMESPACE
11+
12+
namespace mtx {
13+
14+
BSDL_INLINE_METHOD
15+
ConductorFresnel::ConductorFresnel(Power IOR, Power extinction, float lambda_0)
16+
: IOR(IOR), extinction(extinction), lambda_0(lambda_0)
17+
{
18+
}
19+
20+
BSDL_INLINE_METHOD Power
21+
ConductorFresnel::eval(float cos_theta) const
22+
{
23+
cos_theta = CLAMP(cos_theta, 0.0f, 1.0f);
24+
const Power one(1, lambda_0);
25+
const Power cosTheta2(cos_theta * cos_theta, lambda_0);
26+
const Power sinTheta2 = one - cosTheta2;
27+
const Power& n = IOR;
28+
const Power& k = extinction;
29+
const Power n2 = n * n;
30+
const Power k2 = k * k;
31+
const Power t0 = n2 - k2 - sinTheta2;
32+
const Power a2plusb2 = sqrt(t0 * t0 + 4 * n2 * k2);
33+
const Power t1 = a2plusb2 + cosTheta2;
34+
const Power a = sqrt(0.5f * (a2plusb2 + t0));
35+
const Power t2 = (2.0f * cos_theta) * a;
36+
const Power rs = (t1 - t2) / (t1 + t2).clamped(FLOAT_MIN, BIG);
37+
38+
const Power t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2;
39+
const Power t4 = t2 * sinTheta2;
40+
const Power rp = rs * (t3 - t4) / (t3 + t4).clamped(FLOAT_MIN, BIG);
41+
42+
return 0.5f * (rp + rs).clamped(0, 2);
43+
}
44+
45+
BSDL_INLINE_METHOD Power
46+
ConductorFresnel::F0() const
47+
{
48+
const Power one(1, lambda_0);
49+
const Power& n = IOR;
50+
const Power& k = extinction;
51+
const Power n2 = n * n;
52+
const Power k2 = k * k;
53+
const Power t0 = n2 - k2;
54+
const Power a2plusb2 = sqrt(t0 * t0 + 4 * n2 * k2);
55+
const Power t1 = a2plusb2 + one;
56+
const Power a = sqrt(0.5f * (a2plusb2 + t0));
57+
const Power t2 = 2.0f * a;
58+
const Power rs = (t1 - t2) / (t1 + t2).clamped(FLOAT_MIN, BIG);
59+
60+
return rs.clamped(0, 1);
61+
}
62+
63+
BSDL_INLINE_METHOD Power
64+
ConductorFresnel::avg() const
65+
{
66+
return Power(
67+
[&](int i) {
68+
// Very simple fit for cosine weighted average fresnel. Not very
69+
// accurate but enough for albedo based sampling decisions.
70+
constexpr float a = -0.32775145f, b = 0.18346033f, c = 0.61146583f,
71+
d = -0.07785134f;
72+
const float x = IOR[i], y = extinction[i];
73+
const float p = a + b * x + c * y + d * x * y;
74+
return p / (1 + p);
75+
},
76+
lambda_0);
77+
}
78+
79+
template<typename BSDF_ROOT>
80+
template<typename T>
81+
BSDL_INLINE_METHOD
82+
ConductorLobe<BSDF_ROOT>::ConductorLobe(T* lobe, const BsdfGlobals& globals,
83+
const Data& data)
84+
: Base(lobe, globals.visible_normal(data.N), data.U, 0.0f, globals.lambda_0,
85+
false)
86+
{
87+
Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true);
88+
// MaterialX expects the raw x/y roughness as input, but for albedo tables it
89+
// is better to use the roughness/anisotropy parametrization so we can
90+
// ignore roughness
91+
const float rx = CLAMP(data.roughness_x, EPSILON, 2.0f);
92+
const float ry = CLAMP(data.roughness_y, EPSILON, 2.0f);
93+
const float ax = std::max(rx, ry);
94+
const float ay = std::min(rx, ry);
95+
const float b = ay / ax;
96+
const float aniso = (1 - b) / (1 + b);
97+
// Also assume we square the roughness for linearity
98+
const float roughness = globals.regularize_roughness(
99+
sqrtf(ax / (1 + aniso)));
100+
const float cosNO = Base::frame.Z.dot(globals.wo);
101+
// Flip aniso if roughness_x < roughness_y
102+
dist = GGXDist(roughness, aniso, (rx < ry));
103+
fresnel = ConductorFresnel(globals.wave(data.IOR),
104+
globals.wave(data.extinction), globals.lambda_0);
105+
TabulatedEnergyCurve<spi::MiniMicrofacetGGX> curve(roughness, 0.0f);
106+
E_ms = curve.Emiss_eval(cosNO);
107+
Base::set_roughness(roughness);
108+
}
109+
110+
template<typename BSDF_ROOT>
111+
BSDL_INLINE_METHOD Sample
112+
ConductorLobe<BSDF_ROOT>::eval_impl(const Imath::V3f& wo,
113+
const Imath::V3f& wi) const
114+
{
115+
Sample s = eval_turquin_microms_reflection(dist, fresnel, E_ms, wo, wi);
116+
s.roughness = Base::roughness();
117+
return s;
118+
}
119+
120+
template<typename BSDF_ROOT>
121+
BSDL_INLINE_METHOD Sample
122+
ConductorLobe<BSDF_ROOT>::sample_impl(const Imath::V3f& wo,
123+
const Imath::V3f& rnd) const
124+
{
125+
const float cosNO = wo.z;
126+
if (cosNO <= 0)
127+
return {};
128+
129+
// sample microfacet (half vector)
130+
// generate outgoing direction
131+
Imath::V3f wi = reflect(wo, dist.sample(wo, rnd.x, rnd.y));
132+
// evaluate brdf on outgoing direction
133+
return eval_impl(wo, wi);
134+
}
135+
136+
} // namespace mtx
137+
138+
BSDL_LEAVE_NAMESPACE

src/libbsdl/include/BSDL/microfacet_tools_decl.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ BSDL_ENTER_NAMESPACE
1414
struct GGXDist {
1515
BSDL_INLINE_METHOD GGXDist() : ax(0), ay(0) {}
1616

17-
BSDL_INLINE_METHOD GGXDist(float rough, float aniso)
17+
BSDL_INLINE_METHOD GGXDist(float rough, float aniso,
18+
bool flip_aniso = false)
1819
: ax(SQR(rough)), ay(ax)
1920
{
2021
assert(rough >= 0 && rough <= 1);
2122
assert(aniso >= 0 && aniso <= 1);
23+
if (flip_aniso)
24+
aniso = -aniso;
2225
constexpr float ALPHA_MIN = 1e-5f;
2326
ax = std::max(ax * (1 + aniso), ALPHA_MIN);
2427
ay = std::max(ay * (1 - aniso), ALPHA_MIN);
@@ -127,4 +130,11 @@ template<typename Fresnel> struct MicrofacetMS {
127130
float Eo_avg;
128131
};
129132

133+
// Turquin style microfacet with multiple scattering
134+
template<typename Dist, typename Fresnel>
135+
BSDL_INLINE Sample
136+
eval_turquin_microms_reflection(const Dist& dist, const Fresnel& fresnel,
137+
float E_ms, const Imath::V3f& wo,
138+
const Imath::V3f& wi);
139+
130140
BSDL_LEAVE_NAMESPACE

src/libbsdl/include/BSDL/microfacet_tools_impl.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,35 @@ MicrofacetMS<Fresnel>::computeFmiss() const
298298
return Fmiss;
299299
}
300300

301+
// Turquin style microfacet with multiple scattering
302+
template<typename Dist, typename Fresnel>
303+
BSDL_INLINE Sample
304+
eval_turquin_microms_reflection(const Dist& dist, const Fresnel& fresnel,
305+
float E_ms, const Imath::V3f& wo,
306+
const Imath::V3f& wi)
307+
{
308+
const float cosNO = wo.z;
309+
const float cosNI = wi.z;
310+
if (cosNI <= 0 || cosNO <= 0)
311+
return {};
312+
313+
// get half vector
314+
Imath::V3f m = (wo + wi).normalized();
315+
float cosMO = m.dot(wo);
316+
const float D = dist.D(m);
317+
const float G1 = dist.G1(wo);
318+
const float out = dist.G2_G1(wi, wo);
319+
float s_pdf = (G1 * D) / (4.0f * cosNO);
320+
// fresnel term between outgoing direction and microfacet
321+
const Power F = fresnel.eval(cosMO);
322+
// From "Practical multiple scattering compensation for microfacet models" - Emmanuel Turquin
323+
// equation 16. Should we use F0 for msf scaling? Doesn't make a big difference.
324+
const Power F_ms = F;
325+
const float msf = E_ms / (1 - E_ms);
326+
const Power one(1, 1);
327+
const Power O = out * F * (one + F_ms * msf);
328+
329+
return { wi, O, s_pdf, -1 /* roughness set by caller */ };
330+
}
331+
301332
BSDL_LEAVE_NAMESPACE

src/libbsdl/include/BSDL/spectrum_decl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,16 @@ struct Power {
307307
}
308308
};
309309

310+
BSDL_INLINE Power
311+
sqrt(Power x)
312+
{
313+
x = x.clamped(0.0f, std::numeric_limits<float>::max());
314+
BSDL_UNROLL()
315+
for (int i = 0; i != Power::N; ++i)
316+
x.data[i] = sqrtf(x.data[i]);
317+
return x;
318+
}
319+
310320
BSDL_INLINE Power
311321
operator-(Power o)
312322
{

0 commit comments

Comments
 (0)