Skip to content

Commit 2068828

Browse files
committed
Switch MX conductor to spi way for MS
Turquin style multiple scattering is very simple, but also way noiser than using a diffuse lobe for MS. This is because sampling microfacets for reflection is far from ideal at high roughnesses. I don't have any proof of one model being closer to reality than the other. They are close, and at low roughness I like Turquin look better. But in my production experience cleaner means faster renders and therefore better. So I suggest adopting a non-reciprocal flavor of the diffuse lobe with the original coloring based on the series summation. This is still very simple, cleaner and also recovers saturation. The computation cost itself is almost identical. Given that many times we'll be sampling a diffuse lobe instead of microfacets it could even be faster. I'm using the simplification of F_avg = F_ss, similar to Turquin to skip fitting anything, let the render average it. Signed-off-by: Alejandro Conty <[email protected]>
1 parent c71d804 commit 2068828

File tree

8 files changed

+85
-15
lines changed

8 files changed

+85
-15
lines changed

src/libbsdl/include/BSDL/MTX/bsdf_conductor_decl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
44

5-
65
#pragma once
76

87
#include <BSDL/bsdf_decl.h>
@@ -69,6 +68,7 @@ template<typename BSDF_ROOT> struct ConductorLobe : public Lobe<BSDF_ROOT> {
6968
GGXDist dist;
7069
ConductorFresnel fresnel;
7170
float E_ms;
71+
float E_ms_avg;
7272
};
7373

7474
} // namespace mtx

src/libbsdl/include/BSDL/MTX/bsdf_conductor_impl.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
44

5-
65
#pragma once
76

87
#include <BSDL/MTX/bsdf_conductor_decl.h>
@@ -103,7 +102,8 @@ ConductorLobe<BSDF_ROOT>::ConductorLobe(T* lobe, const BsdfGlobals& globals,
103102
fresnel = ConductorFresnel(globals.wave(data.IOR),
104103
globals.wave(data.extinction), globals.lambda_0);
105104
TabulatedEnergyCurve<spi::MiniMicrofacetGGX> curve(roughness, 0.0f);
106-
E_ms = curve.Emiss_eval(cosNO);
105+
E_ms = curve.Emiss_eval(cosNO);
106+
E_ms_avg = curve.get_Emiss_avg();
107107
Base::set_roughness(roughness);
108108
}
109109

@@ -112,7 +112,8 @@ BSDL_INLINE_METHOD Sample
112112
ConductorLobe<BSDF_ROOT>::eval_impl(const Imath::V3f& wo,
113113
const Imath::V3f& wi) const
114114
{
115-
Sample s = eval_turquin_microms_reflection(dist, fresnel, E_ms, wo, wi);
115+
Sample s = eval_spi_microms_reflection(dist, fresnel, E_ms, E_ms_avg, wo,
116+
wi);
116117
s.roughness = Base::roughness();
117118
return s;
118119
}
@@ -122,15 +123,10 @@ BSDL_INLINE_METHOD Sample
122123
ConductorLobe<BSDF_ROOT>::sample_impl(const Imath::V3f& wo,
123124
const Imath::V3f& rnd) const
124125
{
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);
126+
Sample s = sample_spi_microms_reflection(dist, fresnel, E_ms, E_ms_avg, wo,
127+
rnd);
128+
s.roughness = Base::roughness();
129+
return s;
134130
}
135131

136132
} // namespace mtx

src/libbsdl/include/BSDL/microfacet_tools_decl.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
44

5-
65
#pragma once
76

87
#include <BSDL/config.h>
@@ -137,4 +136,17 @@ eval_turquin_microms_reflection(const Dist& dist, const Fresnel& fresnel,
137136
float E_ms, const Imath::V3f& wo,
138137
const Imath::V3f& wi);
139138

139+
// SPI style microfacet with multiple scattering
140+
template<typename Dist, typename Fresnel>
141+
BSDL_INLINE Sample
142+
eval_spi_microms_reflection(const Dist& dist, const Fresnel& fresnel,
143+
float E_ms, float E_ms_avg, const Imath::V3f& wo,
144+
const Imath::V3f& wi);
145+
146+
template<typename Dist, typename Fresnel>
147+
BSDL_INLINE Sample
148+
sample_spi_microms_reflection(const Dist& dist, const Fresnel& fresnel,
149+
float E_ms, float E_ms_avg, const Imath::V3f& wo,
150+
const Imath::V3f& rnd);
151+
140152
BSDL_LEAVE_NAMESPACE

src/libbsdl/include/BSDL/microfacet_tools_impl.h

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
44

5-
65
#pragma once
76

87
#include <BSDL/config.h>
@@ -329,4 +328,67 @@ eval_turquin_microms_reflection(const Dist& dist, const Fresnel& fresnel,
329328
return { wi, O, s_pdf, -1 /* roughness set by caller */ };
330329
}
331330

331+
// SPI style microfacet with multiple scattering, with a diffuse lobe
332+
template<typename Dist, typename Fresnel>
333+
BSDL_INLINE Sample
334+
eval_spi_microms_reflection(const Dist& dist, const Fresnel& fresnel,
335+
float E_ms, float E_ms_avg, const Imath::V3f& wo,
336+
const Imath::V3f& wi)
337+
{
338+
const float cosNO = wo.z;
339+
const float cosNI = wi.z;
340+
if (cosNI <= 0 || cosNO <= 0)
341+
return {};
342+
343+
// get half vector
344+
Imath::V3f m = (wo + wi).normalized();
345+
float cosMO = m.dot(wo);
346+
const float D = dist.D(m);
347+
const float G1 = dist.G1(wo);
348+
const float out = dist.G2_G1(wi, wo);
349+
float s_pdf = (G1 * D) / (4.0f * cosNO);
350+
// fresnel term between outgoing direction and microfacet
351+
const Power F = fresnel.eval(cosMO);
352+
const Power O = out * F;
353+
354+
const Power one = Power(1, 1);
355+
// Like Turquin we are using F_avg = F instead of an average fresnel, the
356+
// render integrator has an average effect for high roughness.
357+
//const Power F0 = fresnel.F0();
358+
const Power F_avg = F;
359+
// Evaluate multiple scattering lobe, roughness set by caller. The
360+
// 1 / (1 - F_avg * E_ms_avg) term comes from the series summation.
361+
const Power O_ms = SQR(F_avg) * (1 - E_ms_avg) / (one - F_avg * E_ms_avg);
362+
363+
Sample ec = { wi, O_ms, E_ms * cosNI * ONEOVERPI, -1 };
364+
ec.update(O, s_pdf, 1 - E_ms);
365+
366+
return ec;
367+
}
368+
369+
template<typename Dist, typename Fresnel>
370+
BSDL_INLINE Sample
371+
sample_spi_microms_reflection(const Dist& dist, const Fresnel& fresnel,
372+
float E_ms, float E_ms_avg, const Imath::V3f& wo,
373+
const Imath::V3f& rnd)
374+
{
375+
const float cosNO = wo.z;
376+
if (cosNO <= 0)
377+
return {};
378+
379+
Imath::V3f wi;
380+
if (rnd.z < E_ms) {
381+
// sample diffuse energy compensation lobe
382+
wi = sample_cos_hemisphere(rnd.x, rnd.y);
383+
} else {
384+
// sample microfacet (half vector)
385+
// generate outgoing direction
386+
wi = reflect(wo, dist.sample(wo, rnd.x, rnd.y));
387+
if (wi.z <= 0)
388+
return {};
389+
}
390+
// evaluate brdf on outgoing direction
391+
return eval_spi_microms_reflection(dist, fresnel, E_ms, E_ms_avg, wo, wi);
392+
}
393+
332394
BSDL_LEAVE_NAMESPACE
2.53 KB
Binary file not shown.
0 Bytes
Binary file not shown.
-7 Bytes
Binary file not shown.
-13 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)