Skip to content

Commit b0e2a45

Browse files
fpsunflowerlgritz
authored andcommitted
Implement new Oren-Nayar model from OpenPBR (#1817)
* Implement new EON mode for Oren-Nayar closures (via an opt-in keyword arg for backwards compatibility) * Add test for energy conserving Oren-Nayar (on half the sphere so we keep checking the old mode as well) Signed-off-by: Chris Kulla <[email protected]>
1 parent ab48d84 commit b0e2a45

File tree

4 files changed

+101
-16
lines changed

4 files changed

+101
-16
lines changed

src/doc/languagespec.tex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4671,10 +4671,17 @@ \subsection{Surface BSDF closures}
46714671
% Optional string parameter to name this component. For use in AOVs / LPEs.
46724672
% \apiend
46734673

4674+
\apiitem{energy_compensation}
4675+
\vspace{12pt}
4676+
Optional int parameter to select if energy compensation should be applied.
4677+
\apiend
4678+
46744679
The Oren-Nayar reflection model is described in M.\ Oren and S.\ K.\
46754680
Nayar, ``Generalization of Lambert's Reflectance Model,'' Proceedings of
46764681
SIGGRAPH 1994, pp.239-246 (July, 1994).
46774682

4683+
The energy compensated model is described in the white paper: ``An energy-preserving Qualitative Oren-Nayar model'' by Jamie Portsmouth.
4684+
46784685
\apiend
46794686

46804687

src/testrender/shading.cpp

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ struct MxOrenNayarDiffuseParams {
101101
float roughness;
102102
// optional
103103
ustring label;
104+
int energy_compensation;
104105
};
105106

106107
struct MxBurleyDiffuseParams {
@@ -316,6 +317,8 @@ register_closures(OSL::ShadingSystem* shadingsys)
316317
CLOSURE_COLOR_PARAM(MxOrenNayarDiffuseParams, albedo),
317318
CLOSURE_FLOAT_PARAM(MxOrenNayarDiffuseParams, roughness),
318319
CLOSURE_STRING_KEYPARAM(MxOrenNayarDiffuseParams, label, "label"),
320+
CLOSURE_INT_KEYPARAM(MxOrenNayarDiffuseParams, energy_compensation,
321+
"energy_compensation"),
319322
CLOSURE_FINISH_PARAM(MxOrenNayarDiffuseParams) } },
320323
{ "burley_diffuse_bsdf",
321324
MX_BURLEY_DIFFUSE_ID,
@@ -459,23 +462,24 @@ template<int trans> struct Diffuse final : public BSDF, DiffuseParams {
459462
struct OrenNayar final : public BSDF, OrenNayarParams {
460463
OrenNayar(const OrenNayarParams& params) : BSDF(), OrenNayarParams(params)
461464
{
462-
// precompute some constants
463-
float s2 = sigma * sigma;
464-
A = 1 - 0.50f * s2 / (s2 + 0.33f);
465-
B = 0.45f * s2 / (s2 + 0.09f);
466465
}
467466
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
468467
{
469468
float NL = N.dot(wi);
470469
float NV = N.dot(wo);
471470
if (NL > 0 && NV > 0) {
471+
float LV = wo.dot(wi);
472+
float s = LV - NL * NV;
472473
// Simplified math from: "A tiny improvement of Oren-Nayar reflectance model"
473474
// by Yasuhiro Fujii
474475
// http://mimosa-pudica.net/improved-oren-nayar.html
475-
// NOTE: This is using the math to match the original ON model, not the tweak
476-
// proposed in the text which is a slightly different BRDF
477-
float LV = wo.dot(wi);
478-
float s = LV - NL * NV;
476+
// NOTE: This is using the math to match the original qualitative ON model
477+
// (QON in the paper above) and not the tweak proposed in the text which
478+
// is a slightly different BRDF (FON in the paper above). This is done for
479+
// backwards compatibility purposes only.
480+
float s2 = sigma * sigma;
481+
float A = 1 - 0.50f * s2 / (s2 + 0.33f);
482+
float B = 0.45f * s2 / (s2 + 0.09f);
479483
float stinv = s > 0 ? s / std::max(NL, NV) : 0.0f;
480484
return { wi, Color3(A + B * stinv), NL * float(M_1_PI), 1.0f };
481485
}
@@ -489,9 +493,77 @@ struct OrenNayar final : public BSDF, OrenNayarParams {
489493
Sampling::sample_cosine_hemisphere(N, rx, ry, out_dir, pdf);
490494
return eval(wo, out_dir);
491495
}
496+
};
497+
498+
struct EnergyCompensatedOrenNayar : public BSDF, MxOrenNayarDiffuseParams {
499+
EnergyCompensatedOrenNayar(const MxOrenNayarDiffuseParams& params)
500+
: BSDF(), MxOrenNayarDiffuseParams(params)
501+
{
502+
}
503+
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
504+
{
505+
float NL = N.dot(wi);
506+
float NV = N.dot(wo);
507+
if (NL > 0 && NV > 0) {
508+
float LV = wo.dot(wi);
509+
float s = LV - NL * NV;
510+
// Code below from Jamie Portsmouth's tech report on Energy conversion Oren-Nayar
511+
// See slack thread for whitepaper:
512+
// https://academysoftwarefdn.slack.com/files/U03SWQFPD08/F06S50CUKV1/oren_nayar.pdf
513+
514+
// TODO: rho should be the albedo which is a parameter of the closure in the Mx parameters
515+
// This only matters for the color-saturation aspect of the BRDF which is rather subtle anyway
516+
// and not always desireable for artists. Hardcoding to 1 leaves the coloring entirely up to the
517+
// closure weight.
518+
519+
const Color3 rho = albedo;
520+
const float sigma = roughness;
521+
522+
float AF = 1.0f / (1.0f + constant1_FON * sigma);
523+
float stinv = s > 0 ? s / std::max(NL, NV) : s;
524+
float f_ss = AF * (1.0 + sigma * stinv); // single-scatt. BRDF
525+
float EFo = E_FON_analytic(NV); // EFo at rho=1 (analytic)
526+
float EFi = E_FON_analytic(NL); // EFi at rho=1 (analytic)
527+
float avgEF = AF * (1.0f + constant2_FON * sigma); // avg. albedo
528+
Color3 rho_ms = (rho * rho) * avgEF
529+
/ (Color3(1.0f)
530+
- rho * std::max(0.0f, 1.0f - avgEF));
531+
float f_ms = std::max(1e-7f, 1.0f - EFo)
532+
* std::max(1e-7f, 1.0f - EFi)
533+
/ std::max(1e-7f, 1.0f - avgEF); // multi-scatter lobe
534+
return { wi, Color3(rho * f_ss + rho_ms * f_ms), NL * float(M_1_PI),
535+
1.0f };
536+
}
537+
return {};
538+
}
539+
540+
Sample sample(const Vec3& wo, float rx, float ry,
541+
float /*rz*/) const override
542+
{
543+
Vec3 out_dir;
544+
float pdf;
545+
Sampling::sample_cosine_hemisphere(N, rx, ry, out_dir, pdf);
546+
return eval(wo, out_dir);
547+
}
492548

493549
private:
494-
float A, B;
550+
static constexpr float constant1_FON = float(0.5 - 2.0 / (3.0 * M_PI));
551+
static constexpr float constant2_FON = float(2.0 / 3.0
552+
- 28.0 / (15.0 * M_PI));
553+
554+
float E_FON_analytic(float mu) const
555+
{
556+
const float sigma = roughness;
557+
float AF = 1.0f
558+
/ (1.0f
559+
+ constant1_FON * sigma); // Fujii model A coefficient
560+
float BF = sigma * AF; // Fujii model B coefficient
561+
float Si = sqrtf(std::max(0.0f, 1.0f - mu * mu));
562+
float G = Si * (OIIO::fast_acos(mu) - Si * mu)
563+
+ 2.0 * ((Si / mu) * (1.0 - Si * Si * Si) - Si) / 3.0f;
564+
float E = AF + (BF * float(M_1_PI)) * G;
565+
return E;
566+
}
495567
};
496568

497569
struct Phong final : public BSDF, PhongParams {
@@ -1615,14 +1687,20 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result,
16151687
ok = result.bsdf.add_bsdf<Transparent>(cw);
16161688
break;
16171689
case MX_OREN_NAYAR_DIFFUSE_ID: {
1618-
// translate MaterialX parameters into existing closure
16191690
const MxOrenNayarDiffuseParams* srcparams
16201691
= comp->as<MxOrenNayarDiffuseParams>();
1621-
OrenNayarParams params = {};
1622-
params.N = srcparams->N;
1623-
params.sigma = srcparams->roughness;
1624-
ok = result.bsdf.add_bsdf<OrenNayar>(cw * srcparams->albedo,
1625-
params);
1692+
if (srcparams->energy_compensation) {
1693+
// energy compensation handled by its own BSDF
1694+
ok = result.bsdf.add_bsdf<EnergyCompensatedOrenNayar>(
1695+
cw, *srcparams);
1696+
} else {
1697+
// translate MaterialX parameters into existing closure
1698+
OrenNayarParams params = {};
1699+
params.N = srcparams->N;
1700+
params.sigma = srcparams->roughness;
1701+
ok = result.bsdf.add_bsdf<OrenNayar>(cw * srcparams->albedo,
1702+
params);
1703+
}
16261704
break;
16271705
}
16281706
case MX_BURLEY_DIFFUSE_ID: {

testsuite/render-mx-furnace-oren-nayar/matte.osl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ matte
1818
float UImin = 0, float UImax = 1 ]]
1919
)
2020
{
21-
Ci = Kd * oren_nayar_diffuse_bsdf (N, Cs, roughness);
21+
Ci = Kd * oren_nayar_diffuse_bsdf (N, Cs, roughness, "energy_compensation", N.y < 0);
2222
}
-862 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)